502 lines
17 KiB
TypeScript
502 lines
17 KiB
TypeScript
/**
|
|
* Comprehensive Tests for MediaResolverPort
|
|
*
|
|
* Tests cover:
|
|
* - Interface contract compliance
|
|
* - ResolutionStrategies for all reference types
|
|
* - resolveWithDefaults helper function
|
|
* - isMediaResolverPort type guard
|
|
* - Edge cases and error handling
|
|
* - Business logic decisions
|
|
*/
|
|
|
|
import { MediaReference } from '@core/domain/media/MediaReference';
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
MediaResolverPort,
|
|
ResolutionStrategies,
|
|
resolveWithDefaults,
|
|
isMediaResolverPort,
|
|
} from './MediaResolverPort';
|
|
|
|
describe('MediaResolverPort - Comprehensive Tests', () => {
|
|
describe('Interface Contract Compliance', () => {
|
|
it('should define resolve method signature correctly', () => {
|
|
// Verify the interface has the correct method signature
|
|
const testInterface: MediaResolverPort = {
|
|
resolve: async (ref: MediaReference): Promise<string | null> => {
|
|
return null;
|
|
},
|
|
};
|
|
|
|
expect(testInterface).toBeDefined();
|
|
expect(typeof testInterface.resolve).toBe('function');
|
|
});
|
|
|
|
it('should accept MediaReference and return Promise<string | null>', async () => {
|
|
const mockResolver: MediaResolverPort = {
|
|
resolve: async (ref: MediaReference): Promise<string | null> => {
|
|
// Verify ref is a MediaReference instance
|
|
expect(ref).toBeInstanceOf(MediaReference);
|
|
return '/test/path';
|
|
},
|
|
};
|
|
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = await mockResolver.resolve(ref);
|
|
|
|
expect(result).toBe('/test/path');
|
|
});
|
|
});
|
|
|
|
describe('ResolutionStrategies - System Default', () => {
|
|
it('should resolve system-default avatar without variant', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = ResolutionStrategies.systemDefault(ref);
|
|
|
|
expect(result).toBe('/media/default/neutral-default-avatar.png');
|
|
});
|
|
|
|
it('should resolve system-default avatar with male variant', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar', 'male');
|
|
const result = ResolutionStrategies.systemDefault(ref);
|
|
|
|
expect(result).toBe('/media/default/male-default-avatar.png');
|
|
});
|
|
|
|
it('should resolve system-default avatar with female variant', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar', 'female');
|
|
const result = ResolutionStrategies.systemDefault(ref);
|
|
|
|
expect(result).toBe('/media/default/female-default-avatar.png');
|
|
});
|
|
|
|
it('should resolve system-default avatar with neutral variant', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar', 'neutral');
|
|
const result = ResolutionStrategies.systemDefault(ref);
|
|
|
|
expect(result).toBe('/media/default/neutral-default-avatar.png');
|
|
});
|
|
|
|
it('should resolve system-default logo', () => {
|
|
const ref = MediaReference.createSystemDefault('logo');
|
|
const result = ResolutionStrategies.systemDefault(ref);
|
|
|
|
expect(result).toBe('/media/default/logo.png');
|
|
});
|
|
|
|
it('should return null for non-system-default reference', () => {
|
|
const ref = MediaReference.createGenerated('team-123');
|
|
const result = ResolutionStrategies.systemDefault(ref);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('ResolutionStrategies - Generated', () => {
|
|
it('should resolve generated reference for team', () => {
|
|
const ref = MediaReference.createGenerated('team-123');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBe('/media/teams/123/logo');
|
|
});
|
|
|
|
it('should resolve generated reference for league', () => {
|
|
const ref = MediaReference.createGenerated('league-456');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBe('/media/leagues/456/logo');
|
|
});
|
|
|
|
it('should resolve generated reference for driver', () => {
|
|
const ref = MediaReference.createGenerated('driver-789');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBe('/media/avatar/789');
|
|
});
|
|
|
|
it('should resolve generated reference for unknown type', () => {
|
|
const ref = MediaReference.createGenerated('unknown-999');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBe('/media/generated/unknown/999');
|
|
});
|
|
|
|
it('should return null for generated reference without generationRequestId', () => {
|
|
// Create a reference with missing generationRequestId
|
|
const ref = MediaReference.createGenerated('valid-id');
|
|
// Manually create an invalid reference
|
|
const invalidRef = { type: 'generated' } as MediaReference;
|
|
const result = ResolutionStrategies.generated(invalidRef);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null for non-generated reference', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle generated reference with special characters in ID', () => {
|
|
const ref = MediaReference.createGenerated('team-abc-123_XYZ');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBe('/media/teams/abc-123_XYZ/logo');
|
|
});
|
|
|
|
it('should handle generated reference with multiple hyphens', () => {
|
|
const ref = MediaReference.createGenerated('team-abc-def-123');
|
|
const result = ResolutionStrategies.generated(ref);
|
|
|
|
expect(result).toBe('/media/teams/abc-def-123/logo');
|
|
});
|
|
});
|
|
|
|
describe('ResolutionStrategies - Uploaded', () => {
|
|
it('should resolve uploaded reference', () => {
|
|
const ref = MediaReference.createUploaded('media-456');
|
|
const result = ResolutionStrategies.uploaded(ref);
|
|
|
|
expect(result).toBe('/media/uploaded/media-456');
|
|
});
|
|
|
|
it('should return null for uploaded reference without mediaId', () => {
|
|
// Create a reference with missing mediaId
|
|
const ref = MediaReference.createUploaded('valid-id');
|
|
// Manually create an invalid reference
|
|
const invalidRef = { type: 'uploaded' } as MediaReference;
|
|
const result = ResolutionStrategies.uploaded(invalidRef);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null for non-uploaded reference', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = ResolutionStrategies.uploaded(ref);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle uploaded reference with special characters', () => {
|
|
const ref = MediaReference.createUploaded('media-abc-123_XYZ');
|
|
const result = ResolutionStrategies.uploaded(ref);
|
|
|
|
expect(result).toBe('/media/uploaded/media-abc-123_XYZ');
|
|
});
|
|
|
|
it('should handle uploaded reference with very long ID', () => {
|
|
const longId = 'a'.repeat(1000);
|
|
const ref = MediaReference.createUploaded(longId);
|
|
const result = ResolutionStrategies.uploaded(ref);
|
|
|
|
expect(result).toBe(`/media/uploaded/${longId}`);
|
|
});
|
|
});
|
|
|
|
describe('ResolutionStrategies - None', () => {
|
|
it('should return null for none reference', () => {
|
|
const ref = MediaReference.createNone();
|
|
const result = ResolutionStrategies.none(ref);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null for any reference passed to none strategy', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = ResolutionStrategies.none(ref);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('resolveWithDefaults - Integration Tests', () => {
|
|
it('should resolve system-default reference using resolveWithDefaults', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/default/neutral-default-avatar.png');
|
|
});
|
|
|
|
it('should resolve system-default avatar with male variant using resolveWithDefaults', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar', 'male');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/default/male-default-avatar.png');
|
|
});
|
|
|
|
it('should resolve system-default logo using resolveWithDefaults', () => {
|
|
const ref = MediaReference.createSystemDefault('logo');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/default/logo.png');
|
|
});
|
|
|
|
it('should resolve generated reference using resolveWithDefaults', () => {
|
|
const ref = MediaReference.createGenerated('team-123');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/teams/123/logo');
|
|
});
|
|
|
|
it('should resolve uploaded reference using resolveWithDefaults', () => {
|
|
const ref = MediaReference.createUploaded('media-456');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/uploaded/media-456');
|
|
});
|
|
|
|
it('should resolve none reference using resolveWithDefaults', () => {
|
|
const ref = MediaReference.createNone();
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle all reference types in sequence', () => {
|
|
const refs = [
|
|
MediaReference.createSystemDefault('avatar'),
|
|
MediaReference.createSystemDefault('avatar', 'male'),
|
|
MediaReference.createSystemDefault('logo'),
|
|
MediaReference.createGenerated('team-123'),
|
|
MediaReference.createGenerated('league-456'),
|
|
MediaReference.createGenerated('driver-789'),
|
|
MediaReference.createUploaded('media-456'),
|
|
MediaReference.createNone(),
|
|
];
|
|
|
|
const results = refs.map(ref => resolveWithDefaults(ref));
|
|
|
|
expect(results).toEqual([
|
|
'/media/default/neutral-default-avatar.png',
|
|
'/media/default/male-default-avatar.png',
|
|
'/media/default/logo.png',
|
|
'/media/teams/123/logo',
|
|
'/media/leagues/456/logo',
|
|
'/media/avatar/789',
|
|
'/media/uploaded/media-456',
|
|
null,
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('isMediaResolverPort Type Guard', () => {
|
|
it('should return true for valid MediaResolverPort implementation', () => {
|
|
const validResolver: MediaResolverPort = {
|
|
resolve: async (ref: MediaReference): Promise<string | null> => {
|
|
return '/test/path';
|
|
},
|
|
};
|
|
|
|
expect(isMediaResolverPort(validResolver)).toBe(true);
|
|
});
|
|
|
|
it('should return false for null', () => {
|
|
expect(isMediaResolverPort(null)).toBe(false);
|
|
});
|
|
|
|
it('should return false for undefined', () => {
|
|
expect(isMediaResolverPort(undefined)).toBe(false);
|
|
});
|
|
|
|
it('should return false for non-object', () => {
|
|
expect(isMediaResolverPort('string')).toBe(false);
|
|
expect(isMediaResolverPort(123)).toBe(false);
|
|
expect(isMediaResolverPort(true)).toBe(false);
|
|
});
|
|
|
|
it('should return false for object without resolve method', () => {
|
|
const invalidResolver = {
|
|
someOtherMethod: () => {},
|
|
};
|
|
|
|
expect(isMediaResolverPort(invalidResolver)).toBe(false);
|
|
});
|
|
|
|
it('should return false for object with resolve property but not a function', () => {
|
|
const invalidResolver = {
|
|
resolve: 'not a function',
|
|
};
|
|
|
|
expect(isMediaResolverPort(invalidResolver)).toBe(false);
|
|
});
|
|
|
|
it('should return false for object with resolve as non-function property', () => {
|
|
const invalidResolver = {
|
|
resolve: 123,
|
|
};
|
|
|
|
expect(isMediaResolverPort(invalidResolver)).toBe(false);
|
|
});
|
|
|
|
it('should return true for object with resolve method and other properties', () => {
|
|
const validResolver = {
|
|
resolve: async (ref: MediaReference): Promise<string | null> => {
|
|
return '/test/path';
|
|
},
|
|
extraProperty: 'value',
|
|
anotherMethod: () => {},
|
|
};
|
|
|
|
expect(isMediaResolverPort(validResolver)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Business Logic Decisions', () => {
|
|
it('should make correct decision for system-default avatar without variant', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should use neutral default avatar
|
|
expect(result).toBe('/media/default/neutral-default-avatar.png');
|
|
});
|
|
|
|
it('should make correct decision for system-default avatar with specific variant', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar', 'female');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should use the specified variant
|
|
expect(result).toBe('/media/default/female-default-avatar.png');
|
|
});
|
|
|
|
it('should make correct decision for generated team reference', () => {
|
|
const ref = MediaReference.createGenerated('team-123');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should resolve to team logo path
|
|
expect(result).toBe('/media/teams/123/logo');
|
|
});
|
|
|
|
it('should make correct decision for generated league reference', () => {
|
|
const ref = MediaReference.createGenerated('league-456');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should resolve to league logo path
|
|
expect(result).toBe('/media/leagues/456/logo');
|
|
});
|
|
|
|
it('should make correct decision for generated driver reference', () => {
|
|
const ref = MediaReference.createGenerated('driver-789');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should resolve to avatar path
|
|
expect(result).toBe('/media/avatar/789');
|
|
});
|
|
|
|
it('should make correct decision for uploaded reference', () => {
|
|
const ref = MediaReference.createUploaded('media-456');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should resolve to uploaded media path
|
|
expect(result).toBe('/media/uploaded/media-456');
|
|
});
|
|
|
|
it('should make correct decision for none reference', () => {
|
|
const ref = MediaReference.createNone();
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should return null (no media)
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should make correct decision for unknown generated type', () => {
|
|
const ref = MediaReference.createGenerated('unknown-999');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Decision: Should fall back to generic generated path
|
|
expect(result).toBe('/media/generated/unknown/999');
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases and Error Handling', () => {
|
|
it('should handle empty string IDs gracefully', () => {
|
|
// MediaReference factory methods throw on empty strings
|
|
// This tests that the strategies handle invalid refs gracefully
|
|
const invalidRef = { type: 'generated' } as MediaReference;
|
|
const result = ResolutionStrategies.generated(invalidRef);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle references with missing properties', () => {
|
|
const invalidRef = { type: 'uploaded' } as MediaReference;
|
|
const result = ResolutionStrategies.uploaded(invalidRef);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should handle very long IDs without performance issues', () => {
|
|
const longId = 'a'.repeat(10000);
|
|
const ref = MediaReference.createUploaded(longId);
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe(`/media/uploaded/${longId}`);
|
|
});
|
|
|
|
it('should handle Unicode characters in IDs', () => {
|
|
const ref = MediaReference.createUploaded('media-日本語-123');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/uploaded/media-日本語-123');
|
|
});
|
|
|
|
it('should handle special characters in generated IDs', () => {
|
|
const ref = MediaReference.createGenerated('team-abc_def-123');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
expect(result).toBe('/media/teams/abc_def-123/logo');
|
|
});
|
|
});
|
|
|
|
describe('Path Format Consistency', () => {
|
|
it('should maintain consistent path format for system-default', () => {
|
|
const ref = MediaReference.createSystemDefault('avatar');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Should start with /media/default/
|
|
expect(result).toMatch(/^\/media\/default\//);
|
|
});
|
|
|
|
it('should maintain consistent path format for generated team', () => {
|
|
const ref = MediaReference.createGenerated('team-123');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Should start with /media/teams/
|
|
expect(result).toMatch(/^\/media\/teams\//);
|
|
});
|
|
|
|
it('should maintain consistent path format for generated league', () => {
|
|
const ref = MediaReference.createGenerated('league-456');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Should start with /media/leagues/
|
|
expect(result).toMatch(/^\/media\/leagues\//);
|
|
});
|
|
|
|
it('should maintain consistent path format for generated driver', () => {
|
|
const ref = MediaReference.createGenerated('driver-789');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Should start with /media/avatar/
|
|
expect(result).toMatch(/^\/media\/avatar\//);
|
|
});
|
|
|
|
it('should maintain consistent path format for uploaded', () => {
|
|
const ref = MediaReference.createUploaded('media-456');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Should start with /media/uploaded/
|
|
expect(result).toMatch(/^\/media\/uploaded\//);
|
|
});
|
|
|
|
it('should maintain consistent path format for unknown generated type', () => {
|
|
const ref = MediaReference.createGenerated('unknown-999');
|
|
const result = resolveWithDefaults(ref);
|
|
|
|
// Should start with /media/generated/
|
|
expect(result).toMatch(/^\/media\/generated\//);
|
|
});
|
|
});
|
|
});
|