Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,15 @@ func (s ParcelService) Delete(number int) error {

func main() {
// настройте подключение к БД
db, err := sql.Open("sqlite", "tracker.db")
if err != nil {
fmt.Println(err)
return

}
defer db.Close()

store := // создайте объект ParcelStore функцией NewParcelStore
store := NewParcelStore(db) // создайте объект ParcelStore функцией NewParcelStore
service := NewParcelService(store)

// регистрация посылки
Expand Down
98 changes: 86 additions & 12 deletions parcel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"database/sql"
"fmt"
)

type ParcelStore struct {
Expand All @@ -13,48 +14,121 @@ func NewParcelStore(db *sql.DB) ParcelStore {
}

func (s ParcelStore) Add(p Parcel) (int, error) {
// реализуйте добавление строки в таблицу parcel, используйте данные из переменной p

// верните идентификатор последней добавленной записи
return 0, nil
res, err := s.db.Exec("INSERT INTO parcel (client, status, address, created_at) VALUES ( :client, :status, :address, :createdAt)",
sql.Named("client", p.Client),
sql.Named("status", p.Status),
sql.Named("address", p.Address),
sql.Named("createdAt", p.CreatedAt))
if err != nil {
return 0, fmt.Errorf("insert failed: %w", err)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хорошая формулировка, понятно что именно упало

}

id, err := res.LastInsertId()
if err != nil {
return 0, fmt.Errorf("scan id failed: %w", err)
}

return int(id), nil

}

func (s ParcelStore) Get(number int) (Parcel, error) {
// реализуйте чтение строки по заданному number
// здесь из таблицы должна вернуться только одна строка

// заполните объект Parcel данными из таблицы
p := Parcel{}
row := s.db.QueryRow("SELECT number, client, status, address, created_at FROM parcel WHERE number = :number", sql.Named("number", number))
err := row.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt)
if err != nil {
return Parcel{}, fmt.Errorf("select error: %w", err)
}

return p, nil
}

func (s ParcelStore) GetByClient(client int) ([]Parcel, error) {
// реализуйте чтение строк из таблицы parcel по заданному client
// здесь из таблицы может вернуться несколько строк
rows, err := s.db.Query(`SELECT number, client, status, address, created_at FROM parcel WHERE client = :client`, sql.Named("client", client))
if err != nil {
return nil, fmt.Errorf("select failed: %w", err)
}
defer rows.Close()

// заполните срез Parcel данными из таблицы
var res []Parcel

for rows.Next() {
p := Parcel{}

err := rows.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt)
if err != nil {

return res, fmt.Errorf("scan failed: %w", err)
}

res = append(res, p)
}
if err := rows.Err(); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 коректная работа с БД - проверка ошибки и rows закрываешь - отлично.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

на 68 строке, как я поняла проверяется, что построчное чтение rows прошло успешно, без прерывания цикла rows.Nex(). так?
я просто ориентировалась на теорию, не всегда понимая, что происходит(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, все так, проверяем что все верно вычитано без ошибок

return nil, fmt.Errorf("rows unpacking failed: %w", err)
}

return res, nil
}

func (s ParcelStore) SetStatus(number int, status string) error {
// реализуйте обновление статуса в таблице parcel

_, err := s.db.Exec("UPDATE parcel SET status = :status WHERE number = :number",
sql.Named("status", status),
sql.Named("number", number))
if err != nil {
return fmt.Errorf("update failed: %w", err)
}
return nil
}

func (s ParcelStore) GetStatus(number int) (string, error) {
// не хочу повторять код в двух других функциях, поэтому отдельно реализовала получение статуса
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хорошее решение.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(важно понимать что это будет дополнительный запрос. Бывают ситуации, когда нам важано ускориться и не делать лишние запросы. Тогда можно проверку статуса унести в основной запрос (например Update), в условие WHERE

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Например так?
что-то я не додумалась с первого раза. Так действительно проще) Спасибо!
_, err = s.db.Exec("UPDATE parcel SET address = :address WHERE number = :number AND status = :status",
sql.Named("address", address),
sql.Named("number", number),
sql.Named("status", ParcelStatusRegistered))
if err != nil {
return err
}
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, так

p := Parcel{}
row := s.db.QueryRow("SELECT status FROM parcel WHERE number = :number", sql.Named("number", number))
err := row.Scan(&p.Status)
if err != nil {
return "", fmt.Errorf("select failed: %w", err)
}

return p.Status, nil

}
func (s ParcelStore) SetAddress(number int, address string) error {
// реализуйте обновление адреса в таблице parcel
// менять адрес можно только если значение статуса registered

status, err := s.GetStatus(number)
if err != nil {
return fmt.Errorf("getStatus failed: %w", err)
}

if status != ParcelStatusRegistered {
return fmt.Errorf("address shouldn't be updated") // не знаю что возвратить
}
_, err = s.db.Exec("UPDATE parcel SET address = :address WHERE number = :number",
sql.Named("address", address),
sql.Named("number", number))
if err != nil {
return fmt.Errorf("update failed: %w", err)
}
return nil

}

