diff --git a/main.go b/main.go index 44c32b3f..76afbc20 100644 --- a/main.go +++ b/main.go @@ -37,17 +37,13 @@ func (s ParcelService) Register(client int, address string) (Parcel, error) { Address: address, CreatedAt: time.Now().UTC().Format(time.RFC3339), } - id, err := s.store.Add(parcel) if err != nil { return parcel, err } - parcel.Number = id - fmt.Printf("Новая посылка № %d на адрес %s от клиента с идентификатором %d зарегистрирована %s\n", parcel.Number, parcel.Address, parcel.Client, parcel.CreatedAt) - return parcel, nil } @@ -56,14 +52,12 @@ func (s ParcelService) PrintClientParcels(client int) error { if err != nil { return err } - fmt.Printf("Посылки клиента %d:\n", client) for _, parcel := range parcels { fmt.Printf("Посылка № %d на адрес %s от клиента с идентификатором %d зарегистрирована %s, статус %s\n", parcel.Number, parcel.Address, parcel.Client, parcel.CreatedAt, parcel.Status) } fmt.Println() - return nil } @@ -72,19 +66,16 @@ func (s ParcelService) NextStatus(number int) error { if err != nil { return err } - var nextStatus string switch parcel.Status { case ParcelStatusRegistered: nextStatus = ParcelStatusSent case ParcelStatusSent: nextStatus = ParcelStatusDelivered - case ParcelStatusDelivered: + default: return nil } - fmt.Printf("У посылки № %d новый статус: %s\n", number, nextStatus) - return s.store.SetStatus(number, nextStatus) } @@ -97,12 +88,15 @@ func (s ParcelService) Delete(number int) error { } func main() { - // настройте подключение к БД - - store := // создайте объект ParcelStore функцией NewParcelStore + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + fmt.Println(err) + return + } + defer db.Close() + store := NewParcelStore(db) service := NewParcelService(store) - // регистрация посылки client := 1 address := "Псков, д. Пушкина, ул. Колотушкина, д. 5" p, err := service.Register(client, address) @@ -111,62 +105,43 @@ func main() { return } - // изменение адреса newAddress := "Саратов, д. Верхние Зори, ул. Козлова, д. 25" - err = service.ChangeAddress(p.Number, newAddress) - if err != nil { + if err := service.ChangeAddress(p.Number, newAddress); err != nil { fmt.Println(err) return } - // изменение статуса - err = service.NextStatus(p.Number) - if err != nil { + if err := service.NextStatus(p.Number); err != nil { fmt.Println(err) return } - // вывод посылок клиента - err = service.PrintClientParcels(client) - if err != nil { + if err := service.PrintClientParcels(client); err != nil { fmt.Println(err) return } - // попытка удаления отправленной посылки - err = service.Delete(p.Number) - if err != nil { - fmt.Println(err) - return + if err := service.Delete(p.Number); err != nil { + fmt.Println(err) // ожидаемая ошибка } - // вывод посылок клиента - // предыдущая посылка не должна удалиться, т.к. её статус НЕ «зарегистрирована» - err = service.PrintClientParcels(client) - if err != nil { + if err := service.PrintClientParcels(client); err != nil { fmt.Println(err) return } - // регистрация новой посылки p, err = service.Register(client, address) if err != nil { fmt.Println(err) return } - // удаление новой посылки - err = service.Delete(p.Number) - if err != nil { + if err := service.Delete(p.Number); err != nil { fmt.Println(err) return } - // вывод посылок клиента - // здесь не должно быть последней посылки, т.к. она должна была успешно удалиться - err = service.PrintClientParcels(client) - if err != nil { + if err := service.PrintClientParcels(client); err != nil { fmt.Println(err) - return } } diff --git a/parcel.go b/parcel.go index db6c815d..8da86210 100644 --- a/parcel.go +++ b/parcel.go @@ -2,6 +2,7 @@ package main import ( "database/sql" + "fmt" ) type ParcelStore struct { @@ -9,52 +10,110 @@ type ParcelStore struct { } func NewParcelStore(db *sql.DB) ParcelStore { + _, _ = db.Exec(`CREATE TABLE IF NOT EXISTS parcel( + number INTEGER PRIMARY KEY AUTOINCREMENT, + client INTEGER NOT NULL, + status TEXT NOT NULL, + address TEXT NOT NULL, + created_at TEXT NOT NULL + )`) return ParcelStore{db: db} } 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(?,?,?,?)`, + p.Client, p.Status, p.Address, p.CreatedAt, + ) + if err != nil { + return 0, err + } + id, err := res.LastInsertId() + return int(id), err } 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, + ) + var p Parcel + if err := row.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt); err != nil { + return p, err + } return p, nil } func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { - // реализуйте чтение строк из таблицы parcel по заданному client - // здесь из таблицы может вернуться несколько строк - - // заполните срез Parcel данными из таблицы + rows, err := s.db.Query( + `SELECT number,client,status,address,created_at FROM parcel WHERE client=?`, + client, + ) + if err != nil { + return nil, err + } + defer rows.Close() var res []Parcel - + for rows.Next() { + var p Parcel + if err := rows.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt); err != nil { + return nil, err + } + res = append(res, p) + } + if err := rows.Err(); err != nil { + return nil, err + } return res, nil } func (s ParcelStore) SetStatus(number int, status string) error { - // реализуйте обновление статуса в таблице parcel - + res, err := s.db.Exec(`UPDATE parcel SET status=? WHERE number=?`, status, number) + if err != nil { + return err + } + aff, err := res.RowsAffected() + if err != nil { + return err + } + if aff == 0 { + return fmt.Errorf("parcel %d not found", number) + } return nil } func (s ParcelStore) SetAddress(number int, address string) error { - // реализуйте обновление адреса в таблице parcel - // менять адрес можно только если значение статуса registered - + res, err := s.db.Exec( + `UPDATE parcel SET address=? WHERE number=? AND status=?`, + address, number, ParcelStatusRegistered, + ) + if err != nil { + return err + } + aff, err := res.RowsAffected() + if err != nil { + return err + } + if aff == 0 { + return fmt.Errorf("cannot change address") + } return nil } func (s ParcelStore) Delete(number int) error { - // реализуйте удаление строки из таблицы parcel - // удалять строку можно только если значение статуса registered - + res, err := s.db.Exec( + `DELETE FROM parcel WHERE number=? AND status=?`, + number, ParcelStatusRegistered, + ) + if err != nil { + return err + } + aff, err := res.RowsAffected() + if err != nil { + return err + } + if aff == 0 { + return fmt.Errorf("cannot delete parcel") + } return nil } diff --git a/parcel_test.go b/parcel_test.go index d1b93827..5a1d2f4b 100644 --- a/parcel_test.go +++ b/parcel_test.go @@ -6,19 +6,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + _ "modernc.org/sqlite" ) var ( - // randSource источник псевдо случайных чисел. - // Для повышения уникальности в качестве seed - // используется текущее время в unix формате (в виде числа) randSource = rand.NewSource(time.Now().UnixNano()) - // randRange использует randSource для генерации случайных чисел - randRange = rand.New(randSource) + randRange = rand.New(randSource) ) -// getTestParcel возвращает тестовую посылку func getTestParcel() Parcel { return Parcel{ Client: 1000, @@ -28,94 +25,85 @@ func getTestParcel() Parcel { } } -// TestAddGetDelete проверяет добавление, получение и удаление посылки func TestAddGetDelete(t *testing.T) { - // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", ":memory:") + require.NoError(t, err) store := NewParcelStore(db) - parcel := getTestParcel() - // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + p := getTestParcel() + id, err := store.Add(p) + require.NoError(t, err) + require.NotZero(t, id) - // get - // получите только что добавленную посылку, убедитесь в отсутствии ошибки - // проверьте, что значения всех полей в полученном объекте совпадают со значениями полей в переменной parcel + p.Number = id + got, err := store.Get(id) + require.NoError(t, err) + assert.Equal(t, p, got) - // delete - // удалите добавленную посылку, убедитесь в отсутствии ошибки - // проверьте, что посылку больше нельзя получить из БД + require.NoError(t, store.Delete(id)) + _, err = store.Get(id) + require.ErrorIs(t, err, sql.ErrNoRows) } -// TestSetAddress проверяет обновление адреса func TestSetAddress(t *testing.T) { - // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", ":memory:") + require.NoError(t, err) + store := NewParcelStore(db) - // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + p := getTestParcel() + id, err := store.Add(p) + require.NoError(t, err) - // set address - // обновите адрес, убедитесь в отсутствии ошибки newAddress := "new test address" + require.NoError(t, store.SetAddress(id, newAddress)) - // check - // получите добавленную посылку и убедитесь, что адрес обновился + got, err := store.Get(id) + require.NoError(t, err) + assert.Equal(t, newAddress, got.Address) } -// TestSetStatus проверяет обновление статуса func TestSetStatus(t *testing.T) { - // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", ":memory:") + require.NoError(t, err) + store := NewParcelStore(db) - // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + p := getTestParcel() + id, err := store.Add(p) + require.NoError(t, err) - // set status - // обновите статус, убедитесь в отсутствии ошибки + require.NoError(t, store.SetStatus(id, ParcelStatusSent)) - // check - // получите добавленную посылку и убедитесь, что статус обновился + got, err := store.Get(id) + require.NoError(t, err) + assert.Equal(t, ParcelStatusSent, got.Status) } -// TestGetByClient проверяет получение посылок по идентификатору клиента func TestGetByClient(t *testing.T) { - // prepare - db, err := // настройте подключение к БД - - parcels := []Parcel{ - getTestParcel(), - getTestParcel(), - getTestParcel(), - } - parcelMap := map[int]Parcel{} + db, err := sql.Open("sqlite", ":memory:") + require.NoError(t, err) + store := NewParcelStore(db) - // задаём всем посылкам один и тот же идентификатор клиента + parcels := []Parcel{getTestParcel(), getTestParcel(), getTestParcel()} client := randRange.Intn(10_000_000) - parcels[0].Client = client - parcels[1].Client = client - parcels[2].Client = client - - // add - for i := 0; i < len(parcels); i++ { - id, err := // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - // обновляем идентификатор добавленной у посылки + for i := range parcels { + parcels[i].Client = client + id, err := store.Add(parcels[i]) + require.NoError(t, err) parcels[i].Number = id - - // сохраняем добавленную посылку в структуру map, чтобы её можно было легко достать по идентификатору посылки - parcelMap[id] = parcels[i] } - // get by client - storedParcels, err := // получите список посылок по идентификатору клиента, сохранённого в переменной client - // убедитесь в отсутствии ошибки - // убедитесь, что количество полученных посылок совпадает с количеством добавленных + stored, err := store.GetByClient(client) + require.NoError(t, err) + require.Len(t, stored, len(parcels)) - // check - for _, parcel := range storedParcels { - // в parcelMap лежат добавленные посылки, ключ - идентификатор посылки, значение - сама посылка - // убедитесь, что все посылки из storedParcels есть в parcelMap - // убедитесь, что значения полей полученных посылок заполнены верно + storedMap := map[int]Parcel{} + for _, p := range stored { + storedMap[p.Number] = p + } + for _, exp := range parcels { + got, ok := storedMap[exp.Number] + require.True(t, ok) + assert.Equal(t, exp, got) } } diff --git a/tracker.db b/tracker.db index b6ba48a1..8c16ddab 100644 Binary files a/tracker.db and b/tracker.db differ