d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
240 lines
6.8 KiB
TypeScript
240 lines
6.8 KiB
TypeScript
/**
|
|
* @vitest-environment node
|
|
*/
|
|
import { describe, expect, it } from 'vitest'
|
|
import { textractAnalyzeExpenseTool } from '@/tools/textract/analyze-expense'
|
|
import { textractAnalyzeIdTool } from '@/tools/textract/analyze-id'
|
|
import { textractParserTool, textractParserV2Tool } from '@/tools/textract/parser'
|
|
|
|
const respond = (body: unknown) => new Response(JSON.stringify(body))
|
|
|
|
describe('textract_parser', () => {
|
|
const body = textractParserTool.request.body!
|
|
|
|
it('builds a sync body from filePath', () => {
|
|
expect(
|
|
body({
|
|
accessKeyId: ' key ',
|
|
secretAccessKey: ' secret ',
|
|
region: ' us-east-1 ',
|
|
filePath: ' https://example.com/doc.pdf ',
|
|
featureTypes: ['TABLES'],
|
|
} as never)
|
|
).toMatchObject({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
processingMode: 'sync',
|
|
filePath: 'https://example.com/doc.pdf',
|
|
featureTypes: ['TABLES'],
|
|
})
|
|
})
|
|
|
|
it('requires s3Uri for async mode', () => {
|
|
expect(() =>
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
processingMode: 'async',
|
|
} as never)
|
|
).not.toThrow()
|
|
expect(
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
processingMode: 'async',
|
|
s3Uri: 's3://bucket/key.pdf',
|
|
} as never)
|
|
).toMatchObject({ processingMode: 'async', s3Uri: 's3://bucket/key.pdf' })
|
|
})
|
|
|
|
it('throws when no document is provided for sync mode', () => {
|
|
expect(() =>
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
} as never)
|
|
).toThrow('Document is required for single-page processing')
|
|
})
|
|
|
|
it('normalizes the documented response shape', async () => {
|
|
const result = await textractParserTool.transformResponse!(
|
|
respond({
|
|
success: true,
|
|
output: {
|
|
blocks: [{ BlockType: 'LINE', Id: '1', Text: 'Hello' }],
|
|
documentMetadata: { pages: 2 },
|
|
modelVersion: '1.0',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.output.blocks).toHaveLength(1)
|
|
expect(result.output.documentMetadata.pages).toBe(2)
|
|
expect(result.output.modelVersion).toBe('1.0')
|
|
})
|
|
|
|
it('surfaces the API error message on failure', async () => {
|
|
await expect(
|
|
textractParserTool.transformResponse!(respond({ success: false, error: 'Bad request' }))
|
|
).rejects.toThrow('Bad request')
|
|
})
|
|
})
|
|
|
|
describe('textract_parser_v2', () => {
|
|
const body = textractParserV2Tool.request.body!
|
|
|
|
it('throws when no file is provided for sync mode', () => {
|
|
expect(() =>
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
} as never)
|
|
).toThrow('Document file is required for single-page processing')
|
|
})
|
|
|
|
it('builds a sync body from a UserFile', () => {
|
|
const file = { key: 'file-key', name: 'doc.pdf' } as never
|
|
expect(
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
file,
|
|
} as never)
|
|
).toMatchObject({ processingMode: 'sync', file })
|
|
})
|
|
})
|
|
|
|
describe('textract_analyze_expense', () => {
|
|
const body = textractAnalyzeExpenseTool.request.body!
|
|
|
|
it('throws when neither file nor filePath is provided for sync mode', () => {
|
|
expect(() =>
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
} as never)
|
|
).toThrow('Document is required for single-page processing')
|
|
})
|
|
|
|
it('falls back to filePath when no file object is provided', () => {
|
|
expect(
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
filePath: ' https://example.com/receipt.pdf ',
|
|
} as never)
|
|
).toMatchObject({ processingMode: 'sync', filePath: 'https://example.com/receipt.pdf' })
|
|
})
|
|
|
|
it('builds an async body requiring s3Uri', () => {
|
|
expect(
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
processingMode: 'async',
|
|
s3Uri: 's3://bucket/receipt.pdf',
|
|
} as never)
|
|
).toMatchObject({ processingMode: 'async', s3Uri: 's3://bucket/receipt.pdf' })
|
|
})
|
|
|
|
it('normalizes expense documents from the response', async () => {
|
|
const result = await textractAnalyzeExpenseTool.transformResponse!(
|
|
respond({
|
|
success: true,
|
|
output: {
|
|
expenseDocuments: [{ expenseIndex: 0, summaryFields: [], lineItemGroups: [] }],
|
|
documentMetadata: { pages: 1 },
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.output.expenseDocuments).toHaveLength(1)
|
|
expect(result.output.documentMetadata.pages).toBe(1)
|
|
})
|
|
})
|
|
|
|
describe('textract_analyze_id', () => {
|
|
const body = textractAnalyzeIdTool.request.body!
|
|
|
|
it('throws when no front-of-ID file is provided', () => {
|
|
expect(() =>
|
|
body({ accessKeyId: 'key', secretAccessKey: 'secret', region: 'us-east-1' } as never)
|
|
).toThrow('Identity document is required')
|
|
})
|
|
|
|
it('includes fileBack only when provided', () => {
|
|
const file = { key: 'front-key' } as never
|
|
expect(
|
|
body({ accessKeyId: 'key', secretAccessKey: 'secret', region: 'us-east-1', file } as never)
|
|
).toEqual({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
file,
|
|
})
|
|
|
|
const fileBack = { key: 'back-key' } as never
|
|
expect(
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
file,
|
|
fileBack,
|
|
} as never)
|
|
).toEqual({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
file,
|
|
fileBack,
|
|
})
|
|
})
|
|
|
|
it('falls back to filePath/filePathBack when no file objects are provided', () => {
|
|
expect(
|
|
body({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
filePath: ' https://example.com/id-front.png ',
|
|
filePathBack: ' https://example.com/id-back.png ',
|
|
} as never)
|
|
).toEqual({
|
|
accessKeyId: 'key',
|
|
secretAccessKey: 'secret',
|
|
region: 'us-east-1',
|
|
filePath: 'https://example.com/id-front.png',
|
|
filePathBack: 'https://example.com/id-back.png',
|
|
})
|
|
})
|
|
|
|
it('normalizes identity documents from the response', async () => {
|
|
const result = await textractAnalyzeIdTool.transformResponse!(
|
|
respond({
|
|
success: true,
|
|
output: {
|
|
identityDocuments: [{ documentIndex: 0, identityDocumentFields: [] }],
|
|
documentMetadata: { pages: 1 },
|
|
modelVersion: '1.0',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(result.success).toBe(true)
|
|
expect(result.output.identityDocuments).toHaveLength(1)
|
|
expect(result.output.modelVersion).toBe('1.0')
|
|
})
|
|
})
|