func (s ParcelStore) Delete(number int) error {
// реализуйте удаление строки из таблицы parcel
// удалять строку можно только если значение статуса registered
status, err := s.GetStatus(number)
if err != nil {
return err
}

if status != ParcelStatusRegistered {
return fmt.Errorf("parcel shouldn't be deleted")
}

_, err = s.db.Exec("DELETE FROM parcel WHERE number = :number", sql.Named("number", number))
return fmt.Errorf("delete failed: %w", err)

return nil
}
90 changes: 66 additions & 24 deletions parcel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -31,57 +32,94 @@ func getTestParcel() Parcel {
// TestAddGetDelete проверяет добавление, получение и удаление посылки
func TestAddGetDelete(t *testing.T) {
// prepare
db, err := // настройте подключение к БД
db, err := sql.Open("sqlite", "tracker.db") // настройте подключение к БД
require.NoError(t, err)
store := NewParcelStore(db)
parcel := getTestParcel()

// add
// добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора
number, err := store.Add(parcel)
require.NoError(t, err)
require.NotEmpty(t, number)

// get
// получите только что добавленную посылку, убедитесь в отсутствии ошибки
// проверьте, что значения всех полей в полученном объекте совпадают со значениями полей в переменной parcel
getParcel, err := store.Get(number)
require.NoError(t, err)
//не можем проверить через assert.Equal(t, getParcel, parcel) тк функция Add не меняет 'number' у 'parcel'
assert.Equal(t, getParcel.Address, parcel.Address)
assert.Equal(t, getParcel.Client, parcel.Client)
assert.Equal(t, getParcel.CreatedAt, parcel.CreatedAt)
assert.Equal(t, getParcel.Status, parcel.Status)

err = store.Delete(number)
require.NoError(t, err)

_, err = store.Get(number)
require.Equal(t, sql.ErrNoRows, err)

// delete
// удалите добавленную посылку, убедитесь в отсутствии ошибки
// проверьте, что посылку больше нельзя получить из БД
}

// TestSetAddress проверяет обновление адреса
func TestSetAddress(t *testing.T) {
// prepare
db, err := // настройте подключение к БД
db, err := sql.Open("sqlite", "tracker.db") // настройте подключение к БД
require.NoError(t, err)
store := NewParcelStore(db)
parcel := getTestParcel()

// add
// добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора
number, err := store.Add(parcel)
require.NoError(t, err)
require.NotEmpty(t, number)

// set address
// обновите адрес, убедитесь в отсутствии ошибки
newAddress := "new test address"
err = store.SetAddress(number, newAddress)
require.NoError(t, err)

// check
// получите добавленную посылку и убедитесь, что адрес обновился
getParcel, err := store.Get(number)
require.NoError(t, err)
require.Equal(t, getParcel.Address, newAddress)

// delete
err = store.Delete(number)
require.NoError(t, err)

}

// TestSetStatus проверяет обновление статуса
func TestSetStatus(t *testing.T) {
// prepare
db, err := // настройте подключение к БД

db, err := sql.Open("sqlite", "tracker.db")
require.NoError(t, err)
// add
// добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора

store := NewParcelStore(db)
parcel := getTestParcel()

number, err := store.Add(parcel)
require.NoError(t, err)
require.NotEmpty(t, number)

// set status
// обновите статус, убедитесь в отсутствии ошибки
err = store.SetStatus(number, ParcelStatusDelivered)
require.NoError(t, err)

// check
// получите добавленную посылку и убедитесь, что статус обновился
getParcel, err := store.Get(number)
require.NoError(t, err)
require.Equal(t, getParcel.Status, ParcelStatusDelivered)
// deete
err = store.Delete(number)
require.NoError(t, err)

}

// TestGetByClient проверяет получение посылок по идентификатору клиента
func TestGetByClient(t *testing.T) {
// prepare
db, err := // настройте подключение к БД
db, err := sql.Open("sqlite", "tracker.db")
require.NoError(t, err)
store := NewParcelStore(db)

parcels := []Parcel{
getTestParcel(),
Expand All @@ -98,8 +136,9 @@ func TestGetByClient(t *testing.T) {

// add
for i := 0; i < len(parcels); i++ {
id, err := // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора

id, err := store.Add(parcels[i]) // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора
require.NoError(t, err)
require.NotEmpty(t, id)
// обновляем идентификатор добавленной у посылки
parcels[i].Number = id

Expand All @@ -108,14 +147,17 @@ func TestGetByClient(t *testing.T) {
}

// get by client
storedParcels, err := // получите список посылок по идентификатору клиента, сохранённого в переменной client
// убедитесь в отсутствии ошибки
// убедитесь, что количество полученных посылок совпадает с количеством добавленных
storedParcels, err := store.GetByClient(client)
require.NoError(t, err)
require.Equal(t, len(storedParcels), 3)

// check
for _, parcel := range storedParcels {
// в parcelMap лежат добавленные посылки, ключ - идентификатор посылки, значение - сама посылка
// убедитесь, что все посылки из storedParcels есть в parcelMap
require.NotEmpty(t, parcelMap[parcel.Number])
// убедитесь, что значения полей полученных посылок заполнены верно
require.Equal(t, parcel, parcelMap[parcel.Number])
}

}
Binary file modified tracker.db
Binary file not shown.