package service import ( "context" "errors" "fmt" "testing" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func setupCaptainDocumentServiceTest(t *testing.T) (*gorm.DB, *CaptainDocumentService) { t.Helper() dbName := fmt.Sprintf("file:%s?mode=memory&cache=private", t.Name()) db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{}) require.NoError(t, err) require.NoError(t, db.AutoMigrate(&model.Account{}, &model.CaptainAssistant{}, &model.CaptainDocument{})) t.Cleanup(func() { sqlDB, _ := db.DB() sqlDB.Close() }) documentRepo := repository.NewCaptainDocumentRepo(db) assistantRepo := repository.NewCaptainAssistantRepo(db) return db, NewCaptainDocumentService(documentRepo, nil, assistantRepo) } func seedCaptainDocumentSyncFixture(t *testing.T, db *gorm.DB) (*model.Account, *model.Account, *model.CaptainDocument) { t.Helper() account := &model.Account{Name: "Captain Org", Active: true} otherAccount := &model.Account{Name: "Other Org", Active: true} require.NoError(t, db.Create(account).Error) require.NoError(t, db.Create(otherAccount).Error) assistant := &model.CaptainAssistant{AccountID: account.ID, Name: "Fin", Description: "Support", Status: model.AssistantStatusActive} require.NoError(t, db.Create(assistant).Error) doc := &model.CaptainDocument{ AccountID: account.ID, AssistantID: assistant.ID, Name: "Help", ExternalLink: "https://example.com/help", Status: model.DocumentStatusCompleted, SyncStatus: model.DocumentSyncStatusSynced, } require.NoError(t, db.Create(doc).Error) return account, otherAccount, doc } func TestCaptainDocumentService_SyncDocumentByAccountDisabledMarksFailed(t *testing.T) { db, svc := setupCaptainDocumentServiceTest(t) account, _, doc := seedCaptainDocumentSyncFixture(t, db) updated, err := svc.SyncDocumentByAccount(context.Background(), account.ID, doc.ID) require.NoError(t, err) assert.Equal(t, model.DocumentSyncStatusFailed, updated.SyncStatus) assert.Equal(t, "sync_disabled", updated.LastSyncErrorCode) assert.NotNil(t, updated.LastSyncAttemptedAt) } func TestCaptainDocumentService_SyncDocumentByAccountUsesFakeBackend(t *testing.T) { db, svc := setupCaptainDocumentServiceTest(t) account, _, doc := seedCaptainDocumentSyncFixture(t, db) backend := &captainDocumentFakeSyncBackend{result: &CaptainDocumentSyncResult{Title: "Fresh Help", Content: "hello\n\nworld"}} svc.SetSyncBackend(backend) updated, err := svc.SyncDocumentByAccount(context.Background(), account.ID, doc.ID) require.NoError(t, err) assert.Equal(t, doc.ID, backend.documentID) assert.Equal(t, "Fresh Help", updated.Name) assert.Equal(t, "hello\n\nworld", updated.Content) assert.Equal(t, computeFingerprint("hello world"), updated.ContentFingerprint) assert.Equal(t, model.DocumentStatusCompleted, updated.Status) assert.Equal(t, model.DocumentSyncStatusSynced, updated.SyncStatus) assert.Empty(t, updated.LastSyncErrorCode) assert.NotNil(t, updated.LastSyncedAt) } func TestCaptainDocumentService_SyncDocumentByAccountRecordsBackendFailures(t *testing.T) { db, svc := setupCaptainDocumentServiceTest(t) account, _, doc := seedCaptainDocumentSyncFixture(t, db) svc.SetSyncBackend(&captainDocumentFakeSyncBackend{err: errors.New("boom")}) updated, err := svc.SyncDocumentByAccount(context.Background(), account.ID, doc.ID) require.Error(t, err) assert.Equal(t, model.DocumentSyncStatusFailed, updated.SyncStatus) assert.Equal(t, "sync_error", updated.LastSyncErrorCode) svc.SetSyncBackend(&captainDocumentFakeSyncBackend{result: &CaptainDocumentSyncResult{ErrorCode: "access_denied"}}) updated, err = svc.SyncDocumentByAccount(context.Background(), account.ID, doc.ID) require.NoError(t, err) assert.Equal(t, model.DocumentSyncStatusFailed, updated.SyncStatus) assert.Equal(t, "access_denied", updated.LastSyncErrorCode) } func TestCaptainDocumentService_SyncDocumentByAccountScopesDocument(t *testing.T) { db, svc := setupCaptainDocumentServiceTest(t) _, otherAccount, doc := seedCaptainDocumentSyncFixture(t, db) svc.SetSyncBackend(&captainDocumentFakeSyncBackend{result: &CaptainDocumentSyncResult{Content: "fresh"}}) _, err := svc.SyncDocumentByAccount(context.Background(), otherAccount.ID, doc.ID) require.Error(t, err) } type captainDocumentFakeSyncBackend struct { result *CaptainDocumentSyncResult err error documentID uint } func (b *captainDocumentFakeSyncBackend) SyncCaptainDocument(ctx context.Context, doc *model.CaptainDocument) (*CaptainDocumentSyncResult, error) { b.documentID = doc.ID if b.err != nil { return nil, b.err } return b.result, nil }