additional refactoring for good interface composition alternative
This commit is contained in:
+58
-48
@@ -9,54 +9,14 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("entity not found")
|
||||
ErrAlreadyExists = errors.New("entity already exists")
|
||||
)
|
||||
|
||||
// User represents a Jules user.
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
Language string
|
||||
Timezone string
|
||||
PreferredChat string
|
||||
}
|
||||
|
||||
// Chat links a user to an external messaging platform.
|
||||
type Chat struct {
|
||||
UserID uuid.UUID
|
||||
Platform string // "telegram", "email", "whatsapp"
|
||||
Identifier string // @username, email, phone
|
||||
}
|
||||
|
||||
// Fact stores fact Jules knows about a user.
|
||||
type Fact struct {
|
||||
UserID uuid.UUID
|
||||
Value string
|
||||
}
|
||||
|
||||
// Contact represents a relationship between two Jules users.
|
||||
type Contact struct {
|
||||
OwnerID uuid.UUID // User who owns this contact
|
||||
TargetID uuid.UUID // Target user ID
|
||||
Name string // "mom", "brother", "Lena"
|
||||
}
|
||||
|
||||
// Notification is a scheduled reminder or check-in.
|
||||
type Notification struct {
|
||||
ID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
InitiatorID uuid.UUID
|
||||
ScheduledAt time.Time
|
||||
Content string // "call mom", "morning workout", "make a compliment", "talk to random person"
|
||||
}
|
||||
|
||||
// Action records an interaction between Jules and a user.
|
||||
type Action struct {
|
||||
OwnerID uuid.UUID
|
||||
InitiatorID uuid.UUID
|
||||
ExecutedAt time.Time
|
||||
Payload json.RawMessage
|
||||
type Database interface {
|
||||
Users() Users
|
||||
Chats() Chats
|
||||
Facts() Facts
|
||||
Contacts() Contacts
|
||||
Notifications() Notifications
|
||||
Actions() Actions
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Users manages user persistence.
|
||||
@@ -104,3 +64,53 @@ type Actions interface {
|
||||
Recent(ctx context.Context, ownerID uuid.UUID, limit int) ([]Action, error)
|
||||
DeleteOlderThan(ctx context.Context, ownerID uuid.UUID, t time.Time) (int64, error)
|
||||
}
|
||||
|
||||
// User represents a Jules user.
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
Language string
|
||||
Timezone string
|
||||
PreferredChat string
|
||||
}
|
||||
|
||||
// Chat links a user to an external messaging platform.
|
||||
type Chat struct {
|
||||
UserID uuid.UUID
|
||||
Platform string // "telegram", "email", "whatsapp"
|
||||
Identifier string // @username, email, phone
|
||||
}
|
||||
|
||||
// Fact stores fact Jules knows about a user.
|
||||
type Fact struct {
|
||||
UserID uuid.UUID
|
||||
Value string
|
||||
}
|
||||
|
||||
// Contact represents a relationship between two Jules users.
|
||||
type Contact struct {
|
||||
OwnerID uuid.UUID // User who owns this contact
|
||||
TargetID uuid.UUID // Target user ID
|
||||
Name string // "mom", "brother", "Lena"
|
||||
}
|
||||
|
||||
// Notification is a scheduled reminder or check-in.
|
||||
type Notification struct {
|
||||
ID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
InitiatorID uuid.UUID
|
||||
ScheduledAt time.Time
|
||||
Content string // "call mom", "morning workout", "make a compliment", "talk to random person"
|
||||
}
|
||||
|
||||
// Action records an interaction between Jules and a user.
|
||||
type Action struct {
|
||||
OwnerID uuid.UUID
|
||||
InitiatorID uuid.UUID
|
||||
ExecutedAt time.Time
|
||||
Payload json.RawMessage
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("entity not found")
|
||||
ErrAlreadyExists = errors.New("entity already exists")
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func TestActions_Log(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
payload, _ := json.Marshal(map[string]any{"type": "user_msg", "text": "hello"})
|
||||
|
||||
@@ -27,10 +27,10 @@ func TestActions_Log(t *testing.T) {
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
err := db.Actions.Log(context.Background(), action)
|
||||
err := db.ActionsRepo.Log(context.Background(), action)
|
||||
require.NoError(t, err)
|
||||
|
||||
actions, err := db.Actions.Recent(context.Background(), user.ID, 10)
|
||||
actions, err := db.ActionsRepo.Recent(context.Background(), user.ID, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, actions, 1)
|
||||
assert.Equal(t, user.ID, actions[0].OwnerID)
|
||||
@@ -40,9 +40,9 @@ func TestActions_Log(t *testing.T) {
|
||||
|
||||
func TestActions_Log_NilPayload(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
err := db.Actions.Log(context.Background(), &database.Action{
|
||||
err := db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
@@ -54,9 +54,9 @@ func TestActions_Log_NilPayload(t *testing.T) {
|
||||
|
||||
func TestActions_Log_InvalidJSON(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
err := db.Actions.Log(context.Background(), &database.Action{
|
||||
err := db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
@@ -67,62 +67,62 @@ func TestActions_Log_InvalidJSON(t *testing.T) {
|
||||
|
||||
func TestActions_Recent(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
payload1, _ := json.Marshal(map[string]any{"type": "user_msg", "text": "first"})
|
||||
payload2, _ := json.Marshal(map[string]any{"type": "user_msg", "text": "second"})
|
||||
|
||||
db.Actions.Log(context.Background(), &database.Action{
|
||||
db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC().Add(-time.Hour),
|
||||
Payload: payload1,
|
||||
})
|
||||
db.Actions.Log(context.Background(), &database.Action{
|
||||
db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
Payload: payload2,
|
||||
})
|
||||
|
||||
actions, err := db.Actions.Recent(context.Background(), user.ID, 10)
|
||||
actions, err := db.ActionsRepo.Recent(context.Background(), user.ID, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, actions, 2)
|
||||
assert.JSONEq(t, string(payload2), string(actions[0].Payload))
|
||||
assert.JSONEq(t, string(payload1), string(actions[1].Payload))
|
||||
|
||||
actions, err = db.Actions.Recent(context.Background(), user.ID, 1)
|
||||
actions, err = db.ActionsRepo.Recent(context.Background(), user.ID, 1)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, actions, 1)
|
||||
}
|
||||
|
||||
func TestActions_DeleteOlderThan(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
oldTime := time.Now().UTC().Add(-48 * time.Hour).Truncate(time.Second)
|
||||
newTime := time.Now().UTC().Truncate(time.Second)
|
||||
|
||||
payload, _ := json.Marshal(map[string]any{"type": "user_msg", "text": "test"})
|
||||
|
||||
db.Actions.Log(context.Background(), &database.Action{
|
||||
db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: oldTime,
|
||||
Payload: payload,
|
||||
})
|
||||
db.Actions.Log(context.Background(), &database.Action{
|
||||
db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: newTime,
|
||||
Payload: payload,
|
||||
})
|
||||
|
||||
deleted, err := db.Actions.DeleteOlderThan(context.Background(), user.ID, time.Now().UTC().Add(-24*time.Hour))
|
||||
deleted, err := db.ActionsRepo.DeleteOlderThan(context.Background(), user.ID, time.Now().UTC().Add(-24*time.Hour))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), deleted)
|
||||
|
||||
actions, _ := db.Actions.Recent(context.Background(), user.ID, 10)
|
||||
actions, _ := db.ActionsRepo.Recent(context.Background(), user.ID, 10)
|
||||
assert.Len(t, actions, 1)
|
||||
assert.WithinDuration(t, newTime, actions[0].ExecutedAt, time.Second)
|
||||
}
|
||||
@@ -168,12 +168,12 @@ func TestActions_DeleteOlderThan_Error(t *testing.T) {
|
||||
|
||||
func TestActions_Log_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
db.Close()
|
||||
|
||||
payload, _ := json.Marshal(map[string]any{"type": "user_msg"})
|
||||
err := db.Actions.Log(context.Background(), &database.Action{
|
||||
err := db.ActionsRepo.Log(context.Background(), &database.Action{
|
||||
OwnerID: user.ID,
|
||||
InitiatorID: user.ID,
|
||||
ExecutedAt: time.Now().UTC(),
|
||||
|
||||
@@ -13,23 +13,23 @@ import (
|
||||
|
||||
func TestChats_Attach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Chats.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
err := db.ChatsRepo.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("already exists", func(t *testing.T) {
|
||||
err := db.Chats.Attach(context.Background(), user.ID, "telegram", "@test_attach")
|
||||
err := db.ChatsRepo.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.Chats.Attach(context.Background(), user.ID, "whatsapp", "@test_attach")
|
||||
err := db2.ChatsRepo.Attach(context.Background(), user.ID, "whatsapp", "@test_attach")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrAlreadyExists)
|
||||
})
|
||||
@@ -37,27 +37,27 @@ func TestChats_Attach(t *testing.T) {
|
||||
|
||||
func TestChats_GetUserID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = db.Chats.Attach(context.Background(), user.ID, "telegram", "@test_get")
|
||||
err = db.ChatsRepo.Attach(context.Background(), user.ID, "telegram", "@test_get")
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("found", func(t *testing.T) {
|
||||
got, err := db.Chats.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
got, err := db.ChatsRepo.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.Chats.GetUserID(context.Background(), "telegram", "@notfound")
|
||||
_, err := db.ChatsRepo.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.Chats.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
_, err := db2.ChatsRepo.GetUserID(context.Background(), "telegram", "@test_get")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
@@ -65,51 +65,51 @@ func TestChats_GetUserID(t *testing.T) {
|
||||
|
||||
func TestChats_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
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")
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
db.ChatsRepo.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
db.ChatsRepo.Attach(context.Background(), user.ID, "email", "test@example.com")
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
chats, err := db.Chats.List(context.Background(), user.ID)
|
||||
chats, err := db.ChatsRepo.List(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, chats, 2)
|
||||
})
|
||||
|
||||
t.Run("empty list", func(t *testing.T) {
|
||||
otherUser, _ := db.Users.Create(context.Background())
|
||||
chats, err := db.Chats.List(context.Background(), otherUser.ID)
|
||||
otherUser, _ := db.UsersRepo.Create(context.Background())
|
||||
chats, err := db.ChatsRepo.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.Chats.List(context.Background(), user.ID)
|
||||
_, err := db.ChatsRepo.List(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChats_Detach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
db.Chats.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
db.ChatsRepo.Attach(context.Background(), user.ID, "telegram", "@test")
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Chats.Detach(context.Background(), user.ID, "telegram")
|
||||
err := db.ChatsRepo.Detach(context.Background(), user.ID, "telegram")
|
||||
assert.NoError(t, err)
|
||||
|
||||
chats, _ := db.Chats.List(context.Background(), user.ID)
|
||||
chats, _ := db.ChatsRepo.List(context.Background(), user.ID)
|
||||
assert.Empty(t, chats)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
err := db.Chats.Detach(context.Background(), user.ID, "telegram")
|
||||
err := db.ChatsRepo.Detach(context.Background(), user.ID, "telegram")
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("database error", func(t *testing.T) {
|
||||
db.Close()
|
||||
err := db.Chats.Detach(context.Background(), user.ID, "telegram")
|
||||
err := db.ChatsRepo.Detach(context.Background(), user.ID, "telegram")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
@@ -117,7 +117,7 @@ func TestChats_Detach(t *testing.T) {
|
||||
|
||||
func TestChats_List_ScanError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
_, err := db.conn.ExecContext(context.Background(), `
|
||||
ALTER TABLE chats ALTER COLUMN identifier DROP NOT NULL
|
||||
@@ -139,7 +139,7 @@ func TestChats_List_ScanError(t *testing.T) {
|
||||
`)
|
||||
})
|
||||
|
||||
_, err = db.Chats.List(context.Background(), user.ID)
|
||||
_, err = db.ChatsRepo.List(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
func TestContacts_Add(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
owner, _ := db.Users.Create(context.Background())
|
||||
target, _ := db.Users.Create(context.Background())
|
||||
owner, _ := db.UsersRepo.Create(context.Background())
|
||||
target, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
contact := &database.Contact{
|
||||
OwnerID: owner.ID,
|
||||
@@ -24,18 +24,18 @@ func TestContacts_Add(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := db.Contacts.Add(context.Background(), contact)
|
||||
err := db.ContactsRepo.Add(context.Background(), contact)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("already exists", func(t *testing.T) {
|
||||
err := db.Contacts.Add(context.Background(), contact)
|
||||
err := db.ContactsRepo.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)
|
||||
err := db.ContactsRepo.Add(context.Background(), contact)
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrAlreadyExists)
|
||||
})
|
||||
@@ -43,18 +43,18 @@ func TestContacts_Add(t *testing.T) {
|
||||
|
||||
func TestContacts_Get(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
owner, _ := db.Users.Create(context.Background())
|
||||
target, _ := db.Users.Create(context.Background())
|
||||
owner, _ := db.UsersRepo.Create(context.Background())
|
||||
target, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
contact := &database.Contact{
|
||||
OwnerID: owner.ID,
|
||||
TargetID: target.ID,
|
||||
Name: "брат",
|
||||
}
|
||||
db.Contacts.Add(context.Background(), contact)
|
||||
db.ContactsRepo.Add(context.Background(), contact)
|
||||
|
||||
t.Run("found", func(t *testing.T) {
|
||||
got, err := db.Contacts.Get(context.Background(), owner.ID, "брат")
|
||||
got, err := db.ContactsRepo.Get(context.Background(), owner.ID, "брат")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, owner.ID, got.OwnerID)
|
||||
assert.Equal(t, target.ID, got.TargetID)
|
||||
@@ -62,13 +62,13 @@ func TestContacts_Get(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
_, err := db.Contacts.Get(context.Background(), owner.ID, "сестра")
|
||||
_, err := db.ContactsRepo.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, "брат")
|
||||
_, err := db.ContactsRepo.Get(context.Background(), owner.ID, "брат")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
@@ -76,56 +76,56 @@ func TestContacts_Get(t *testing.T) {
|
||||
|
||||
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())
|
||||
owner, _ := db.UsersRepo.Create(context.Background())
|
||||
target1, _ := db.UsersRepo.Create(context.Background())
|
||||
target2, _ := db.UsersRepo.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: "друг"})
|
||||
db.ContactsRepo.Add(context.Background(), &database.Contact{OwnerID: owner.ID, TargetID: target1.ID, Name: "брат"})
|
||||
db.ContactsRepo.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)
|
||||
contacts, err := db.ContactsRepo.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)
|
||||
other, _ := db.UsersRepo.Create(context.Background())
|
||||
contacts, err := db.ContactsRepo.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)
|
||||
_, err := db.ContactsRepo.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())
|
||||
owner, _ := db.UsersRepo.Create(context.Background())
|
||||
target, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
db.Contacts.Add(context.Background(), &database.Contact{OwnerID: owner.ID, TargetID: target.ID, Name: "брат"})
|
||||
db.ContactsRepo.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, "брат")
|
||||
err := db.ContactsRepo.Delete(context.Background(), owner.ID, "брат")
|
||||
require.NoError(t, err)
|
||||
|
||||
contacts, _ := db.Contacts.List(context.Background(), owner.ID)
|
||||
contacts, _ := db.ContactsRepo.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, "брат")
|
||||
err := db.ContactsRepo.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, "брат")
|
||||
err := db.ContactsRepo.Delete(context.Background(), owner.ID, "брат")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
@@ -14,60 +14,60 @@ import (
|
||||
|
||||
func TestFacts_Add(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
err := db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
err := db.FactsRepo.Add(context.Background(), user.ID, "мама Ирина")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
err = db.FactsRepo.Add(context.Background(), user.ID, "мама Ирина")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFacts_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
db.Facts.Add(context.Background(), user.ID, "спит в 23:30")
|
||||
db.FactsRepo.Add(context.Background(), user.ID, "мама Ирина")
|
||||
db.FactsRepo.Add(context.Background(), user.ID, "спит в 23:30")
|
||||
|
||||
facts, err := db.Facts.List(context.Background(), user.ID)
|
||||
facts, err := db.FactsRepo.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())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
db.Facts.Add(context.Background(), user.ID, "мама Ирина")
|
||||
db.FactsRepo.Add(context.Background(), user.ID, "мама Ирина")
|
||||
|
||||
err := db.Facts.Delete(context.Background(), user.ID, "мама Ирина")
|
||||
err := db.FactsRepo.Delete(context.Background(), user.ID, "мама Ирина")
|
||||
require.NoError(t, err)
|
||||
|
||||
facts, _ := db.Facts.List(context.Background(), user.ID)
|
||||
facts, _ := db.FactsRepo.List(context.Background(), user.ID)
|
||||
assert.Empty(t, facts)
|
||||
|
||||
err = db.Facts.Delete(context.Background(), user.ID, "мама Ирина")
|
||||
err = db.FactsRepo.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")
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
db.FactsRepo.Add(context.Background(), user.ID, "test")
|
||||
|
||||
db.Close()
|
||||
_, err := db.Facts.List(context.Background(), user.ID)
|
||||
_, err := db.FactsRepo.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")
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
db.FactsRepo.Add(context.Background(), user.ID, "test")
|
||||
|
||||
db.Close()
|
||||
err := db.Facts.Delete(context.Background(), user.ID, "test")
|
||||
err := db.FactsRepo.Delete(context.Background(), user.ID, "test")
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ import (
|
||||
|
||||
func TestNotifications_ConcurrentPop(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now().UTC().Truncate(time.Minute)
|
||||
|
||||
for range 1000 {
|
||||
err := db.Notifications.Push(ctx, &database.Notification{
|
||||
err := db.NotificationsRepo.Push(ctx, &database.Notification{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
ScheduledAt: now,
|
||||
@@ -37,7 +37,7 @@ func TestNotifications_ConcurrentPop(t *testing.T) {
|
||||
for range 10 {
|
||||
wg.Go(func() {
|
||||
for {
|
||||
notifs, err := db.Notifications.Pop(ctx, 10)
|
||||
notifs, err := db.NotificationsRepo.Pop(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
if len(notifs) == 0 {
|
||||
return
|
||||
@@ -58,7 +58,7 @@ func TestNotifications_ConcurrentPop(t *testing.T) {
|
||||
}
|
||||
assert.Len(t, ids, 1000)
|
||||
|
||||
remaining, err := db.Notifications.Pop(ctx, 10)
|
||||
remaining, err := db.NotificationsRepo.Pop(ctx, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, remaining)
|
||||
}
|
||||
@@ -85,36 +85,36 @@ func TestNotifications_Pop_ScanError(t *testing.T) {
|
||||
|
||||
func TestNotifications_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
user, _ := db.Users.Create(context.Background())
|
||||
user, _ := db.UsersRepo.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)
|
||||
db.NotificationsRepo.Push(context.Background(), n1)
|
||||
db.NotificationsRepo.Push(context.Background(), n2)
|
||||
|
||||
notifs, err := db.Notifications.List(context.Background(), user.ID)
|
||||
notifs, err := db.NotificationsRepo.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)
|
||||
other, _ := db.UsersRepo.Create(context.Background())
|
||||
notifs, err = db.NotificationsRepo.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())
|
||||
user, _ := db.UsersRepo.Create(context.Background())
|
||||
|
||||
n := &database.Notification{ID: uuid.New(), UserID: user.ID, ScheduledAt: time.Now().UTC(), Content: "test"}
|
||||
db.Notifications.Push(context.Background(), n)
|
||||
db.NotificationsRepo.Push(context.Background(), n)
|
||||
|
||||
err := db.Notifications.Delete(context.Background(), n.ID)
|
||||
err := db.NotificationsRepo.Delete(context.Background(), n.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Notifications.Delete(context.Background(), n.ID)
|
||||
err = db.NotificationsRepo.Delete(context.Background(), n.ID)
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,17 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/d1nch8g/jules/database"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
Users Users
|
||||
Chats Chats
|
||||
Facts Facts
|
||||
Contacts Contacts
|
||||
Notifications Notifications
|
||||
Actions Actions
|
||||
UsersRepo *Users
|
||||
ChatsRepo *Chats
|
||||
FactsRepo *Facts
|
||||
ContactsRepo *Contacts
|
||||
NotificationsRepo *Notifications
|
||||
ActionsRepo *Actions
|
||||
|
||||
conn *sql.DB
|
||||
}
|
||||
@@ -40,17 +41,41 @@ func New(connString string) (*DB, error) {
|
||||
}
|
||||
|
||||
return &DB{
|
||||
Users: Users{conn: conn},
|
||||
Chats: Chats{conn: conn},
|
||||
Facts: Facts{conn: conn},
|
||||
Contacts: Contacts{conn: conn},
|
||||
Notifications: Notifications{conn: conn},
|
||||
Actions: Actions{conn: conn},
|
||||
UsersRepo: &Users{conn: conn},
|
||||
ChatsRepo: &Chats{conn: conn},
|
||||
FactsRepo: &Facts{conn: conn},
|
||||
ContactsRepo: &Contacts{conn: conn},
|
||||
NotificationsRepo: &Notifications{conn: conn},
|
||||
ActionsRepo: &Actions{conn: conn},
|
||||
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DB) Users() database.Users {
|
||||
return db.UsersRepo
|
||||
}
|
||||
|
||||
func (db *DB) Chats() database.Chats {
|
||||
return db.ChatsRepo
|
||||
}
|
||||
|
||||
func (db *DB) Facts() database.Facts {
|
||||
return db.FactsRepo
|
||||
}
|
||||
|
||||
func (db *DB) Contacts() database.Contacts {
|
||||
return db.ContactsRepo
|
||||
}
|
||||
|
||||
func (db *DB) Notifications() database.Notifications {
|
||||
return db.NotificationsRepo
|
||||
}
|
||||
|
||||
func (db *DB) Actions() database.Actions {
|
||||
return db.ActionsRepo
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
return db.conn.Close()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,13 @@ func TestNew_Success(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db)
|
||||
defer db.Close()
|
||||
|
||||
assert.NotNil(t, db.Users())
|
||||
assert.NotNil(t, db.Chats())
|
||||
assert.NotNil(t, db.Facts())
|
||||
assert.NotNil(t, db.Contacts())
|
||||
assert.NotNil(t, db.Notifications())
|
||||
assert.NotNil(t, db.Actions())
|
||||
}
|
||||
|
||||
func TestNew_PingFailed(t *testing.T) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func TestUsers_Create(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, uuid.Nil, user.ID)
|
||||
assert.Equal(t, "telegram", user.PreferredChat)
|
||||
@@ -23,18 +23,18 @@ func TestUsers_Create(t *testing.T) {
|
||||
func TestUsers_Get(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("found", func(t *testing.T) {
|
||||
fetched, err := db.Users.Get(context.Background(), user.ID)
|
||||
fetched, err := db.UsersRepo.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.Users.Get(context.Background(), uuid.New())
|
||||
_, err := db.UsersRepo.Get(context.Background(), uuid.New())
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func TestUsers_Get(t *testing.T) {
|
||||
func TestUsers_Update(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
@@ -50,10 +50,10 @@ func TestUsers_Update(t *testing.T) {
|
||||
user.Language = "ru"
|
||||
user.Timezone = "Europe/Moscow"
|
||||
|
||||
err := db.Users.Update(context.Background(), user)
|
||||
err := db.UsersRepo.Update(context.Background(), user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fetched, err := db.Users.Get(context.Background(), user.ID)
|
||||
fetched, err := db.UsersRepo.Get(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "whatsapp", fetched.PreferredChat)
|
||||
assert.Equal(t, "ru", fetched.Language)
|
||||
@@ -62,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.Users.Update(context.Background(), ghost)
|
||||
err := db.UsersRepo.Update(context.Background(), ghost)
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
@@ -71,18 +71,18 @@ func TestUsers_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = db.Users.Delete(context.Background(), user.ID)
|
||||
err = db.UsersRepo.Delete(context.Background(), user.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = db.Users.Get(context.Background(), user.ID)
|
||||
_, err = db.UsersRepo.Get(context.Background(), user.ID)
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
err := db.Users.Delete(context.Background(), uuid.New())
|
||||
err := db.UsersRepo.Delete(context.Background(), uuid.New())
|
||||
assert.ErrorIs(t, err, database.ErrNotFound)
|
||||
})
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func TestUsers_Create_DatabaseError(t *testing.T) {
|
||||
|
||||
db.Close()
|
||||
|
||||
_, err := db.Users.Create(context.Background())
|
||||
_, err := db.UsersRepo.Create(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func TestUsers_Get_DatabaseError(t *testing.T) {
|
||||
|
||||
db.Close()
|
||||
|
||||
_, err := db.Users.Get(context.Background(), uuid.New())
|
||||
_, err := db.UsersRepo.Get(context.Background(), uuid.New())
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -110,12 +110,12 @@ func TestUsers_Get_DatabaseError(t *testing.T) {
|
||||
func TestUsers_Update_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
db.Close()
|
||||
|
||||
err = db.Users.Update(context.Background(), user)
|
||||
err = db.UsersRepo.Update(context.Background(), user)
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
@@ -123,12 +123,12 @@ func TestUsers_Update_DatabaseError(t *testing.T) {
|
||||
func TestUsers_Delete_DatabaseError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
user, err := db.Users.Create(context.Background())
|
||||
user, err := db.UsersRepo.Create(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
db.Close()
|
||||
|
||||
err = db.Users.Delete(context.Background(), user.ID)
|
||||
err = db.UsersRepo.Delete(context.Background(), user.ID)
|
||||
assert.Error(t, err)
|
||||
assert.NotErrorIs(t, err, database.ErrNotFound)
|
||||
}
|
||||
|
||||
@@ -1 +1,64 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/d1nch8g/jules/chat"
|
||||
"github.com/d1nch8g/jules/database"
|
||||
"github.com/d1nch8g/jules/llm"
|
||||
"github.com/d1nch8g/jules/search"
|
||||
)
|
||||
|
||||
type Parameters struct {
|
||||
Database database.Database
|
||||
LLM llm.LLM
|
||||
Searcher search.Searcher
|
||||
Chats map[string]chat.Chat
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
Database database.Database
|
||||
LLM llm.LLM
|
||||
Searcher search.Searcher
|
||||
Chats map[string]chat.Chat
|
||||
}
|
||||
|
||||
func New(params *Parameters) (*Engine, error) {
|
||||
if params == nil {
|
||||
return nil, errors.New("params can't be nil")
|
||||
}
|
||||
|
||||
if params.Database == nil {
|
||||
return nil, errors.New("database can't be nil")
|
||||
}
|
||||
|
||||
if params.LLM == nil {
|
||||
return nil, errors.New("llm can't be nil")
|
||||
}
|
||||
|
||||
if params.Searcher == nil {
|
||||
return nil, errors.New("seach engine can't be nil")
|
||||
}
|
||||
|
||||
if len(params.Chats) == 0 {
|
||||
return nil, errors.New("chats can't be nil or with 0 length")
|
||||
}
|
||||
|
||||
for name, chat := range params.Chats {
|
||||
if chat == nil {
|
||||
return nil, fmt.Errorf("chat %s is nil, provide implementation", name)
|
||||
}
|
||||
}
|
||||
|
||||
return &Engine{
|
||||
Database: params.Database,
|
||||
LLM: params.LLM,
|
||||
Searcher: params.Searcher,
|
||||
Chats: params.Chats,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *Engine) Run() {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user