test: add validation and event tests for organization and API key

Adds unit tests for the `AddOrganization` and `AddAPIKey` commands. 
These tests validate various scenarios, including success cases, 
handling of already existing organizations or keys, and ensuring 
required fields are checked. The
changes enhance test coverage and ensure robustness of the command 
logic.
This commit is contained in:
2025-11-21 12:49:23 +01:00
parent 06aeedc3b0
commit 862060875b
3 changed files with 825 additions and 0 deletions
+1
View File
@@ -3,6 +3,7 @@
.testCoverage.txt
.testCoverage.txt.tmp
coverage.html
coverage.out
/exported
/release
/schemactl
+390
View File
@@ -7,10 +7,68 @@ import (
"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
@@ -74,3 +132,335 @@ func TestAddAPIKey_Event(t *testing.T) {
})
}
}
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)
}
+434
View File
@@ -0,0 +1,434 @@
package sdlmerge
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMergeSDLs_Success(t *testing.T) {
// Both types need to be in the same subgraph or properly federated
sdl1 := `
type User {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
}
`
result, err := MergeSDLs(sdl1)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "Post")
assert.Contains(t, result, "id")
assert.Contains(t, result, "name")
assert.Contains(t, result, "title")
}
func TestMergeSDLs_SingleSchema(t *testing.T) {
sdl := `
type Query {
hello: String
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Query")
assert.Contains(t, result, "hello")
}
func TestMergeSDLs_EmptySchemas(t *testing.T) {
result, err := MergeSDLs()
require.NoError(t, err)
// With no schemas, result will be empty after processing
// This is valid - just verifies no crash
_ = result
}
func TestMergeSDLs_InvalidSyntax(t *testing.T) {
invalidSDL := `
type User {
id: ID!
name: String!
// Missing closing brace
`
_, err := MergeSDLs(invalidSDL)
require.Error(t, err)
assert.Contains(t, err.Error(), "parse graphql document string")
}
func TestMergeSDLs_UnknownType(t *testing.T) {
sdl := `
type User {
id: ID!
profile: UnknownType
}
`
_, err := MergeSDLs(sdl)
require.Error(t, err)
assert.Contains(t, err.Error(), "validate schema")
}
func TestMergeSDLs_DuplicateTypes_DifferentFields(t *testing.T) {
// Same type with different fields in different subgraphs - should fail
// In federation, shared types must be identical
sdl1 := `
type User {
id: ID!
}
`
sdl2 := `
type User {
name: String!
}
`
_, err := MergeSDLs(sdl1, sdl2)
require.Error(t, err)
assert.Contains(t, err.Error(), "shared type")
}
func TestMergeSDLs_ExtendType(t *testing.T) {
sdl1 := `
type User {
id: ID!
}
`
sdl2 := `
extend type User {
email: String!
}
`
result, err := MergeSDLs(sdl1, sdl2)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "id")
assert.Contains(t, result, "email")
}
func TestMergeSDLs_Scalars(t *testing.T) {
sdl := `
scalar DateTime
type Event {
id: ID!
createdAt: DateTime!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "DateTime")
assert.Contains(t, result, "Event")
}
func TestMergeSDLs_Enums(t *testing.T) {
sdl := `
enum Role {
ADMIN
USER
GUEST
}
type User {
id: ID!
role: Role!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Role")
assert.Contains(t, result, "ADMIN")
assert.Contains(t, result, "USER")
}
func TestMergeSDLs_Interfaces(t *testing.T) {
sdl := `
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Node")
assert.Contains(t, result, "implements")
}
func TestMergeSDLs_Unions(t *testing.T) {
sdl := `
type User {
id: ID!
name: String!
}
type Bot {
id: ID!
version: String!
}
union Actor = User | Bot
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "Actor")
assert.Contains(t, result, "User")
assert.Contains(t, result, "Bot")
}
func TestMergeSDLs_InputTypes(t *testing.T) {
sdl := `
input CreateUserInput {
name: String!
email: String!
}
type Mutation {
createUser(input: CreateUserInput!): User
}
type User {
id: ID!
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "CreateUserInput")
assert.Contains(t, result, "createUser")
}
func TestMergeSDLs_Directives(t *testing.T) {
sdl := `
type User {
id: ID!
name: String! @deprecated(reason: "Use fullName instead")
fullName: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "name")
assert.Contains(t, result, "fullName")
}
func TestMergeSDLs_FederationKeys(t *testing.T) {
// Federation @key directive
sdl := `
type User @key(fields: "id") {
id: ID!
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
// @key directive should be removed during merge
assert.NotContains(t, result, "@key")
}
func TestMergeSDLs_ExternalFields(t *testing.T) {
// Federation @external directive
sdl1 := `
type User @key(fields: "id") {
id: ID!
name: String!
}
`
sdl2 := `
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post {
id: ID!
title: String!
}
`
result, err := MergeSDLs(sdl1, sdl2)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "Post")
// @external fields should be removed
assert.NotContains(t, result, "@external")
}
func TestMergeSDLs_ComplexSchema(t *testing.T) {
// Multiple subgraphs with various types - simplified to avoid cross-references
users := `
type User @key(fields: "id") {
id: ID!
username: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
`
posts := `
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post @key(fields: "id") {
id: ID!
title: String!
content: String!
}
extend type Query {
post(id: ID!): Post
posts: [Post!]!
}
`
comments := `
extend type Post @key(fields: "id") {
id: ID! @external
comments: [Comment!]!
}
type Comment {
id: ID!
text: String!
}
extend type Query {
comment(id: ID!): Comment
}
`
result, err := MergeSDLs(users, posts, comments)
require.NoError(t, err)
// Verify all types are present
assert.Contains(t, result, "User")
assert.Contains(t, result, "Post")
assert.Contains(t, result, "Comment")
assert.Contains(t, result, "Query")
// Verify fields from all subgraphs
assert.Contains(t, result, "username")
assert.Contains(t, result, "posts")
assert.Contains(t, result, "comments")
}
func TestMergeSDLs_EmptyTypeDefinition(t *testing.T) {
sdl := `
type Empty {}
`
_, err := MergeSDLs(sdl)
require.Error(t, err)
// Empty types are invalid in GraphQL
assert.Contains(t, err.Error(), "empty body")
}
func TestMergeSDLs_MultipleValidationErrors(t *testing.T) {
// Schema with multiple errors
sdl := `
type User {
id: ID!
profile: NonExistentType1
settings: NonExistentType2
}
`
_, err := MergeSDLs(sdl)
require.Error(t, err)
}
func TestMergeSDLs_ListTypes(t *testing.T) {
sdl := `
type User {
id: ID!
tags: [String!]!
friends: [User!]
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "tags")
assert.Contains(t, result, "friends")
}
func TestMergeSDLs_NonNullTypes(t *testing.T) {
sdl := `
type User {
id: ID!
name: String!
email: String
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
assert.Contains(t, result, "id")
assert.Contains(t, result, "name")
assert.Contains(t, result, "email")
}
func TestMergeSDLs_Comments(t *testing.T) {
sdl := `
# This is a user type
type User {
# User ID
id: ID!
# User name
name: String!
}
`
result, err := MergeSDLs(sdl)
require.NoError(t, err)
assert.Contains(t, result, "User")
}
func TestMergeSDLs_LargeSchema(t *testing.T) {
// Test with a reasonably large schema to ensure performance
var sdlBuilder strings.Builder
for i := 0; i < 50; i++ {
sdlBuilder.WriteString("type Type")
sdlBuilder.WriteString(strings.Repeat(string(rune('A'+i%26)), 1))
sdlBuilder.WriteString(string(rune('0' + i/26)))
sdlBuilder.WriteString(" { id: ID }\n")
}
result, err := MergeSDLs(sdlBuilder.String())
require.NoError(t, err)
// Verify some types are present
assert.Contains(t, result, "TypeA0")
assert.Contains(t, result, "TypeB0")
assert.Contains(t, result, "TypeC0")
}