import { describe, it, expect } from 'vitest' import { validate } from '../validate.js' import { generate } from '../generate.js' import { BINARY_TYPE_DENYLIST } from '../types.js' import type { ScaffoldEntityInput, EntityField } from '../types.js' function field(name: string, over: Partial = {}): EntityField { return { name, type: 'string', required: true, isKey: false, indexed: false, ...over } } function fixture(overrides: Partial = {}): Partial { return { name: 'InteractionDocument', pluralName: 'InteractionDocuments', module: 'interactions', appCode: 'TestV2', applicationCode: 'crm', domainPrefix: 'crm', namespace: 'TestV2', tenantMode: 'strict', schemaTarget: 'extensions', fields: [field('FileName', { maxLength: 256 })], relations: [], constraints: [], projectPath: '/tmp/project', ...overrides, } } describe('scaffold-entity / binary-content guard — file content never goes in the DB', () => { it.each([...BINARY_TYPE_DENYLIST].map(t => [t]))( 'validate rejects a field of type "%s" with the IFileStorageService steer', (type) => { const r = validate(fixture({ fields: [field('FileName'), field('Content', { type })] })) expect(r.valid).toBe(false) const msg = r.errors.join('\n') expect(msg).toContain(`type "${type}" is not scaffoldable`) expect(msg).toContain('IFileStorageService') expect(msg).toContain('references/file-storage.md') }, ) it('validate rejects uppercase variants too (Varbinary)', () => { const r = validate(fixture({ fields: [field('Content', { type: 'Varbinary' })] })) expect(r.valid).toBe(false) expect(r.errors.join('\n')).toContain('IFileStorageService') }) it('the metadata shape itself passes (the entity is legitimate)', () => { const r = validate(fixture({ fields: [ field('FileName', { maxLength: 256 }), field('StoredFileName', { maxLength: 500, indexed: true }), field('ContentType', { maxLength: 100 }), field('FileSizeBytes', { type: 'long' }), ], })) expect(r.valid).toBe(true) expect(r.errors).toEqual([]) }) it('generate throws defensively when validate was skipped', () => { const spec = fixture({ fields: [field('Content', { type: 'binary' })] }) as ScaffoldEntityInput expect(() => generate(spec)).toThrowError(/IFileStorageService/) }) it('raw C# passthrough types stay accepted (the fallback survives the denylist)', () => { const r = validate(fixture({ fields: [field('Duration', { type: 'TimeSpan' })] })) expect(r.valid).toBe(true) }) })