package domain import ( "context" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitlab.com/unboundsoftware/schemas/hash" ) // AddOrganization tests func TestAddOrganization_Validate_Success(t *testing.T) { cmd := AddOrganization{ Name: "Test Org", Initiator: "user@example.com", } org := &Organization{} // New organization with no identity err := cmd.Validate(context.Background(), org) assert.NoError(t, err) } func TestAddOrganization_Validate_AlreadyExists(t *testing.T) { cmd := AddOrganization{ Name: "Test Org", Initiator: "user@example.com", } org := &Organization{ BaseAggregate: eventsourced.BaseAggregateFromString("existing-org-id"), } err := cmd.Validate(context.Background(), org) require.Error(t, err) assert.Contains(t, err.Error(), "already exists") } func TestAddOrganization_Validate_EmptyName(t *testing.T) { cmd := AddOrganization{ Name: "", Initiator: "user@example.com", } org := &Organization{} err := cmd.Validate(context.Background(), org) require.Error(t, err) assert.Contains(t, err.Error(), "name is required") } func TestAddOrganization_Event(t *testing.T) { cmd := AddOrganization{ Name: "Test Org", Initiator: "user@example.com", } event := cmd.Event(context.Background()) require.NotNil(t, event) orgEvent, ok := event.(*OrganizationAdded) require.True(t, ok) assert.Equal(t, "Test Org", orgEvent.Name) assert.Equal(t, "user@example.com", orgEvent.Initiator) } // AddAPIKey tests func TestAddAPIKey_Event(t *testing.T) { type fields struct { Name string Key string Refs []string Read bool Publish bool Initiator string } type args struct { in0 context.Context } tests := []struct { name string fields fields args args }{ { name: "event", fields: fields{ Name: "test", Key: "us_ak_1234567890123456", Refs: []string{"Example@dev"}, Read: true, Publish: true, Initiator: "jim@example.org", }, args: args{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := AddAPIKey{ Name: tt.fields.Name, Key: tt.fields.Key, Refs: tt.fields.Refs, Read: tt.fields.Read, Publish: tt.fields.Publish, Initiator: tt.fields.Initiator, } event := a.Event(tt.args.in0) require.NotNil(t, event) // Cast to APIKeyAdded to verify fields apiKeyEvent, ok := event.(*APIKeyAdded) require.True(t, ok, "Event should be *APIKeyAdded") // Verify non-key fields match exactly assert.Equal(t, tt.fields.Name, apiKeyEvent.Name) assert.Equal(t, tt.fields.Refs, apiKeyEvent.Refs) assert.Equal(t, tt.fields.Read, apiKeyEvent.Read) assert.Equal(t, tt.fields.Publish, apiKeyEvent.Publish) assert.Equal(t, tt.fields.Initiator, apiKeyEvent.Initiator) // Verify the key is hashed correctly (bcrypt format) assert.True(t, strings.HasPrefix(apiKeyEvent.Key, "$2"), "Key should be bcrypt hashed") assert.NotEqual(t, tt.fields.Key, apiKeyEvent.Key, "Key should be hashed, not plaintext") // Verify the hash matches the original key assert.True(t, hash.CompareAPIKey(apiKeyEvent.Key, tt.fields.Key), "Hashed key should match original") }) } } func TestAddAPIKey_Validate_Success(t *testing.T) { cmd := AddAPIKey{ Name: "production-key", Key: "us_ak_1234567890123456", Refs: []string{"main"}, Read: true, Publish: false, Initiator: "user@example.com", } org := &Organization{ BaseAggregate: eventsourced.BaseAggregateFromString("org-123"), APIKeys: []APIKey{}, } err := cmd.Validate(context.Background(), org) assert.NoError(t, err) } func TestAddAPIKey_Validate_OrganizationNotExists(t *testing.T) { cmd := AddAPIKey{ Name: "production-key", Key: "us_ak_1234567890123456", Refs: []string{"main"}, Read: true, Publish: false, Initiator: "user@example.com", } org := &Organization{} // No identity means it doesn't exist err := cmd.Validate(context.Background(), org) require.Error(t, err) assert.Contains(t, err.Error(), "does not exist") } func TestAddAPIKey_Validate_DuplicateKeyName(t *testing.T) { cmd := AddAPIKey{ Name: "existing-key", Key: "us_ak_1234567890123456", Refs: []string{"main"}, Read: true, Publish: false, Initiator: "user@example.com", } org := &Organization{ BaseAggregate: eventsourced.BaseAggregateFromString("org-123"), APIKeys: []APIKey{ { Name: "existing-key", Key: "hashed-key", }, }, } err := cmd.Validate(context.Background(), org) require.Error(t, err) assert.Contains(t, err.Error(), "already exist") assert.Contains(t, err.Error(), "existing-key") } // UpdateSubGraph tests func TestUpdateSubGraph_Validate_Success(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &url, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) assert.NoError(t, err) } func TestUpdateSubGraph_Validate_MissingRef(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "", Service: "users", Url: &url, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "ref is missing") } func TestUpdateSubGraph_Validate_RefWhitespaceOnly(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: " ", Service: "users", Url: &url, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "ref is missing") } func TestUpdateSubGraph_Validate_MissingService(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "", Url: &url, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "service is missing") } func TestUpdateSubGraph_Validate_ServiceWhitespaceOnly(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: " ", Url: &url, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "service is missing") } func TestUpdateSubGraph_Validate_MissingSDL(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &url, Sdl: "", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "SDL is missing") } func TestUpdateSubGraph_Validate_SDLWhitespaceOnly(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &url, Sdl: " \n\t ", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "SDL is missing") } func TestUpdateSubGraph_Validate_MissingURL_NoExistingURL(t *testing.T) { cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: nil, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), Url: nil, // No existing URL } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "url is missing") } func TestUpdateSubGraph_Validate_MissingURL_HasExistingURL(t *testing.T) { existingURL := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: nil, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), Url: &existingURL, // Has existing URL, so nil is OK } err := cmd.Validate(context.Background(), subGraph) assert.NoError(t, err) } func TestUpdateSubGraph_Validate_EmptyURL_NoExistingURL(t *testing.T) { emptyURL := "" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &emptyURL, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), Url: nil, } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "url is missing") } func TestUpdateSubGraph_Validate_URLWhitespaceOnly_NoExistingURL(t *testing.T) { whitespaceURL := " " cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &whitespaceURL, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } subGraph := &SubGraph{ BaseAggregate: eventsourced.BaseAggregateFromString("subgraph-123"), Url: nil, } err := cmd.Validate(context.Background(), subGraph) require.Error(t, err) assert.Contains(t, err.Error(), "url is missing") } func TestUpdateSubGraph_Validate_WrongAggregateType(t *testing.T) { url := "http://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &url, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } // Pass wrong aggregate type org := &Organization{ BaseAggregate: eventsourced.BaseAggregateFromString("org-123"), } err := cmd.Validate(context.Background(), org) require.Error(t, err) assert.Contains(t, err.Error(), "not a SubGraph") } func TestUpdateSubGraph_Event(t *testing.T) { url := "http://example.com/graphql" wsURL := "ws://example.com/graphql" cmd := UpdateSubGraph{ OrganizationId: "org-123", Ref: "main", Service: "users", Url: &url, WSUrl: &wsURL, Sdl: "type Query { hello: String }", Initiator: "user@example.com", } event := cmd.Event(context.Background()) require.NotNil(t, event) subGraphEvent, ok := event.(*SubGraphUpdated) require.True(t, ok) assert.Equal(t, "org-123", subGraphEvent.OrganizationId) assert.Equal(t, "main", subGraphEvent.Ref) assert.Equal(t, "users", subGraphEvent.Service) assert.Equal(t, url, *subGraphEvent.Url) assert.Equal(t, wsURL, *subGraphEvent.WSUrl) assert.Equal(t, "type Query { hello: String }", subGraphEvent.Sdl) assert.Equal(t, "user@example.com", subGraphEvent.Initiator) }