diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..848bce1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tracker.db diff --git a/go.mod b/go.mod index 20eb2205..00733d9d 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/Yandex-Practicum/go-db-sql-final +module github.com/St-Ivanov/sprint8 go 1.21 diff --git a/main.go b/main.go index 44c32b3f..a73ece67 100644 --- a/main.go +++ b/main.go @@ -98,8 +98,12 @@ func (s ParcelService) Delete(number int) error { func main() { // настройте подключение к БД - - store := // создайте объект ParcelStore функцией NewParcelStore + db, err := sql.Open("sqlite", "tracker.db") + if err != nil { + return + } + defer db.Close() + store := NewParcelStore(db) // создайте объект ParcelStore функцией NewParcelStore service := NewParcelService(store) // регистрация посылки diff --git a/parcel.go b/parcel.go index db6c815d..ea970855 100644 --- a/parcel.go +++ b/parcel.go @@ -2,6 +2,12 @@ package main import ( "database/sql" + "errors" + "time" +) + +var ( + errStatusNoRegistered = errors.New("Попытка изменить объект. Статус нерегистрация.") ) type ParcelStore struct { @@ -14,47 +20,119 @@ func NewParcelStore(db *sql.DB) ParcelStore { func (s ParcelStore) Add(p Parcel) (int, error) { // реализуйте добавление строки в таблицу parcel, используйте данные из переменной p - + created_at := time.Now().UTC().Format(time.RFC3339) + data, err := s.db.Exec("INSERT INTO parcel (client, status, address, created_at) VALUES (:client, :status, :address, :created_at)", + sql.Named("client", p.Client), + sql.Named("status", p.Status), + sql.Named("address", p.Address), + sql.Named("created_at", created_at)) + if err != nil { + return 0, err + } + id, err := data.LastInsertId() // верните идентификатор последней добавленной записи - return 0, nil + if err != nil { + return 0, err + } + return int(id), nil } func (s ParcelStore) Get(number int) (Parcel, error) { // реализуйте чтение строки по заданному number // здесь из таблицы должна вернуться только одна строка - + row := s.db.QueryRow(` + SELECT number, client, status, address, created_at + FROM parcel + WHERE number = :number + `, + sql.Named("number", number), + ) // заполните объект Parcel данными из таблицы p := Parcel{} + err := row.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) - return p, nil + return p, err } func (s ParcelStore) GetByClient(client int) ([]Parcel, error) { // реализуйте чтение строк из таблицы parcel по заданному client // здесь из таблицы может вернуться несколько строк - - // заполните срез Parcel данными из таблицы var res []Parcel - - return res, nil + rows, err := s.db.Query(` + SELECT number, client, status, address, created_at + FROM parcel + WHERE client = :client + `, + sql.Named("client", client), + ) + // заполните срез Parcel данными из таблицы + if err != nil { + return res, err + } + defer rows.Close() + + for rows.Next() { + p := Parcel{} + + err := rows.Scan(&p.Number, &p.Client, &p.Status, &p.Address, &p.CreatedAt) + if err != nil { + return res, err + } + res = append(res, p) + } + err = rows.Err() + return res, err } func (s ParcelStore) SetStatus(number int, status string) error { // реализуйте обновление статуса в таблице parcel - - return nil + _, err := s.db.Exec(` + UPDATE parcel + SET status = :status + WHERE number = :number + `, + sql.Named("status", status), + sql.Named("number", number), + ) + return err } func (s ParcelStore) SetAddress(number int, address string) error { // реализуйте обновление адреса в таблице parcel // менять адрес можно только если значение статуса registered - - return nil + p, err := s.Get(number) + if err != nil { + return err + } + if p.Address == ParcelStatusSent || p.Address == ParcelStatusDelivered { + return errStatusNoRegistered + } + _, err = s.db.Exec(` + UPDATE parcel + SET address = :address + WHERE number = :number + `, + sql.Named("address", address), + sql.Named("number", number), + ) + return err } func (s ParcelStore) Delete(number int) error { // реализуйте удаление строки из таблицы parcel // удалять строку можно только если значение статуса registered - - return nil + p, err := s.Get(number) + if err != nil { + return err + } + if p.Address == ParcelStatusSent || p.Address == ParcelStatusDelivered { + return errStatusNoRegistered + } + _, err = s.db.Exec(` + DELETE FROM parcel + WHERE number = :number + `, + sql.Named("number", number), + ) + return err } diff --git a/parcel_test.go b/parcel_test.go index d1b93827..08f1f164 100644 --- a/parcel_test.go +++ b/parcel_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -31,57 +32,100 @@ func getTestParcel() Parcel { // TestAddGetDelete проверяет добавление, получение и удаление посылки func TestAddGetDelete(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") + require.NoError(t, err) + defer db.Close() + store := NewParcelStore(db) parcel := getTestParcel() // add // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - + id, err := store.Add(parcel) + parcel.Number = id + require.NoError(t, err) + assert.NotEqual(t, 0, id) // get // получите только что добавленную посылку, убедитесь в отсутствии ошибки // проверьте, что значения всех полей в полученном объекте совпадают со значениями полей в переменной parcel - + p, err := store.Get(id) + require.NoError(t, err) + assert.Equal(t, parcel, p) // delete // удалите добавленную посылку, убедитесь в отсутствии ошибки // проверьте, что посылку больше нельзя получить из БД + err = store.Delete(id) + require.NotErrorIs(t, err, errStatusNoRegistered) + + p, err = store.Get(id) + require.ErrorIs(t, err, sql.ErrNoRows) } // TestSetAddress проверяет обновление адреса func TestSetAddress(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") + require.NoError(t, err) + defer db.Close() + + store := NewParcelStore(db) + parcel := getTestParcel() // add // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - + id, err := store.Add(parcel) + require.NoError(t, err) + assert.NotEmpty(t, id) // set address // обновите адрес, убедитесь в отсутствии ошибки newAddress := "new test address" + err = store.SetAddress(id, newAddress) + + require.NoError(t, err) + // check // получите добавленную посылку и убедитесь, что адрес обновился + p, err := store.Get(id) + require.NoError(t, err) + assert.Equal(t, newAddress, p.Address) } // TestSetStatus проверяет обновление статуса func TestSetStatus(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") + require.NoError(t, err) + defer db.Close() + store := NewParcelStore(db) + parcel := getTestParcel() // add // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - + id, err := store.Add(parcel) + require.NoError(t, err) + assert.NotEmpty(t, id) // set status // обновите статус, убедитесь в отсутствии ошибки - + err = store.SetStatus(id, ParcelStatusSent) + require.NoError(t, err) // check // получите добавленную посылку и убедитесь, что статус обновился + p, err := store.Get(id) + require.NoError(t, err) + assert.Equal(t, ParcelStatusSent, p.Status) + } // TestGetByClient проверяет получение посылок по идентификатору клиента func TestGetByClient(t *testing.T) { // prepare - db, err := // настройте подключение к БД + db, err := sql.Open("sqlite", "tracker.db") + require.NoError(t, err) + defer db.Close() + + store := NewParcelStore(db) + parcel := getTestParcel() parcels := []Parcel{ getTestParcel(), @@ -98,8 +142,9 @@ func TestGetByClient(t *testing.T) { // add for i := 0; i < len(parcels); i++ { - id, err := // добавьте новую посылку в БД, убедитесь в отсутствии ошибки и наличии идентификатора - + id, err := store.Add(parcel) + require.NoError(t, err) + assert.NotEmpty(t, id) // обновляем идентификатор добавленной у посылки parcels[i].Number = id @@ -108,12 +153,20 @@ func TestGetByClient(t *testing.T) { } // get by client - storedParcels, err := // получите список посылок по идентификатору клиента, сохранённого в переменной client + storedParcels, err := store.GetByClient(client) // получите список посылок по идентификатору клиента, сохранённого в переменной client + require.NoError(t, err) // убедитесь в отсутствии ошибки // убедитесь, что количество полученных посылок совпадает с количеством добавленных // check for _, parcel := range storedParcels { + _, ok := parcelMap[parcel.Number] + assert.True(t, ok) + assert.NotEmpty(t, parcel.Number) + assert.NotEmpty(t, parcel.Client) + assert.NotEmpty(t, parcel.Status) + assert.NotEmpty(t, parcel.Address) + assert.NotEmpty(t, parcel.CreatedAt) // в parcelMap лежат добавленные посылки, ключ - идентификатор посылки, значение - сама посылка // убедитесь, что все посылки из storedParcels есть в parcelMap // убедитесь, что значения полей полученных посылок заполнены верно diff --git a/tracker.db b/tracker.db deleted file mode 100644 index b6ba48a1..00000000 Binary files a/tracker.db and /dev/null differ