From a6547c1978a9816d52e6de688d9c873feafe555e Mon Sep 17 00:00:00 2001 From: "Bond, James Bond" Date: Sat, 31 May 2025 18:34:44 +0300 Subject: [PATCH 1/3] first commit --- main.go | 10 ++- parcel.go | 84 +++++++++++++++++++++---- parcel_test.go | 162 ++++++++++++++++++++++++++++--------------------- 3 files changed, 174 insertions(+), 82 deletions(-) diff --git a/main.go b/main.go index 44c32b3f..29f2879f 100644 --- a/main.go +++ b/main.go @@ -97,9 +97,13 @@ 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) // регистрация посылки diff --git a/parcel.go b/parcel.go index db6c815d..a106af04 100644 --- a/parcel.go +++ b/parcel.go @@ -2,6 +2,7 @@ package main import ( "database/sql" + "fmt" ) type ParcelStore struct { @@ -9,45 +10,100 @@ type ParcelStore struct { } func NewParcelStore(db *sql.DB) ParcelStore { + 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() + if err != nil { + return 0, err + } + + return int(id), nil + } func (s ParcelStore) Get(number int) (Parcel, error) { - // реализуйте чтение строки по заданному number - // здесь из таблицы должна вернуться только одна строка - - // заполните объект Parcel данными из таблицы p := Parcel{} - + err := s.db.QueryRow("select client, status, address, created_at from parcel where number = :number", + sql.Named("number", number)). + Scan(&p.Client, &p.Status, &p.Address, &p.CreatedAt) + if err != nil { + fmt.Println(err) + return p, err + } + p.Number = number return p, nil } func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { // реализуйте чтение строк из таблицы parcel по заданному client // здесь из таблицы может вернуться несколько строк - - // заполните срез Parcel данными из таблицы var res []Parcel - + rows, err := s.db.Query("select number, client, status, address, created_at from parcel where client = :client", sql.Named("client", client)) + if err != nil { + fmt.Println(err) + return res, err + } + defer rows.Close() + + for rows.Next() { + // заполните объект Parcel данными из таблицы + var resNumber, resClient int + var resStatus, resAddress, resCreatedAt string + + err := rows.Scan(&resNumber, &resClient, &resStatus, &resAddress, &resCreatedAt) + if err != nil { + fmt.Println(err) + return res, err + } + p := Parcel{ + Number: resNumber, + Client: resClient, + Status: resStatus, + Address: resAddress, + CreatedAt: resCreatedAt, + } + res = append(res, p) + } + if err := rows.Err(); err != nil { + fmt.Println(err) + return res, 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 { + fmt.Println(err) + return err + } return nil } func (s ParcelStore) SetAddress(number int, address string) error { // реализуйте обновление адреса в таблице parcel // менять адрес можно только если значение статуса registered + _, err := s.db.Exec("update parcel set address = :address where number = :number and status = 'registered'", + sql.Named("address", address), + sql.Named("number", number)) + if err != nil { + fmt.Println(err) + return err + } return nil } @@ -55,6 +111,12 @@ func (s ParcelStore) SetAddress(number int, address string) error { func (s ParcelStore) Delete(number int) error { // реализуйте удаление строки из таблицы parcel // удалять строку можно только если значение статуса registered + _, err := s.db.Exec("delete from parcel where number = :number and status = 'registered' ", + sql.Named("number", number)) + if err != nil { + fmt.Println(err) + return err + } return nil } diff --git a/parcel_test.go b/parcel_test.go index d1b93827..3e8e3dd9 100644 --- a/parcel_test.go +++ b/parcel_test.go @@ -10,112 +10,138 @@ import ( ) 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, + Client: randRange.Intn(1000000), Status: ParcelStatusRegistered, - Address: "test", + Address: "Test Address", CreatedAt: time.Now().UTC().Format(time.RFC3339), } } -// TestAddGetDelete проверяет добавление, получение и удаление посылки +// getTestStore создаёт ParcelStore с временной БД в памяти +func getTestStore(t *testing.T) ParcelStore { + db, err := sql.Open("sqlite", "file::memory:?cache=shared") + require.NoError(t, err) + + // Удалить таблицу, если она уже есть + _, err = db.Exec(`DROP TABLE IF EXISTS parcel`) + require.NoError(t, err) + + // Создать таблицу заново + _, err = db.Exec(`CREATE TABLE parcel ( + number INTEGER PRIMARY KEY AUTOINCREMENT, + client INTEGER NOT NULL, + status TEXT NOT NULL, + address TEXT NOT NULL, + created_at TEXT NOT NULL + )`) + require.NoError(t, err) + + return NewParcelStore(db) +} + func TestAddGetDelete(t *testing.T) { - // prepare - db, err := // настройте подключение к БД - store := NewParcelStore(db) + store := getTestStore(t) parcel := getTestParcel() - // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - - // get - // получите только что добавленную посылку, убедитесь в отсутствии ошибки - // проверьте, что значения всех полей в полученном объекте совпадают со значениями полей в переменной parcel - - // delete - // удалите добавленную посылку, убедитесь в отсутствии ошибки - // проверьте, что посылку больше нельзя получить из БД + // Add + id, err := store.Add(parcel) + require.NoError(t, err) + require.NotZero(t, id) + + // Get + stored, err := store.Get(id) + require.NoError(t, err) + require.Equal(t, parcel.Client, stored.Client) + require.Equal(t, parcel.Status, stored.Status) + require.Equal(t, parcel.Address, stored.Address) + + // Delete + err = store.Delete(id) + require.NoError(t, err) + + // Get again + _, err = store.Get(id) + require.Error(t, err) } -// TestSetAddress проверяет обновление адреса func TestSetAddress(t *testing.T) { - // prepare - db, err := // настройте подключение к БД + store := getTestStore(t) + parcel := getTestParcel() - // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + // Add + id, err := store.Add(parcel) + require.NoError(t, err) - // set address - // обновите адрес, убедитесь в отсутствии ошибки - newAddress := "new test address" + // Set Address + newAddress := "Updated Address" + err = store.SetAddress(id, newAddress) + require.NoError(t, err) - // check - // получите добавленную посылку и убедитесь, что адрес обновился + // Get + stored, err := store.Get(id) + require.NoError(t, err) + require.Equal(t, newAddress, stored.Address) } -// TestSetStatus проверяет обновление статуса func TestSetStatus(t *testing.T) { - // prepare - db, err := // настройте подключение к БД + store := getTestStore(t) + parcel := getTestParcel() - // add - // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора + // Add + id, err := store.Add(parcel) + require.NoError(t, err) - // set status - // обновите статус, убедитесь в отсутствии ошибки + // Set Status + newStatus := ParcelStatusSent + err = store.SetStatus(id, newStatus) + require.NoError(t, err) - // check - // получите добавленную посылку и убедитесь, что статус обновился + // Get + stored, err := store.Get(id) + require.NoError(t, err) + require.Equal(t, newStatus, stored.Status) } -// TestGetByClient проверяет получение посылок по идентификатору клиента func TestGetByClient(t *testing.T) { - // prepare - db, err := // настройте подключение к БД + store := getTestStore(t) + clientID := randRange.Intn(1000000) parcels := []Parcel{ getTestParcel(), getTestParcel(), getTestParcel(), } - parcelMap := map[int]Parcel{} - - // задаём всем посылкам один и тот же идентификатор клиента - 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 = clientID + 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 - // убедитесь в отсутствии ошибки - // убедитесь, что количество полученных посылок совпадает с количеством добавленных - - // check - for _, parcel := range storedParcels { - // в parcelMap лежат добавленные посылки, ключ - идентификатор посылки, значение - сама посылка - // убедитесь, что все посылки из storedParcels есть в parcelMap - // убедитесь, что значения полей полученных посылок заполнены верно + stored, err := store.GetByClient(clientID) + require.NoError(t, err) + require.Len(t, stored, len(parcels)) + + // Проверим, что каждая посылка есть в ответе + for _, original := range parcels { + var found bool + for _, s := range stored { + if s.Number == original.Number { + require.Equal(t, original.Address, s.Address) + require.Equal(t, original.Status, s.Status) + require.Equal(t, original.Client, s.Client) + found = true + break + } + } + require.True(t, found, "Посылка %d не найдена", original.Number) } } From 307598e292cfaa6c563db4aabe4f61b59e9db18d Mon Sep 17 00:00:00 2001 From: "Bond, James Bond" Date: Mon, 2 Jun 2025 08:59:09 +0300 Subject: [PATCH 2/3] second commit --- parcel.go | 27 +++++-------------------- parcel_test.go | 55 +++++++++++++++----------------------------------- 2 files changed, 21 insertions(+), 61 deletions(-) diff --git a/parcel.go b/parcel.go index a106af04..07848fc7 100644 --- a/parcel.go +++ b/parcel.go @@ -2,7 +2,6 @@ package main import ( "database/sql" - "fmt" ) type ParcelStore struct { @@ -38,8 +37,7 @@ func (s ParcelStore) Get(number int) (Parcel, error) { sql.Named("number", number)). Scan(&p.Client, &p.Status, &p.Address, &p.CreatedAt) if err != nil { - fmt.Println(err) - return p, err + return Parcel{}, err } p.Number = number return p, nil @@ -51,33 +49,21 @@ func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { var res []Parcel rows, err := s.db.Query("select number, client, status, address, created_at from parcel where client = :client", sql.Named("client", client)) if err != nil { - fmt.Println(err) return res, err } defer rows.Close() for rows.Next() { // заполните объект Parcel данными из таблицы - var resNumber, resClient int - var resStatus, resAddress, resCreatedAt string - - err := rows.Scan(&resNumber, &resClient, &resStatus, &resAddress, &resCreatedAt) + p := Parcel{} + err := rows.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) if err != nil { - fmt.Println(err) - return res, err - } - p := Parcel{ - Number: resNumber, - Client: resClient, - Status: resStatus, - Address: resAddress, - CreatedAt: resCreatedAt, + return nil, err } res = append(res, p) } if err := rows.Err(); err != nil { - fmt.Println(err) - return res, err + return nil, err } return res, nil } @@ -88,7 +74,6 @@ func (s ParcelStore) SetStatus(number int, status string) error { sql.Named("status", status), sql.Named("number", number)) if err != nil { - fmt.Println(err) return err } return nil @@ -101,7 +86,6 @@ func (s ParcelStore) SetAddress(number int, address string) error { sql.Named("address", address), sql.Named("number", number)) if err != nil { - fmt.Println(err) return err } @@ -114,7 +98,6 @@ func (s ParcelStore) Delete(number int) error { _, err := s.db.Exec("delete from parcel where number = :number and status = 'registered' ", sql.Named("number", number)) if err != nil { - fmt.Println(err) return err } diff --git a/parcel_test.go b/parcel_test.go index 3e8e3dd9..6a349c53 100644 --- a/parcel_test.go +++ b/parcel_test.go @@ -2,22 +2,17 @@ package main import ( "database/sql" - "math/rand" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var ( - randSource = rand.NewSource(time.Now().UnixNano()) - randRange = rand.New(randSource) -) - -// getTestParcel возвращает тестовую посылку +// getTestParcel возвращает тестовую посылку с фиксированными значениями func getTestParcel() Parcel { return Parcel{ - Client: randRange.Intn(1000000), + Client: 42, Status: ParcelStatusRegistered, Address: "Test Address", CreatedAt: time.Now().UTC().Format(time.RFC3339), @@ -29,11 +24,9 @@ func getTestStore(t *testing.T) ParcelStore { db, err := sql.Open("sqlite", "file::memory:?cache=shared") require.NoError(t, err) - // Удалить таблицу, если она уже есть _, err = db.Exec(`DROP TABLE IF EXISTS parcel`) require.NoError(t, err) - // Создать таблицу заново _, err = db.Exec(`CREATE TABLE parcel ( number INTEGER PRIMARY KEY AUTOINCREMENT, client INTEGER NOT NULL, @@ -50,23 +43,19 @@ func TestAddGetDelete(t *testing.T) { store := getTestStore(t) parcel := getTestParcel() - // Add id, err := store.Add(parcel) require.NoError(t, err) require.NotZero(t, id) - // Get stored, err := store.Get(id) require.NoError(t, err) - require.Equal(t, parcel.Client, stored.Client) - require.Equal(t, parcel.Status, stored.Status) - require.Equal(t, parcel.Address, stored.Address) + assert.Equal(t, parcel.Client, stored.Client) + assert.Equal(t, parcel.Status, stored.Status) + assert.Equal(t, parcel.Address, stored.Address) - // Delete err = store.Delete(id) require.NoError(t, err) - // Get again _, err = store.Get(id) require.Error(t, err) } @@ -75,43 +64,37 @@ func TestSetAddress(t *testing.T) { store := getTestStore(t) parcel := getTestParcel() - // Add id, err := store.Add(parcel) require.NoError(t, err) - // Set Address newAddress := "Updated Address" err = store.SetAddress(id, newAddress) require.NoError(t, err) - // Get stored, err := store.Get(id) require.NoError(t, err) - require.Equal(t, newAddress, stored.Address) + assert.Equal(t, newAddress, stored.Address) } func TestSetStatus(t *testing.T) { store := getTestStore(t) parcel := getTestParcel() - // Add id, err := store.Add(parcel) require.NoError(t, err) - // Set Status newStatus := ParcelStatusSent err = store.SetStatus(id, newStatus) require.NoError(t, err) - // Get stored, err := store.Get(id) require.NoError(t, err) - require.Equal(t, newStatus, stored.Status) + assert.Equal(t, newStatus, stored.Status) } func TestGetByClient(t *testing.T) { store := getTestStore(t) - clientID := randRange.Intn(1000000) + clientID := 42 parcels := []Parcel{ getTestParcel(), @@ -119,29 +102,23 @@ func TestGetByClient(t *testing.T) { getTestParcel(), } + parcelMap := map[int]Parcel{} for i := range parcels { parcels[i].Client = clientID id, err := store.Add(parcels[i]) require.NoError(t, err) parcels[i].Number = id + parcelMap[id] = parcels[i] } stored, err := store.GetByClient(clientID) require.NoError(t, err) require.Len(t, stored, len(parcels)) - // Проверим, что каждая посылка есть в ответе - for _, original := range parcels { - var found bool - for _, s := range stored { - if s.Number == original.Number { - require.Equal(t, original.Address, s.Address) - require.Equal(t, original.Status, s.Status) - require.Equal(t, original.Client, s.Client) - found = true - break - } - } - require.True(t, found, "Посылка %d не найдена", original.Number) + for _, s := range stored { + original := parcelMap[s.Number] + assert.Equal(t, original.Address, s.Address) + assert.Equal(t, original.Status, s.Status) + assert.Equal(t, original.Client, s.Client) } } From fa18d7541da052b71762a0019a266e6ecf243a97 Mon Sep 17 00:00:00 2001 From: "Bond, James Bond" Date: Mon, 2 Jun 2025 09:12:26 +0300 Subject: [PATCH 3/3] third commit --- parcel.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/parcel.go b/parcel.go index 07848fc7..66c9baf5 100644 --- a/parcel.go +++ b/parcel.go @@ -33,7 +33,7 @@ func (s ParcelStore) Add(p Parcel) (int, error) { func (s ParcelStore) Get(number int) (Parcel, error) { p := Parcel{} - err := s.db.QueryRow("select client, status, address, created_at from parcel where number = :number", + err := s.db.QueryRow("SELECT client, status, address, created_at FROM parcel WHERE number = :number", sql.Named("number", number)). Scan(&p.Client, &p.Status, &p.Address, &p.CreatedAt) if err != nil { @@ -47,7 +47,7 @@ func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { // реализуйте чтение строк из таблицы parcel по заданному client // здесь из таблицы может вернуться несколько строк var res []Parcel - rows, err := s.db.Query("select number, client, status, address, created_at from parcel where client = :client", sql.Named("client", 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 res, err } @@ -70,7 +70,7 @@ func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { func (s ParcelStore) SetStatus(number int, status string) error { // реализуйте обновление статуса в таблице parcel - _, err := s.db.Exec("update parcel set status = :status where number = :number", + _, err := s.db.Exec("UPDATE parcel SET status = :status WHERE number = :number", sql.Named("status", status), sql.Named("number", number)) if err != nil { @@ -82,9 +82,10 @@ func (s ParcelStore) SetStatus(number int, status string) error { func (s ParcelStore) SetAddress(number int, address string) error { // реализуйте обновление адреса в таблице parcel // менять адрес можно только если значение статуса registered - _, err := s.db.Exec("update parcel set address = :address where number = :number and status = 'registered'", + _, 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("number", number), + sql.Named("status", ParcelStatusRegistered)) if err != nil { return err } @@ -95,8 +96,9 @@ func (s ParcelStore) SetAddress(number int, address string) error { func (s ParcelStore) Delete(number int) error { // реализуйте удаление строки из таблицы parcel // удалять строку можно только если значение статуса registered - _, err := s.db.Exec("delete from parcel where number = :number and status = 'registered' ", - sql.Named("number", number)) + _, err := s.db.Exec("DELETE FROM parcel WHERE number = :number AND status = :status ", + sql.Named("number", number), + sql.Named("status", ParcelStatusRegistered)) if err != nil { return err }