@codeforbreakfast/eventsourcing-commands
Version:
Wire command validation and dispatch for event sourcing systems - External boundary layer with schema validation
165 lines (145 loc) • 4.77 kB
text/typescript
import { describe, test, expect } from '@codeforbreakfast/buntest';
import { Schema, Effect, pipe, Either } from 'effect';
import { WireCommand, CommandResult, validateCommand, CommandValidationError } from './commands';
describe('Wire Commands', () => {
describe('WireCommand Schema', () => {
test('should validate a valid wire command', () => {
const validCommand = {
id: 'cmd-123',
target: 'user-456',
name: 'CreateUser',
payload: { email: 'test@example.com' },
};
expect(Either.isRight(pipe(validCommand, Schema.decodeUnknownEither(WireCommand)))).toBe(
true
);
});
test('should reject invalid wire command', () => {
const invalidCommand = {
id: 'cmd-123',
// missing target
name: 'CreateUser',
payload: { email: 'test@example.com' },
};
expect(Either.isLeft(pipe(invalidCommand, Schema.decodeUnknownEither(WireCommand)))).toBe(
true
);
});
test('should accept unknown payload types', () => {
const commandWithComplexPayload = {
id: 'cmd-123',
target: 'user-456',
name: 'CreateUser',
payload: {
nested: { data: 'value' },
array: [1, 2, 3],
boolean: true,
},
};
expect(
Either.isRight(pipe(commandWithComplexPayload, Schema.decodeUnknownEither(WireCommand)))
).toBe(true);
});
});
describe('CommandResult Schema', () => {
test('should validate success result', () => {
const successResult = {
_tag: 'Success',
position: {
streamId: 'user-456',
eventNumber: 1,
},
};
expect(Either.isRight(pipe(successResult, Schema.decodeUnknownEither(CommandResult)))).toBe(
true
);
});
test('should validate validation error', () => {
const failureResult = {
_tag: 'Failure',
error: {
_tag: 'ValidationError',
commandId: 'cmd-123',
commandName: 'CreateUser',
validationErrors: ['Email is required', 'Name must be at least 1 character'],
},
};
expect(Either.isRight(pipe(failureResult, Schema.decodeUnknownEither(CommandResult)))).toBe(
true
);
});
test('should validate handler not found error', () => {
const failureResult = {
_tag: 'Failure',
error: {
_tag: 'HandlerNotFound',
commandId: 'cmd-123',
commandName: 'UnknownCommand',
availableHandlers: ['CreateUser', 'UpdateUser'],
},
};
expect(Either.isRight(pipe(failureResult, Schema.decodeUnknownEither(CommandResult)))).toBe(
true
);
});
});
describe('Command Validation', () => {
const UserPayload = Schema.Struct({
email: pipe(Schema.String, Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
name: pipe(Schema.String, Schema.minLength(1)),
age: Schema.optional(pipe(Schema.Number, Schema.between(13, 120))),
});
test('should validate and transform valid payload', async () => {
const wireCommand: WireCommand = {
id: 'cmd-123',
target: 'user-456',
name: 'CreateUser',
payload: {
email: 'test@example.com',
name: 'John Doe',
age: 30,
},
};
const result = await pipe(wireCommand, validateCommand(UserPayload), Effect.runPromise);
expect(result.id).toBe('cmd-123');
expect(result.target).toBe('user-456');
expect(result.name).toBe('CreateUser');
expect(result.payload.email).toBe('test@example.com');
expect(result.payload.name).toBe('John Doe');
expect(result.payload.age).toBe(30);
});
test('should fail validation for invalid payload', async () => {
const wireCommand: WireCommand = {
id: 'cmd-123',
target: 'user-456',
name: 'CreateUser',
payload: {
email: 'invalid-email',
name: '',
age: 150,
},
};
const result = await pipe(
wireCommand,
validateCommand(UserPayload),
Effect.either,
Effect.runPromise
);
expect(Either.isLeft(result)).toBe(true);
pipe(
result,
Either.match({
onLeft: (validationError) => {
expect(validationError).toBeInstanceOf(CommandValidationError);
expect(validationError.commandId).toBe('cmd-123');
expect(validationError.commandName).toBe('CreateUser');
expect(validationError.validationErrors.length).toBeGreaterThan(0);
},
onRight: () => {
// Should not reach here
},
})
);
});
});
});