package service import ( "context" "testing" "github.com/gochat/gochat/internal/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ========== Create ========== func TestPlatformAppService_Create_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) req := CreatePlatformAppRequest{ Name: "TestPlatformApp", Description: "A test platform app", Type: "api", AccountID: account.ID, } app, err := svc.Create(context.Background(), req) require.NoError(t, err) assert.NotZero(t, app.ID) assert.Equal(t, "TestPlatformApp", app.Name) assert.NotEmpty(t, app.APIKey, "API key should be auto-generated") assert.Equal(t, "api", app.Type) assert.Equal(t, "active", app.Status, "status should default to active") assert.Equal(t, account.ID, app.AccountID) } func TestPlatformAppService_Create_DefaultType(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) req := CreatePlatformAppRequest{ Name: "NoTypeApp", AccountID: account.ID, } app, err := svc.Create(context.Background(), req) require.NoError(t, err) assert.Equal(t, "api", app.Type, "type should default to 'api' when empty") } func TestPlatformAppService_Create_AgentBotType(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) req := CreatePlatformAppRequest{ Name: "BotApp", Type: "agent_bot", AccountID: account.ID, } app, err := svc.Create(context.Background(), req) require.NoError(t, err) assert.Equal(t, "agent_bot", app.Type) } func TestPlatformAppService_Create_PlatformLevel(t *testing.T) { _, _, svc := setupPlatformAppService(t) // AccountID = 0 means platform-level (no account restriction) req := CreatePlatformAppRequest{ Name: "PlatformApp", Type: "api", } app, err := svc.Create(context.Background(), req) require.NoError(t, err) assert.Zero(t, app.AccountID, "platform-level app should have AccountID=0") } func TestPlatformAppService_Create_Validation_NameRequired(t *testing.T) { _, _, svc := setupPlatformAppService(t) req := CreatePlatformAppRequest{ Name: "", // empty name — should fail validation (min=2) } _, err := svc.Create(context.Background(), req) assert.Error(t, err, "empty name should fail validation") } func TestPlatformAppService_Create_Validation_NameTooShort(t *testing.T) { _, _, svc := setupPlatformAppService(t) req := CreatePlatformAppRequest{ Name: "X", // single char — min=2 } _, err := svc.Create(context.Background(), req) assert.Error(t, err, "1-char name should fail validation (min=2)") } func TestPlatformAppService_Create_Validation_InvalidType(t *testing.T) { _, _, svc := setupPlatformAppService(t) req := CreatePlatformAppRequest{ Name: "ValidName", Type: "invalid_type", // not in oneof=api agent_bot } _, err := svc.Create(context.Background(), req) assert.Error(t, err, "invalid type should fail validation") } func TestPlatformAppService_Create_Validation_DescriptionTooLong(t *testing.T) { _, _, svc := setupPlatformAppService(t) // Create a description > 500 chars longDesc := "" for i := 0; i < 501; i++ { longDesc += "x" } req := CreatePlatformAppRequest{ Name: "ValidName", Description: longDesc, } _, err := svc.Create(context.Background(), req) assert.Error(t, err, "description > 500 chars should fail validation") } func TestPlatformAppService_Create_UniqueAPIKey(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) req1 := CreatePlatformAppRequest{ Name: "App1", Type: "api", AccountID: account.ID, } app1, err := svc.Create(context.Background(), req1) require.NoError(t, err) req2 := CreatePlatformAppRequest{ Name: "App2", Type: "api", AccountID: account.ID, } app2, err := svc.Create(context.Background(), req2) require.NoError(t, err) assert.NotEqual(t, app1.APIKey, app2.APIKey, "each create should generate a unique API key") } // ========== GetByID ========== func TestPlatformAppService_GetByID_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) found, err := svc.GetByID(context.Background(), app.ID) require.NoError(t, err) assert.Equal(t, app.ID, found.ID) assert.Equal(t, app.Name, found.Name) assert.Equal(t, app.APIKey, found.APIKey) } func TestPlatformAppService_GetByID_NotFound(t *testing.T) { _, _, svc := setupPlatformAppService(t) _, err := svc.GetByID(context.Background(), 99999) assert.Error(t, err) } // ========== Update ========== func TestPlatformAppService_Update_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) req := UpdatePlatformAppRequest{ Name: "UpdatedName", Description: "Updated description", } updated, err := svc.Update(context.Background(), app.ID, req) require.NoError(t, err) assert.Equal(t, "UpdatedName", updated.Name) assert.Equal(t, "Updated description", updated.Description) } func TestPlatformAppService_Update_Status(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) req := UpdatePlatformAppRequest{ Status: "disabled", } updated, err := svc.Update(context.Background(), app.ID, req) require.NoError(t, err) assert.Equal(t, "disabled", updated.Status) } func TestPlatformAppService_Update_Type(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) req := UpdatePlatformAppRequest{ Type: "agent_bot", } updated, err := svc.Update(context.Background(), app.ID, req) require.NoError(t, err) assert.Equal(t, "agent_bot", updated.Type) } func TestPlatformAppService_Update_PartialFields(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) originalDesc := app.Description // Only update Name, leave other fields unchanged req := UpdatePlatformAppRequest{ Name: "OnlyNameChanged", } updated, err := svc.Update(context.Background(), app.ID, req) require.NoError(t, err) assert.Equal(t, "OnlyNameChanged", updated.Name) assert.Equal(t, originalDesc, updated.Description, "description should not change") } func TestPlatformAppService_Update_NotFound(t *testing.T) { _, _, svc := setupPlatformAppService(t) req := UpdatePlatformAppRequest{ Name: "NewName", } _, err := svc.Update(context.Background(), 99999, req) assert.Error(t, err) } func TestPlatformAppService_Update_Validation_InvalidType(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) req := UpdatePlatformAppRequest{ Type: "invalid_type", } _, err := svc.Update(context.Background(), app.ID, req) assert.Error(t, err, "invalid type should fail validation") } func TestPlatformAppService_Update_Validation_InvalidStatus(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) req := UpdatePlatformAppRequest{ Status: "unknown_status", } _, err := svc.Update(context.Background(), app.ID, req) assert.Error(t, err, "invalid status should fail validation") } // ========== Delete ========== func TestPlatformAppService_Delete_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) app := createTestPlatformApp(t, db, account.ID) err := svc.Delete(context.Background(), app.ID) require.NoError(t, err) // Verify soft-delete — GetByID should fail _, err = svc.GetByID(context.Background(), app.ID) assert.Error(t, err, "soft-deleted app should not be retrievable") } func TestPlatformAppService_Delete_NotFound(t *testing.T) { _, _, svc := setupPlatformAppService(t) err := svc.Delete(context.Background(), 99999) // GORM Delete on non-existent record may or may not error depending on dialect // SQLite: no error (0 rows affected is not an error for GORM) assert.NoError(t, err) } // ========== ListAll ========== func TestPlatformAppService_ListAll_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) // Create multiple apps for i := 0; i < 5; i++ { createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "ListApp" + string(rune('A'+i)) app.APIKey = "list-key-" + string(rune('A'+i)) }) } apps, count, err := svc.ListAll(context.Background(), 0, 10) require.NoError(t, err) assert.Equal(t, int64(5), count) assert.Len(t, apps, 5) } func TestPlatformAppService_ListAll_Pagination(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) for i := 0; i < 10; i++ { createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "PageApp" + string(rune('0'+i)) app.APIKey = "page-key-" + string(rune('0'+i)) }) } apps, count, err := svc.ListAll(context.Background(), 0, 3) require.NoError(t, err) assert.Equal(t, int64(10), count) assert.Len(t, apps, 3) apps, count, err = svc.ListAll(context.Background(), 3, 3) require.NoError(t, err) assert.Equal(t, int64(10), count) assert.Len(t, apps, 3) } func TestPlatformAppService_ListAll_Empty(t *testing.T) { _, _, svc := setupPlatformAppService(t) apps, count, err := svc.ListAll(context.Background(), 0, 10) require.NoError(t, err) assert.Equal(t, int64(0), count) assert.Len(t, apps, 0) } // ========== ListByAccount ========== func TestPlatformAppService_ListByAccount_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account1 := createTestAccount(t, db) account2 := createTestAccount(t, db) // Create apps for account1 for i := 0; i < 3; i++ { createTestPlatformApp(t, db, account1.ID, func(app *model.PlatformApp) { app.Name = "Acc1App" + string(rune('A'+i)) app.APIKey = "acc1-key-" + string(rune('A'+i)) }) } // Create apps for account2 for i := 0; i < 2; i++ { createTestPlatformApp(t, db, account2.ID, func(app *model.PlatformApp) { app.Name = "Acc2App" + string(rune('A'+i)) app.APIKey = "acc2-key-" + string(rune('A'+i)) }) } apps, count, err := svc.ListByAccount(context.Background(), account1.ID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(3), count) assert.Len(t, apps, 3) apps, count, err = svc.ListByAccount(context.Background(), account2.ID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), count) assert.Len(t, apps, 2) } func TestPlatformAppService_ListByAccount_Empty(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) apps, count, err := svc.ListByAccount(context.Background(), account.ID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(0), count) assert.Len(t, apps, 0) } // ========== RegenerateAPIKey ========== func TestPlatformAppService_RegenerateAPIKey_Success(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) req := CreatePlatformAppRequest{ Name: "KeyRotateApp", AccountID: account.ID, } app, err := svc.Create(context.Background(), req) require.NoError(t, err) originalKey := app.APIKey require.NotEmpty(t, originalKey) rotated, err := svc.RegenerateAPIKey(context.Background(), app.ID) require.NoError(t, err) assert.NotEqual(t, originalKey, rotated.APIKey, "new key should differ from old key") assert.NotEmpty(t, rotated.APIKey, "new key should not be empty") assert.Equal(t, app.ID, rotated.ID, "ID should stay the same") assert.Equal(t, app.Name, rotated.Name, "name should stay the same") } func TestPlatformAppService_RegenerateAPIKey_NotFound(t *testing.T) { _, _, svc := setupPlatformAppService(t) _, err := svc.RegenerateAPIKey(context.Background(), 99999) assert.Error(t, err, "should fail for nonexistent app") } func TestPlatformAppService_RegenerateAPIKey_DoubleRotate(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) req := CreatePlatformAppRequest{ Name: "DoubleRotateApp", AccountID: account.ID, } app, err := svc.Create(context.Background(), req) require.NoError(t, err) first, err := svc.RegenerateAPIKey(context.Background(), app.ID) require.NoError(t, err) second, err := svc.RegenerateAPIKey(context.Background(), app.ID) require.NoError(t, err) assert.NotEqual(t, first.APIKey, second.APIKey, "each rotation should produce a different key") } // ========== Search ========== func TestPlatformAppService_Search_EmptyQuery(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) // Create some apps so list is non-empty createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "AlphaApp" }) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "BetaApp" }) // Empty query should delegate to FindAll (return all apps) apps, total, err := svc.Search(context.Background(), "", 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), total) assert.Len(t, apps, 2) } func TestPlatformAppService_Search_WithQuery(t *testing.T) { // ILIKE requires PostgreSQL; SQLite does not support it. // This test will skip if running against SQLite. db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "MyAlphaApp" }) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "MyBetaApp" }) apps, total, err := svc.Search(context.Background(), "Alpha", 0, 10) if err != nil { t.Logf("Search failed (likely SQLite ILIKE incompatibility): %v", err) t.Skip("skip: SQLite does not support ILIKE (PlatformAppRepo.SearchByName needs PG)") } assert.Equal(t, int64(1), total) assert.Len(t, apps, 1) assert.Equal(t, "MyAlphaApp", apps[0].Name) } func TestPlatformAppService_SearchByAccount_EmptyQuery(t *testing.T) { db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "App1" }) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "App2" }) apps, total, err := svc.SearchByAccount(context.Background(), account.ID, "", 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), total) assert.Len(t, apps, 2) } func TestPlatformAppService_SearchByAccount_WithQuery(t *testing.T) { // ILIKE requires PostgreSQL; SQLite does not support it. db, _, svc := setupPlatformAppService(t) account := createTestAccount(t, db) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "SearchApp1" }) createTestPlatformApp(t, db, account.ID, func(app *model.PlatformApp) { app.Name = "SearchApp2" }) apps, total, err := svc.SearchByAccount(context.Background(), account.ID, "App1", 0, 10) if err != nil { t.Logf("SearchByAccount failed (likely SQLite ILIKE incompatibility): %v", err) t.Skip("skip: SQLite does not support ILIKE (PlatformAppRepo.SearchByNameByAccount needs PG)") } assert.Equal(t, int64(1), total) assert.Len(t, apps, 1) assert.Equal(t, "SearchApp1", apps[0].Name) }