1import { describe, expect, it } from 'vitest';2import { LIMITS } from '@tribes/protocol';3import { baseStages, createValidator, encodingStage, sizeStage } from '../src/validate';4import type { CompileFn } from '../src/validate';56const okCompile: CompileFn = () => ({ ok: true, error: null });7const failCompile: CompileFn = () => ({ ok: false, error: 'SyntaxError: unexpected token' });89describe('validation pipeline', () => {10 it('passes a normal script through all base stages', async () => {11 const validate = createValidator(baseStages(okCompile));12 expect(await validate('function tick(state, api) { return []; }')).toEqual({13 ok: true,14 failedStage: null,15 detail: null,16 });17 });1819 it('rejects oversized scripts at the size stage (byte-accurate)', async () => {20 const validate = createValidator(baseStages(okCompile));21 // é is 2 UTF-8 bytes: half the limit in chars exceeds it in bytes.22 const big = 'é'.repeat(LIMITS.SCRIPT_MAX_BYTES / 2 + 1);23 const report = await validate(big);24 expect(report.ok).toBe(false);25 expect(report.failedStage).toBe('size');26 });2728 it('accepts a script exactly at the byte limit', () => {29 expect(sizeStage.run('a'.repeat(LIMITS.SCRIPT_MAX_BYTES))).toBeNull();30 expect(sizeStage.run('a'.repeat(LIMITS.SCRIPT_MAX_BYTES + 1))).not.toBeNull();31 });3233 it.each(['\u202A', '\u202E', '\u2066', '\u2069', '\u200E', '\u200F'])(34 'rejects bidi/control character %s at the encoding stage',35 async (ch) => {36 const validate = createValidator(baseStages(okCompile));37 const report = await validate(`// evil ${ch} comment\nfunction tick() {}`);38 expect(report.ok).toBe(false);39 expect(report.failedStage).toBe('encoding');40 expect(report.detail).toMatch(/U\+20/);41 },42 );4344 it('rejects lone surrogates as invalid UTF-8', () => {45 expect(encodingStage.run('bad \uD800 alone')).toMatch(/lone surrogate/);46 expect(encodingStage.run('bad \uDFFF alone')).toMatch(/lone surrogate/);47 expect(encodingStage.run('fine 😀 pair')).toBeNull(); // valid emoji pair48 });4950 it('surfaces compile errors at the syntax stage', async () => {51 const validate = createValidator(baseStages(failCompile));52 const report = await validate('function tick( {');53 expect(report).toEqual({54 ok: false,55 failedStage: 'syntax',56 detail: 'SyntaxError: unexpected token',57 });58 });5960 it('runs stages in order — size failure wins over syntax failure', async () => {61 const validate = createValidator(baseStages(failCompile));62 const report = await validate('a'.repeat(LIMITS.SCRIPT_MAX_BYTES + 1));63 expect(report.failedStage).toBe('size');64 });6566 it('supports appending a smoke stage (integration contract)', async () => {67 const smoke = { name: 'smoke', run: () => 'crashed on tick 1' };68 const validate = createValidator([...baseStages(okCompile), smoke]);69 const report = await validate('function tick() {}');70 expect(report).toEqual({ ok: false, failedStage: 'smoke', detail: 'crashed on tick 1' });71 });72});