Spaces:
Running
Running
Download source/teaql-ts/tests/client.test.ts from teaql/browser-sqlite-playground: direct link, hf CLI and curl.
- Browser
- Download file 13 kB
-
https://huggingface.co/spaces/teaql/browser-sqlite-playground/resolve/main/source/teaql-ts/tests/client.test.ts
- Command line
-
hf download hf://spaces/teaql/browser-sqlite-playground/source/teaql-ts/tests/client.test.ts
-
curl -L -o client.test.ts https://huggingface.co/spaces/teaql/browser-sqlite-playground/resolve/main/source/teaql-ts/tests/client.test.ts
13 kB
| import { TeaQLClient } from '../src/tfp/client'; | |
| import { SelectQuery, SortDirection, OrderBy, MutationQuery } from '../src/core/ast'; | |
| import { RuntimeOperation, RuntimeTelemetry, RuntimeTelemetryScope } from '../src/core/telemetry'; | |
| import { SmartList } from '../src/core/smart-list'; | |
| import { | |
| DelegatingMutationPolicyApprovalProvider, | |
| DelegatingMutationPolicyRegistry, | |
| MutationPolicyError, | |
| } from '../src/core/mutation-policy'; | |
| import { UserContext } from '../src/core/context'; | |
| class TfpRecordingTelemetry implements RuntimeTelemetry { | |
| readonly events: Array<{ operation: RuntimeOperation; outcome?: string; cardinality?: number }> = []; | |
| start(operation: RuntimeOperation): RuntimeTelemetryScope { | |
| const event = { operation } as { operation: RuntimeOperation; outcome?: string; cardinality?: number }; | |
| this.events.push(event); | |
| return { | |
| success: completion => { | |
| event.outcome = 'success'; | |
| event.cardinality = completion?.attributes?.['teaql.result.cardinality'] as number | undefined; | |
| }, | |
| failure: () => { event.outcome = 'failure'; }, | |
| }; | |
| } | |
| } | |
| // Mock fetch globally | |
| global.fetch = jest.fn(); | |
| const federalQuery = (entity: string) => new SelectQuery(entity) | |
| .comment('conformance query').purpose('conformance test'); | |
| describe('TeaQLClient Backend/Node.js Tests', () => { | |
| beforeEach(() => { | |
| (global.fetch as jest.Mock).mockClear(); | |
| }); | |
| it('should serialize SelectQuery correctly and send via fetch', async () => { | |
| // 1. Arrange: Setup mock response | |
| const mockData = [{ id: 1, name: "Test User" }]; | |
| (global.fetch as jest.Mock).mockResolvedValue({ | |
| ok: true, | |
| json: async () => ({ data: mockData }) | |
| }); | |
| // 2. Arrange: Create client and query | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const query = federalQuery("User") | |
| .filter({ "name": { "$eq": "Test User" } }) | |
| .order(OrderBy.desc("id")) | |
| .limit(10); | |
| // 3. Act: Execute query | |
| const result = await client.executeQuery(query); | |
| // 4. Assert: Check response and fetch payload | |
| expect(result).toBeInstanceOf(SmartList); | |
| expect(Array.from(result)).toEqual(mockData); | |
| expect(global.fetch).toHaveBeenCalledTimes(1); | |
| // Check fetch arguments | |
| const [url, options] = (global.fetch as jest.Mock).mock.calls[0]; | |
| expect(url).toBe('http://localhost:8080/api/query'); | |
| expect(options.method).toBe('POST'); | |
| expect(options.headers['Content-Type']).toBe('application/json'); | |
| // Ensure the query was correctly serialized to JSON | |
| const body = JSON.parse(options.body); | |
| expect(body.entity).toBe("User"); | |
| expect(body.limitValue).toBe(10); | |
| expect(body.filterCondition.name.$eq).toBe("Test User"); | |
| expect(body.orderItems[0].field).toBe("id"); | |
| expect(body.orderItems[0].direction).toBe(SortDirection.Desc); | |
| }); | |
| it('serializes canonical facets and restores SmartList facet metadata', async () => { | |
| (global.fetch as jest.Mock).mockResolvedValue({ | |
| ok: true, | |
| json: async () => ({ data: [], facets: { statusCount: [{ code: 'NEW', count: 5 }] } }) | |
| }); | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const query = federalQuery("Task"); | |
| const facetQuery = federalQuery("TaskStatus") | |
| .select(['id', 'code']) | |
| .aggregate("Count", "id", "count"); | |
| query.facetBy("statusCount", "status", { toQuery: () => facetQuery }); | |
| const result = await client.executeQuery(query); | |
| expect(global.fetch).toHaveBeenCalledTimes(1); | |
| const body = JSON.parse((global.fetch as jest.Mock).mock.calls[0][1].body); | |
| expect(body.facets[0].facetName).toBe('statusCount'); | |
| expect(body.facets[0].relationName).toBe('status'); | |
| expect(body.facets[0].query.entity).toBe('TaskStatus'); | |
| expect(result.facet('statusCount')?.[0]).toEqual({ code: 'NEW', count: 5 }); | |
| }); | |
| it('should support mutations and correctly serialize them to /mutate', async () => { | |
| const mockResponse = { success: true, saved_data: [{ id: 1, name: "Created" }] }; | |
| (global.fetch as jest.Mock).mockResolvedValue({ | |
| ok: true, | |
| json: async () => mockResponse | |
| }); | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const mutation = new MutationQuery("Task", "Create", { name: "Created" }, undefined, "create task"); | |
| const result = await client.executeMutation(mutation); | |
| expect(result).toEqual(mockResponse); | |
| expect(global.fetch).toHaveBeenCalledTimes(1); | |
| const [url, options] = (global.fetch as jest.Mock).mock.calls[0]; | |
| expect(url).toBe('http://localhost:8080/api/mutate'); | |
| expect(options.method).toBe('POST'); | |
| const body = JSON.parse(options.body); | |
| expect(body.entity).toBe("Task"); | |
| expect(body.action).toBe("Create"); | |
| expect(body.payload.name).toBe("Created"); | |
| expect(body.comment).toBe("create task"); | |
| }); | |
| it('applies client-profile Mutation Policy before issuing a TFP mutation', async () => { | |
| const context = new UserContext().withMutationPolicyRegistry( | |
| new DelegatingMutationPolicyRegistry(() => ({ | |
| identity: { policyId: 'client-demo', version: '1', fingerprint: 'sha256:client' }, | |
| review: () => ({ verdict: 'deny', code: 'CLIENT_DENIED', message: 'disabled' }), | |
| })), | |
| ); | |
| const client = new TeaQLClient({ | |
| baseUrl: 'http://localhost:8080/api', userContext: context, | |
| }); | |
| await expect(client.executeMutation( | |
| new MutationQuery('Task', 'Create', { name: 'blocked' }, undefined, 'demo'), | |
| )).rejects.toThrow(MutationPolicyError); | |
| expect(global.fetch).not.toHaveBeenCalled(); | |
| }); | |
| it('retains approved client-profile governance without serializing it', async () => { | |
| (global.fetch as jest.Mock).mockResolvedValue({ | |
| ok: true, json: async () => ({ success: true }), | |
| }); | |
| const identity = { policyId: 'client-demo', version: '1', fingerprint: 'sha256:client' }; | |
| const context = new UserContext() | |
| .withMutationPolicyRegistry(new DelegatingMutationPolicyRegistry(() => ({ | |
| identity, review: () => ({ verdict: 'allow' }), | |
| }))) | |
| .withMutationPolicyApprovalProvider(new DelegatingMutationPolicyApprovalProvider( | |
| policy => ({ policy, approvedBy: 'demo-owner', approvedAt: new Date() }), | |
| )); | |
| const client = new TeaQLClient({ | |
| baseUrl: 'http://localhost:8080/api', userContext: context, | |
| }); | |
| await client.executeMutation( | |
| new MutationQuery('Task', 'Create', { name: 'allowed' }, undefined, 'demo'), | |
| ); | |
| expect(client.mutationGovernanceTrace[0].approvalStatus).toBe('approved'); | |
| expect((global.fetch as jest.Mock).mock.calls[0][1].body) | |
| .not.toContain('mutationGovernance'); | |
| }); | |
| it('records balanced TFP client query and mutation lifecycles', async () => { | |
| const telemetry = new TfpRecordingTelemetry(); | |
| (global.fetch as jest.Mock) | |
| .mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 1 }] }) }) | |
| .mockResolvedValueOnce({ ok: true, json: async () => ({ success: true }) }); | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api', runtimeTelemetry: telemetry }); | |
| await client.executeQuery(federalQuery('Task')); | |
| await client.executeMutation(new MutationQuery('Task', 'Create', {}, undefined, 'test')); | |
| expect(telemetry.events.map(event => [ | |
| event.operation.family, | |
| event.operation.name, | |
| event.operation.attributes?.['teaql.tfp.role'], | |
| event.outcome, | |
| ])).toEqual([ | |
| ['tfp', 'client.query', 'client', 'success'], | |
| ['tfp', 'client.mutation', 'client', 'success'], | |
| ]); | |
| expect(telemetry.events[0].cardinality).toBe(1); | |
| }); | |
| it('records TFP transport failure and rethrows the original error', async () => { | |
| const telemetry = new TfpRecordingTelemetry(); | |
| const transportError = new Error('network unavailable'); | |
| (global.fetch as jest.Mock).mockRejectedValue(transportError); | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }) | |
| .setRuntimeTelemetry(telemetry); | |
| await expect(client.executeQuery(federalQuery('Task'))).rejects.toBe(transportError); | |
| expect(telemetry.events).toHaveLength(1); | |
| expect(telemetry.events[0].outcome).toBe('failure'); | |
| }); | |
| it('injects trace metadata inside the TFP client operation without touching payload', async () => { | |
| const telemetry = new TfpRecordingTelemetry() as TfpRecordingTelemetry & RuntimeTelemetry; | |
| telemetry.inject = carrier => { carrier.traceparent = '00-trace-span-01'; }; | |
| (global.fetch as jest.Mock).mockResolvedValue({ | |
| ok: true, json: async () => ({ data: [] }), | |
| }); | |
| const client = new TeaQLClient({ | |
| baseUrl: 'http://localhost:8080/api', runtimeTelemetry: telemetry, | |
| }); | |
| await client.executeQuery(federalQuery('Task')); | |
| const [, options] = (global.fetch as jest.Mock).mock.calls[0]; | |
| expect(options.headers.traceparent).toBe('00-trace-span-01'); | |
| expect(options.body).not.toContain('traceparent'); | |
| }); | |
| it('keeps TFP requests working when propagation injection fails', async () => { | |
| const telemetry = new TfpRecordingTelemetry() as TfpRecordingTelemetry & RuntimeTelemetry; | |
| telemetry.inject = () => { throw new Error('propagator unavailable'); }; | |
| (global.fetch as jest.Mock).mockResolvedValue({ | |
| ok: true, json: async () => ({ data: [] }), | |
| }); | |
| await new TeaQLClient({ | |
| baseUrl: 'http://localhost:8080/api', runtimeTelemetry: telemetry, | |
| }).executeQuery(federalQuery('Task')); | |
| expect(global.fetch).toHaveBeenCalledTimes(1); | |
| }); | |
| it('uses trusted authentication headers for mutations without serializing context', async () => { | |
| (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: async () => ({ success: true }) }); | |
| const client = new TeaQLClient({ | |
| baseUrl: 'http://localhost:8080/api/', | |
| getHeaders: async () => ({ Authorization: 'Bearer trusted-session' }), | |
| }); | |
| const mutation = new MutationQuery('Task', 'Update', { id: 1 }, 2, 'update task'); | |
| await client.executeMutation(mutation); | |
| const [url, options] = (global.fetch as jest.Mock).mock.calls[0]; | |
| expect(url).toBe('http://localhost:8080/api/mutate'); | |
| expect(options.headers.Authorization).toBe('Bearer trusted-session'); | |
| expect(options.body).not.toContain('trusted-session'); | |
| }); | |
| it('rejects streaming over the ordinary federation protocol without issuing HTTP', async () => { | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const consume = async () => { | |
| for await (const _chunk of client.executeForStream(federalQuery('Task'))) { /* consume */ } | |
| }; | |
| await expect(consume()).rejects.toThrow(/dedicated streaming protocol/); | |
| expect(global.fetch).not.toHaveBeenCalled(); | |
| }); | |
| it.each(['hardLimit', 'hard_limit', 'hard_limit_value']) ( | |
| 'rejects remote hard-limit injection through %s without issuing HTTP', | |
| async (field) => { | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const query = federalQuery('Order') as SelectQuery & Record<string, unknown>; | |
| query[field] = 20_000; | |
| await expect(client.executeQuery(query)).rejects.toThrow('TFP_FORBIDDEN_FIELD'); | |
| expect(global.fetch).not.toHaveBeenCalled(); | |
| }, | |
| ); | |
| it.each(['continuousPageFetch', 'continuous_page_fetch', 'continuousPageRuntime']) ( | |
| 'rejects remote local cursor policy field %s without issuing HTTP', | |
| async (field) => { | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const query = federalQuery('Order') as SelectQuery & Record<string, unknown>; | |
| query[field] = { namespace: 'attacker', ttlSeconds: 999 }; | |
| await expect(client.executeQuery(query)).rejects.toThrow('TFP_FORBIDDEN_FIELD'); | |
| expect(global.fetch).not.toHaveBeenCalled(); | |
| }, | |
| ); | |
| it.each(['idSetPagination', 'id_set_pagination', 'paginationWithIdSet']) ( | |
| 'rejects remote ID-set retention policy field %s without issuing HTTP', | |
| async (field) => { | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const query = federalQuery('Order') as SelectQuery & Record<string, unknown>; | |
| query[field] = { namespace: 'attacker', ttlSeconds: 999, maxIds: 9_999_999 }; | |
| await expect(client.executeQuery(query)).rejects.toThrow('TFP_FORBIDDEN_FIELD'); | |
| expect(global.fetch).not.toHaveBeenCalled(); | |
| }, | |
| ); | |
| it('rejects nested remote hard-limit injection without issuing HTTP', async () => { | |
| const client = new TeaQLClient({ baseUrl: 'http://localhost:8080/api' }); | |
| const nested = new SelectQuery('OrderLine') as SelectQuery & Record<string, unknown>; | |
| nested.hard_limit = 20_000; | |
| const query = federalQuery('Order').relationQuery('lines', nested); | |
| await expect(client.executeQuery(query)).rejects.toThrow('TFP_FORBIDDEN_FIELD'); | |
| expect(global.fetch).not.toHaveBeenCalled(); | |
| }); | |
| }); | |