additional refactoring for good interface composition alternative

This commit is contained in:
d1nch8g
2026-04-16 23:37:10 +03:00
parent 110d841e50
commit 1a73256494
10 changed files with 285 additions and 180 deletions
+58 -48
View File
@@ -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")
)
+19 -19
View File
@@ -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(),
+24 -24
View File
@@ -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)
}
+27 -27
View File
@@ -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)
})
+18 -18
View File
@@ -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)
}
+14 -14
View File
@@ -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)
}
+37 -12
View File
@@ -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()
}
+7
View File
@@ -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) {
+18 -18
View File
@@ -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)
}
+63
View File
@@ -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() {
}