almost finished database layer
This commit is contained in:
@@ -9,13 +9,6 @@ install:
|
||||
gen:
|
||||
mkdir -p gen/migrations
|
||||
go-bindata -pkg migrations -o gen/migrations/migrations.go -prefix "sql/migrations/" sql/migrations/...
|
||||
# mkdir -p gen/pb
|
||||
# protoc -I proto \
|
||||
# --go_out=gen/pb \
|
||||
# --go_opt=paths=source_relative \
|
||||
# --go-grpc_out=gen/pb \
|
||||
# --go-grpc_opt=paths=source_relative \
|
||||
# proto/jules.proto
|
||||
|
||||
fmt:
|
||||
gofumpt -w .
|
||||
|
||||
@@ -70,9 +70,9 @@ type Users interface {
|
||||
// Chats manages chat persistence.
|
||||
type Chats interface {
|
||||
Attach(ctx context.Context, userID uuid.UUID, platform, identifier string) error
|
||||
Detach(ctx context.Context, userID uuid.UUID, platform string) error
|
||||
GetUserID(ctx context.Context, platform, identifier string) (uuid.UUID, error)
|
||||
List(ctx context.Context, userID uuid.UUID) ([]Chat, error)
|
||||
Detach(ctx context.Context, userID uuid.UUID, platform string) error
|
||||
}
|
||||
|
||||
// Facts manages facts persistence.
|
||||
@@ -94,6 +94,8 @@ type Contacts interface {
|
||||
type Notifications interface {
|
||||
Push(ctx context.Context, n *Notification) error
|
||||
Pop(ctx context.Context, limit int) ([]Notification, error)
|
||||
List(ctx context.Context, userID uuid.UUID) ([]Notification, error)
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
}
|
||||
|
||||
// Actions manages the action log.
|
||||
|
||||
@@ -10,8 +10,12 @@ import (
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
func (db *DB) Attach(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
||||
_, err := db.conn.ExecContext(ctx, `
|
||||
type Chats struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func (c *Chats) Attach(ctx context.Context, userID uuid.UUID, platform, identifier string) error {
|
||||
_, err := c.conn.ExecContext(ctx, `
|
||||
INSERT INTO chats (user_id, platform, identifier)
|
||||
VALUES ($1, $2, $3)
|
||||
`, userID, platform, identifier)
|
||||
@@ -24,9 +28,9 @@ func (db *DB) Attach(ctx context.Context, userID uuid.UUID, platform, identifier
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetUserID(ctx context.Context, platform, identifier string) (uuid.UUID, error) {
|
||||
func (c *Chats) GetUserID(ctx context.Context, platform, identifier string) (uuid.UUID, error) {
|
||||
var userID uuid.UUID
|
||||
err := db.conn.QueryRowContext(ctx, `
|
||||
err := c.conn.QueryRowContext(ctx, `
|
||||
SELECT user_id
|
||||
FROM chats
|
||||
WHERE platform = $1 AND identifier = $2
|
||||
@@ -40,8 +44,8 @@ func (db *DB) GetUserID(ctx context.Context, platform, identifier string) (uuid.
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (db *DB) List(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
||||
rows, err := db.conn.QueryContext(ctx, `
|
||||
func (c *Chats) List(ctx context.Context, userID uuid.UUID) ([]database.Chat, error) {
|
||||
rows, err := c.conn.QueryContext(ctx, `
|
||||
SELECT user_id, platform, identifier
|
||||
FROM chats
|
||||
WHERE user_id = $1
|
||||
@@ -65,8 +69,8 @@ func (db *DB) List(ctx context.Context, userID uuid.UUID) ([]database.Chat, erro
|
||||
return chats, nil
|
||||
}
|
||||
|
||||
func (db *DB) Detach(ctx context.Context, userID uuid.UUID, platform string) error {
|
||||
result, err := db.conn.ExecContext(ctx, `
|
||||
func (c *Chats) Detach(ctx context.Context, userID uuid.UUID, platform string) error {
|
||||
result, err := c.conn.ExecContext(ctx, `
|
||||
DELETE FROM chats
|
||||
WHERE user_id = $1 AND platform = $2
|
||||
`, userID, platform)
|
||||
|
||||
@@ -9,28 +9,27 @@ import (
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChats_Attach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
require.NoError(t, err)
|
||||
err := db.Chats.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("already exists", func(t *testing.T) {
|
||||
err := db.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
err := db.Chats.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
assert.ErrorIs(t, err, database.ErrAlreadyExists)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db2 := setupTestDB(t)
|
||||
db2.Close()
|
||||
err := db2.Attach(context.Background(), user.ID, "whatsapp", "@test_attach")
|
||||
err := db2.Chats.Attach(context.Background(), user.ID, "whatsapp", "@test_attach")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrAlreadyExists)
|
||||
})
|
||||
@@ -38,28 +37,27 @@ func TestChats_Attach(t *testing.T) {
|
||||
|
||||
func TestChats_GetUserID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = db.Attach(context.Background(), user.ID, "telegram", "@test_get")
|
||||
require.NoError(t, err)
|
||||
err = db.Chats.Attach(context.Background(), user.ID, "telegram", "@test_get")
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("found", func(t *testing.T) {
|
||||
got, err := db.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
require.NoError(t, err)
|
||||
got, err := db.Chats.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, got)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
_, err := db.GetUserID(context.Background(), "telegram", "@notfound")
|
||||
_, err := db.Chats.GetUserID(context.Background(), "telegram", "@notfound")
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
// Создаём свежее соединение и закрываем его
|
||||
db2 := setupTestDB(t)
|
||||
db2.Close()
|
||||
_, err := db2.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
_, err := db2.Chats.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
@@ -67,51 +65,51 @@ func TestChats_GetUserID(t *testing.T) {
|
||||
|
||||
func TestChats_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Create(context.Background())
|
||||
db.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
db.Attach(context.Background(), user.ID, "email", "test@example.com")
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
db.Chats.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
db.Chats.Attach(context.Background(), user.ID, "email", "test@example.com")
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
chats, err := db.List(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
chats, err := db.Chats.List(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, chats, 2)
|
||||
})
|
||||
|
||||
t.Run("empty list", func(t *testing.T) {
|
||||
otherUser, _ := db.Create(context.Background())
|
||||
chats, err := db.List(context.Background(), otherUser.ID)
|
||||
require.NoError(t, err)
|
||||
otherUser, _ := db.Users.Create(context.Background())
|
||||
chats, err := db.Chats.List(context.Background(), otherUser.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, chats)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
_, err := db.List(context.Background(), user.ID)
|
||||
_, err := db.Chats.List(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChats_Detach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Create(context.Background())
|
||||
db.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
db.Chats.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Detach(context.Background(), user.ID, "telegram")
|
||||
require.NoError(t, err)
|
||||
err := db.Chats.Detach(context.Background(), user.ID, "telegram")
|
||||
assert.NoError(t, err)
|
||||
|
||||
chats, _ := db.List(context.Background(), user.ID)
|
||||
chats, _ := db.Chats.List(context.Background(), user.ID)
|
||||
assert.Empty(t, chats)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
err := db.Detach(context.Background(), user.ID, "telegram")
|
||||
err := db.Chats.Detach(context.Background(), user.ID, "telegram")
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
err := db.Detach(context.Background(), user.ID, "telegram")
|
||||
err := db.Chats.Detach(context.Background(), user.ID, "telegram")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
@@ -119,18 +117,18 @@ func TestChats_Detach(t *testing.T) {
|
||||
|
||||
func TestChats_List_ScanError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Create(context.Background())
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
|
||||
_, err := db.conn.ExecContext(context.Background(), `
|
||||
ALTER TABLE chats ALTER COLUMN identifier DROP NOT NULL
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = db.conn.ExecContext(context.Background(), `
|
||||
INSERT INTO chats (user_id, platform, identifier)
|
||||
VALUES ($1, 'telegram', NULL)
|
||||
`, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
db.conn.ExecContext(context.Background(), `
|
||||
@@ -141,16 +139,16 @@ func TestChats_List_ScanError(t *testing.T) {
|
||||
`)
|
||||
})
|
||||
|
||||
_, err = db.List(context.Background(), user.ID)
|
||||
_, err = db.Chats.List(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestChats_List_RowsErr(t *testing.T) {
|
||||
conn, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
db := &DB{conn: conn}
|
||||
db := &Chats{conn: conn}
|
||||
userID := uuid.New()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"user_id", "platform", "identifier"}).
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type Contacts struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func (c *Contacts) Add(ctx context.Context, contact *database.Contact) error {
|
||||
_, err := c.conn.ExecContext(ctx, `
|
||||
INSERT INTO contacts (owner_id, target_id, name)
|
||||
VALUES ($1, $2, $3)
|
||||
`, contact.OwnerID, contact.TargetID, contact.Name)
|
||||
if err != nil {
|
||||
var pqErr *pq.Error
|
||||
if errors.As(err, &pqErr) && pqErr.Code == "23505" {
|
||||
return database.ErrAlreadyExists
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Contacts) Get(ctx context.Context, ownerID uuid.UUID, name string) (*database.Contact, error) {
|
||||
var contact database.Contact
|
||||
err := c.conn.QueryRowContext(ctx, `
|
||||
SELECT owner_id, target_id, name
|
||||
FROM contacts
|
||||
WHERE owner_id = $1 AND name = $2
|
||||
`, ownerID, name).Scan(&contact.OwnerID, &contact.TargetID, &contact.Name)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, database.ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &contact, nil
|
||||
}
|
||||
|
||||
func (c *Contacts) List(ctx context.Context, ownerID uuid.UUID) ([]database.Contact, error) {
|
||||
rows, err := c.conn.QueryContext(ctx, `
|
||||
SELECT owner_id, target_id, name
|
||||
FROM contacts
|
||||
WHERE owner_id = $1
|
||||
`, ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var contacts []database.Contact
|
||||
for rows.Next() {
|
||||
var contact database.Contact
|
||||
if err := rows.Scan(&contact.OwnerID, &contact.TargetID, &contact.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contacts = append(contacts, contact)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contacts, nil
|
||||
}
|
||||
|
||||
func (c *Contacts) Delete(ctx context.Context, ownerID uuid.UUID, name string) error {
|
||||
result, err := c.conn.ExecContext(ctx, `
|
||||
DELETE FROM contacts
|
||||
WHERE owner_id = $1 AND name = $2
|
||||
`, ownerID, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return database.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContacts_Add(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
owner, _ := db.Users.Create(context.Background())
|
||||
target, _ := db.Users.Create(context.Background())
|
||||
|
||||
contact := &database.Contact{
|
||||
OwnerID: owner.ID,
|
||||
TargetID: target.ID,
|
||||
Name: "брат",
|
||||
}
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Contacts.Add(context.Background(), contact)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("already exists", func(t *testing.T) {
|
||||
err := db.Contacts.Add(context.Background(), contact)
|
||||
assert.ErrorIs(t, err, database.ErrAlreadyExists)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
err := db.Contacts.Add(context.Background(), contact)
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrAlreadyExists)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContacts_Get(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
owner, _ := db.Users.Create(context.Background())
|
||||
target, _ := db.Users.Create(context.Background())
|
||||
|
||||
contact := &database.Contact{
|
||||
OwnerID: owner.ID,
|
||||
TargetID: target.ID,
|
||||
Name: "брат",
|
||||
}
|
||||
db.Contacts.Add(context.Background(), contact)
|
||||
|
||||
t.Run("found", func(t *testing.T) {
|
||||
got, err := db.Contacts.Get(context.Background(), owner.ID, "брат")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, owner.ID, got.OwnerID)
|
||||
assert.Equal(t, target.ID, got.TargetID)
|
||||
assert.Equal(t, "брат", got.Name)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
_, err := db.Contacts.Get(context.Background(), owner.ID, "сестра")
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
_, err := db.Contacts.Get(context.Background(), owner.ID, "брат")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContacts_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
owner, _ := db.Users.Create(context.Background())
|
||||
target1, _ := db.Users.Create(context.Background())
|
||||
target2, _ := db.Users.Create(context.Background())
|
||||
|
||||
db.Contacts.Add(context.Background(), &database.Contact{OwnerID: owner.ID, TargetID: target1.ID, Name: "брат"})
|
||||
db.Contacts.Add(context.Background(), &database.Contact{OwnerID: owner.ID, TargetID: target2.ID, Name: "друг"})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
contacts, err := db.Contacts.List(context.Background(), owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, contacts, 2)
|
||||
})
|
||||
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
other, _ := db.Users.Create(context.Background())
|
||||
contacts, err := db.Contacts.List(context.Background(), other.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, contacts)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
_, err := db.Contacts.List(context.Background(), owner.ID)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContacts_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
owner, _ := db.Users.Create(context.Background())
|
||||
target, _ := db.Users.Create(context.Background())
|
||||
|
||||
db.Contacts.Add(context.Background(), &database.Contact{OwnerID: owner.ID, TargetID: target.ID, Name: "брат"})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Contacts.Delete(context.Background(), owner.ID, "брат")
|
||||
require.NoError(t, err)
|
||||
|
||||
contacts, _ := db.Contacts.List(context.Background(), owner.ID)
|
||||
assert.Empty(t, contacts)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
err := db.Contacts.Delete(context.Background(), owner.ID, "брат")
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
err := db.Contacts.Delete(context.Background(), owner.ID, "брат")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestContacts_List_ScanError(t *testing.T) {
|
||||
conn, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
db := &Contacts{conn: conn}
|
||||
ownerID := uuid.New()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"owner_id", "target_id", "name"}).
|
||||
AddRow(uuid.New(), uuid.New(), nil) // NULL в name
|
||||
|
||||
mock.ExpectQuery(`SELECT owner_id, target_id, name FROM contacts WHERE owner_id = \$1`).
|
||||
WithArgs(ownerID).
|
||||
WillReturnRows(rows)
|
||||
|
||||
_, err = db.List(context.Background(), ownerID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestContacts_List_RowsErr(t *testing.T) {
|
||||
conn, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
db := &Contacts{conn: conn}
|
||||
ownerID := uuid.New()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"owner_id", "target_id", "name"}).
|
||||
AddRow(uuid.New(), uuid.New(), "брат").
|
||||
RowError(0, errors.New("connection lost"))
|
||||
|
||||
mock.ExpectQuery(`SELECT owner_id, target_id, name FROM contacts WHERE owner_id = \$1`).
|
||||
WithArgs(ownerID).
|
||||
WillReturnRows(rows)
|
||||
|
||||
_, err = db.List(context.Background(), ownerID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Facts struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func (f *Facts) Add(ctx context.Context, userID uuid.UUID, value string) error {
|
||||
_, err := f.conn.ExecContext(ctx, `
|
||||
INSERT INTO facts (user_id, value)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (user_id, value) DO NOTHING
|
||||
`, userID, value)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *Facts) List(ctx context.Context, userID uuid.UUID) ([]database.Fact, error) {
|
||||
rows, err := f.conn.QueryContext(ctx, `
|
||||
SELECT user_id, value
|
||||
FROM facts
|
||||
WHERE user_id = $1
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var facts []database.Fact
|
||||
for rows.Next() {
|
||||
var fact database.Fact
|
||||
if err := rows.Scan(&fact.UserID, &fact.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
facts = append(facts, fact)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return facts, nil
|
||||
}
|
||||
|
||||
func (f *Facts) Delete(ctx context.Context, userID uuid.UUID, value string) error {
|
||||
result, err := f.conn.ExecContext(ctx, `
|
||||
DELETE FROM facts
|
||||
WHERE user_id = $1 AND value = $2
|
||||
`, userID, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return database.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFacts_Add(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
|
||||
err := db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFacts_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
|
||||
db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
db.Facts.Add(context.Background(), user.ID, "спит в 23:30")
|
||||
|
||||
facts, err := db.Facts.List(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, facts, 2)
|
||||
}
|
||||
|
||||
func TestFacts_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
|
||||
db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
|
||||
err := db.Facts.Delete(context.Background(), user.ID, "мама Ирина")
|
||||
require.NoError(t, err)
|
||||
|
||||
facts, _ := db.Facts.List(context.Background(), user.ID)
|
||||
assert.Empty(t, facts)
|
||||
|
||||
err = db.Facts.Delete(context.Background(), user.ID, "мама Ирина")
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
func TestFacts_List_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
db.Facts.Add(context.Background(), user.ID, "test")
|
||||
|
||||
db.Close()
|
||||
_, err := db.Facts.List(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFacts_Delete_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
db.Facts.Add(context.Background(), user.ID, "test")
|
||||
|
||||
db.Close()
|
||||
err := db.Facts.Delete(context.Background(), user.ID, "test")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
func TestFacts_List_RowsErr(t *testing.T) {
|
||||
conn, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
db := &Facts{conn: conn}
|
||||
userID := uuid.New()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"user_id", "value"}).
|
||||
AddRow(uuid.New(), "test fact").
|
||||
RowError(0, errors.New("connection lost"))
|
||||
|
||||
mock.ExpectQuery(`SELECT user_id, value FROM facts WHERE user_id = \$1`).
|
||||
WithArgs(userID).
|
||||
WillReturnRows(rows)
|
||||
|
||||
_, err = db.List(context.Background(), userID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFacts_List_ScanError(t *testing.T) {
|
||||
conn, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
db := &Facts{conn: conn}
|
||||
userID := uuid.New()
|
||||
|
||||
rows := sqlmock.NewRows([]string{"user_id", "value"}).
|
||||
AddRow(uuid.New(), nil)
|
||||
|
||||
mock.ExpectQuery(`SELECT user_id, value FROM facts WHERE user_id = \$1`).
|
||||
WithArgs(userID).
|
||||
WillReturnRows(rows)
|
||||
|
||||
_, err = db.List(context.Background(), userID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getTestConnString() string {
|
||||
@@ -22,10 +21,10 @@ func getTestConnString() string {
|
||||
|
||||
func getTestConn(t *testing.T) *sql.DB {
|
||||
conn, err := sql.Open("postgres", getTestConnString())
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
require.NoError(t, conn.PingContext(ctx))
|
||||
assert.NoError(t, conn.PingContext(ctx))
|
||||
|
||||
t.Cleanup(func() {
|
||||
cleanTables(t, conn)
|
||||
@@ -67,7 +66,7 @@ func TestRunMigrations_AlreadyApplied(t *testing.T) {
|
||||
dropSchema(t, conn)
|
||||
|
||||
err := runMigrations(conn)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = runMigrations(conn)
|
||||
assert.NoError(t, err)
|
||||
@@ -75,7 +74,7 @@ func TestRunMigrations_AlreadyApplied(t *testing.T) {
|
||||
|
||||
func TestRunMigrations_InvalidConn(t *testing.T) {
|
||||
conn, err := sql.Open("postgres", "postgres://invalid:5432/db")
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
err = runMigrations(conn)
|
||||
@@ -87,7 +86,7 @@ func TestRunMigrations_FailedCreateIOFSDriver(t *testing.T) {
|
||||
migrationsFS = embed.FS{}
|
||||
|
||||
conn, err := sql.Open("postgres", "postgres://invalid:5432/db")
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
err = runMigrations(conn)
|
||||
@@ -102,7 +101,7 @@ func TestRunMigrations_FailedUp(t *testing.T) {
|
||||
dropSchema(t, conn)
|
||||
|
||||
_, err := conn.ExecContext(context.Background(), `CREATE TABLE users ()`)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = runMigrations(conn)
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Notifications struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func (n *Notifications) Push(ctx context.Context, notif *database.Notification) error {
|
||||
_, err := n.conn.ExecContext(ctx, `
|
||||
INSERT INTO notifications (id, user_id, scheduled_at, content)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, notif.ID, notif.UserID, notif.ScheduledAt, notif.Content)
|
||||
return err
|
||||
}
|
||||
|
||||
func (n *Notifications) Pop(ctx context.Context, limit int) ([]database.Notification, error) {
|
||||
now := time.Now().UTC().Truncate(time.Minute)
|
||||
nextMinute := now.Add(time.Minute)
|
||||
|
||||
rows, err := n.conn.QueryContext(ctx, `
|
||||
WITH batch AS (
|
||||
SELECT id
|
||||
FROM notifications
|
||||
WHERE scheduled_at >= $1 AND scheduled_at < $2
|
||||
ORDER BY scheduled_at
|
||||
LIMIT $3
|
||||
FOR UPDATE SKIP LOCKED
|
||||
)
|
||||
DELETE FROM notifications
|
||||
WHERE id IN (SELECT id FROM batch)
|
||||
RETURNING id, user_id, scheduled_at, content
|
||||
`, now, nextMinute, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var notifs []database.Notification
|
||||
for rows.Next() {
|
||||
var n database.Notification
|
||||
if err := rows.Scan(&n.ID, &n.UserID, &n.ScheduledAt, &n.Content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notifs = append(notifs, n)
|
||||
}
|
||||
return notifs, rows.Err()
|
||||
}
|
||||
|
||||
func (n *Notifications) List(ctx context.Context, userID uuid.UUID) ([]database.Notification, error) {
|
||||
rows, err := n.conn.QueryContext(ctx, `
|
||||
SELECT id, user_id, scheduled_at, content
|
||||
FROM notifications
|
||||
WHERE user_id = $1
|
||||
ORDER BY scheduled_at
|
||||
`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var notifs []database.Notification
|
||||
for rows.Next() {
|
||||
var notif database.Notification
|
||||
if err := rows.Scan(¬if.ID, ¬if.UserID, ¬if.ScheduledAt, ¬if.Content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notifs = append(notifs, notif)
|
||||
}
|
||||
return notifs, rows.Err()
|
||||
}
|
||||
|
||||
func (n *Notifications) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
result, err := n.conn.ExecContext(ctx, `
|
||||
DELETE FROM notifications WHERE id = $1
|
||||
`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
if rows == 0 {
|
||||
return database.ErrNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNotifications_ConcurrentPop(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now().UTC().Truncate(time.Minute)
|
||||
|
||||
for range 1000 {
|
||||
err := db.Notifications.Push(ctx, &database.Notification{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
ScheduledAt: now,
|
||||
Content: "test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
popped := make(chan uuid.UUID, 1000)
|
||||
|
||||
for range 10 {
|
||||
wg.Go(func() {
|
||||
for {
|
||||
notifs, err := db.Notifications.Pop(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
if len(notifs) == 0 {
|
||||
return
|
||||
}
|
||||
for _, n := range notifs {
|
||||
popped <- n.ID
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(popped)
|
||||
|
||||
ids := make(map[uuid.UUID]bool)
|
||||
for id := range popped {
|
||||
ids[id] = true
|
||||
}
|
||||
assert.Len(t, ids, 1000)
|
||||
|
||||
remaining, err := db.Notifications.Pop(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remaining)
|
||||
}
|
||||
|
||||
func TestNotifications_Pop_QueryError(t *testing.T) {
|
||||
conn, mock, _ := sqlmock.New()
|
||||
defer conn.Close()
|
||||
db := &Notifications{conn: conn}
|
||||
mock.ExpectQuery(`WITH batch AS`).WillReturnError(errors.New("db down"))
|
||||
_, err := db.Pop(context.Background(), 10)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotifications_Pop_ScanError(t *testing.T) {
|
||||
conn, mock, _ := sqlmock.New()
|
||||
defer conn.Close()
|
||||
db := &Notifications{conn: conn}
|
||||
rows := sqlmock.NewRows([]string{"id", "user_id", "scheduled_at", "content"}).
|
||||
AddRow(uuid.New(), uuid.New(), time.Now(), nil)
|
||||
mock.ExpectQuery(`WITH batch AS`).WillReturnRows(rows)
|
||||
_, err := db.Pop(context.Background(), 10)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotifications_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
now := time.Now().UTC().Truncate(time.Minute)
|
||||
|
||||
n1 := &database.Notification{ID: uuid.New(), UserID: user.ID, ScheduledAt: now, Content: "first"}
|
||||
n2 := &database.Notification{ID: uuid.New(), UserID: user.ID, ScheduledAt: now.Add(time.Hour), Content: "second"}
|
||||
|
||||
db.Notifications.Push(context.Background(), n1)
|
||||
db.Notifications.Push(context.Background(), n2)
|
||||
|
||||
notifs, err := db.Notifications.List(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, notifs, 2)
|
||||
|
||||
other, _ := db.Users.Create(context.Background())
|
||||
notifs, err = db.Notifications.List(context.Background(), other.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, notifs)
|
||||
}
|
||||
|
||||
func TestNotifications_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
|
||||
n := &database.Notification{ID: uuid.New(), UserID: user.ID, ScheduledAt: time.Now().UTC(), Content: "test"}
|
||||
db.Notifications.Push(context.Background(), n)
|
||||
|
||||
err := db.Notifications.Delete(context.Background(), n.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Notifications.Delete(context.Background(), n.ID)
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
func TestNotifications_List_QueryError(t *testing.T) {
|
||||
conn, mock, _ := sqlmock.New()
|
||||
defer conn.Close()
|
||||
db := &Notifications{conn: conn}
|
||||
mock.ExpectQuery(`SELECT id, user_id, scheduled_at, content FROM notifications`).WillReturnError(errors.New("db down"))
|
||||
_, err := db.List(context.Background(), uuid.New())
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotifications_List_ScanError(t *testing.T) {
|
||||
conn, mock, _ := sqlmock.New()
|
||||
defer conn.Close()
|
||||
db := &Notifications{conn: conn}
|
||||
rows := sqlmock.NewRows([]string{"id", "user_id", "scheduled_at", "content"}).
|
||||
AddRow(uuid.New(), uuid.New(), time.Now(), nil)
|
||||
mock.ExpectQuery(`SELECT id, user_id, scheduled_at, content FROM notifications`).WillReturnRows(rows)
|
||||
_, err := db.List(context.Background(), uuid.New())
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotifications_Delete_Error(t *testing.T) {
|
||||
conn, mock, _ := sqlmock.New()
|
||||
defer conn.Close()
|
||||
db := &Notifications{conn: conn}
|
||||
mock.ExpectExec(`DELETE FROM notifications`).WillReturnError(errors.New("db down"))
|
||||
err := db.Delete(context.Background(), uuid.New())
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -10,6 +10,12 @@ import (
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
Users Users
|
||||
Chats Chats
|
||||
Facts Facts
|
||||
Contacts Contacts
|
||||
Notifications Notifications
|
||||
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
@@ -32,7 +38,15 @@ func New(connString string) (*DB, error) {
|
||||
return nil, fmt.Errorf("run migrations: %w", err)
|
||||
}
|
||||
|
||||
return &DB{conn: conn}, nil
|
||||
return &DB{
|
||||
Users: Users{conn: conn},
|
||||
Chats: Chats{conn: conn},
|
||||
Facts: Facts{conn: conn},
|
||||
Contacts: Contacts{conn: conn},
|
||||
Notifications: Notifications{conn: conn},
|
||||
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupTestDB(t *testing.T) *DB {
|
||||
@@ -20,7 +19,7 @@ func TestNew_Success(t *testing.T) {
|
||||
connString := getTestConnString()
|
||||
db, err := New(connString)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db)
|
||||
defer db.Close()
|
||||
}
|
||||
@@ -36,7 +35,7 @@ func TestNew_RunMigrationsFailed(t *testing.T) {
|
||||
dropSchema(t, conn)
|
||||
|
||||
_, err := conn.ExecContext(context.Background(), `CREATE TABLE users ()`)
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = New(getTestConnString())
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -9,9 +9,13 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (db *DB) Create(ctx context.Context) (*database.User, error) {
|
||||
type Users struct {
|
||||
conn *sql.DB
|
||||
}
|
||||
|
||||
func (u *Users) Create(ctx context.Context) (*database.User, error) {
|
||||
var user database.User
|
||||
err := db.conn.QueryRowContext(ctx, `
|
||||
err := u.conn.QueryRowContext(ctx, `
|
||||
INSERT INTO users (preferred_chat, language, timezone)
|
||||
VALUES ('telegram', 'en', 'UTC')
|
||||
RETURNING id, preferred_chat, language, timezone
|
||||
@@ -22,9 +26,9 @@ func (db *DB) Create(ctx context.Context) (*database.User, error) {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *DB) Get(ctx context.Context, id uuid.UUID) (*database.User, error) {
|
||||
func (u *Users) Get(ctx context.Context, id uuid.UUID) (*database.User, error) {
|
||||
var user database.User
|
||||
err := db.conn.QueryRowContext(ctx, `
|
||||
err := u.conn.QueryRowContext(ctx, `
|
||||
SELECT id, preferred_chat, language, timezone
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
@@ -38,8 +42,8 @@ func (db *DB) Get(ctx context.Context, id uuid.UUID) (*database.User, error) {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *DB) Update(ctx context.Context, user *database.User) error {
|
||||
result, err := db.conn.ExecContext(ctx, `
|
||||
func (u *Users) Update(ctx context.Context, user *database.User) error {
|
||||
result, err := u.conn.ExecContext(ctx, `
|
||||
UPDATE users
|
||||
SET preferred_chat = $1, language = $2, timezone = $3
|
||||
WHERE id = $4
|
||||
@@ -54,8 +58,8 @@ func (db *DB) Update(ctx context.Context, user *database.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
result, err := db.conn.ExecContext(ctx, `DELETE FROM users WHERE id = $1`, id)
|
||||
func (u *Users) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
result, err := u.conn.ExecContext(ctx, `DELETE FROM users WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,14 +7,13 @@ import (
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUsers_Create(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, uuid.Nil, user.ID)
|
||||
assert.Equal(t, "telegram", user.PreferredChat)
|
||||
assert.Equal(t, "en", user.Language)
|
||||
@@ -24,18 +23,18 @@ func TestUsers_Create(t *testing.T) {
|
||||
func TestUsers_Get(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("found", func(t *testing.T) {
|
||||
fetched, err := db.Get(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
fetched, err := db.Users.Get(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, fetched.ID)
|
||||
assert.Equal(t, user.PreferredChat, fetched.PreferredChat)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
_, err := db.Get(context.Background(), uuid.New())
|
||||
_, err := db.Users.Get(context.Background(), uuid.New())
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
@@ -43,19 +42,19 @@ func TestUsers_Get(t *testing.T) {
|
||||
func TestUsers_Update(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
user.PreferredChat = "whatsapp"
|
||||
user.Language = "ru"
|
||||
user.Timezone = "Europe/Moscow"
|
||||
|
||||
err := db.Update(context.Background(), user)
|
||||
require.NoError(t, err)
|
||||
err := db.Users.Update(context.Background(), user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fetched, err := db.Get(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
fetched, err := db.Users.Get(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "whatsapp", fetched.PreferredChat)
|
||||
assert.Equal(t, "ru", fetched.Language)
|
||||
assert.Equal(t, "Europe/Moscow", fetched.Timezone)
|
||||
@@ -63,7 +62,7 @@ func TestUsers_Update(t *testing.T) {
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
ghost := &database.User{ID: uuid.New()}
|
||||
err := db.Update(context.Background(), ghost)
|
||||
err := db.Users.Update(context.Background(), ghost)
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
@@ -72,18 +71,18 @@ func TestUsers_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = db.Delete(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
err = db.Users.Delete(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = db.Get(context.Background(), user.ID)
|
||||
_, err = db.Users.Get(context.Background(), user.ID)
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
err := db.Delete(context.Background(), uuid.New())
|
||||
err := db.Users.Delete(context.Background(), uuid.New())
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
@@ -93,7 +92,7 @@ func TestUsers_Create_DatabaseError(t *testing.T) {
|
||||
|
||||
db.Close()
|
||||
|
||||
_, err := db.Create(context.Background())
|
||||
_, err := db.Users.Create(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -103,7 +102,7 @@ func TestUsers_Get_DatabaseError(t *testing.T) {
|
||||
|
||||
db.Close()
|
||||
|
||||
_, err := db.Get(context.Background(), uuid.New())
|
||||
_, err := db.Users.Get(context.Background(), uuid.New())
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -111,12 +110,12 @@ func TestUsers_Get_DatabaseError(t *testing.T) {
|
||||
func TestUsers_Update_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
db.Close()
|
||||
|
||||
err = db.Update(context.Background(), user)
|
||||
err = db.Users.Update(context.Background(), user)
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -124,12 +123,12 @@ func TestUsers_Update_DatabaseError(t *testing.T) {
|
||||
func TestUsers_Delete_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Create(context.Background())
|
||||
require.NoError(t, err)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
db.Close()
|
||||
|
||||
err = db.Delete(context.Background(), user.ID)
|
||||
err = db.Users.Delete(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
@@ -30,7 +29,7 @@ func TestProcess_Success(t *testing.T) {
|
||||
|
||||
client := New("key", server.URL)
|
||||
result, err := client.Process(context.Background(), "prompt")
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "response", result)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
@@ -50,7 +49,7 @@ func TestSearch_Success_WithSummarizer(t *testing.T) {
|
||||
|
||||
client := New("key", server.URL)
|
||||
result, err := client.Search(context.Background(), "query")
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "summary text", result)
|
||||
}
|
||||
|
||||
@@ -66,7 +65,7 @@ func TestSearch_Success_NoSummarizer_FallbackToDescription(t *testing.T) {
|
||||
|
||||
client := New("key", server.URL)
|
||||
result, err := client.Search(context.Background(), "query")
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "fallback description", result)
|
||||
}
|
||||
|
||||
@@ -176,7 +175,7 @@ func TestSearch_DoError_WebSearch(t *testing.T) {
|
||||
})
|
||||
|
||||
_, err := client.Search(context.Background(), "query")
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "web search network error")
|
||||
}
|
||||
|
||||
@@ -204,6 +203,6 @@ func TestSearch_DoError_Summarizer(t *testing.T) {
|
||||
})
|
||||
|
||||
_, err := client.Search(context.Background(), "query")
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "summarizer network error")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user