6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (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 / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
290 lines
8.5 KiB
TypeScript
290 lines
8.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { ErrorExtractorId, type ErrorInfo, extractErrorMessage } from '@/tools/error-extractors'
|
|
|
|
describe('Error Extractors', () => {
|
|
describe('extractErrorMessage', () => {
|
|
it('should extract GraphQL error messages', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
errors: [{ message: 'GraphQL validation error' }],
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('GraphQL validation error')
|
|
})
|
|
|
|
it('should extract Twitter API error details', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 403,
|
|
data: {
|
|
errors: [{ detail: 'Rate limit exceeded' }],
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Rate limit exceeded')
|
|
})
|
|
|
|
it('should extract Telegram API description', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 403,
|
|
data: {
|
|
ok: false,
|
|
error_code: 403,
|
|
description: "Forbidden: bots can't send messages to bots",
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe("Forbidden: bots can't send messages to bots")
|
|
})
|
|
|
|
it('should extract standard message field', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
message: 'Invalid request parameters',
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Invalid request parameters')
|
|
})
|
|
|
|
it('should extract OAuth error_description', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 401,
|
|
data: {
|
|
error: 'invalid_grant',
|
|
error_description: 'The provided authorization grant is invalid',
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('The provided authorization grant is invalid')
|
|
})
|
|
|
|
it('should extract SOAP fault strings', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 500,
|
|
data: {
|
|
fault: {
|
|
faultstring: 'SOAP processing error',
|
|
},
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('SOAP processing error')
|
|
})
|
|
|
|
it('should extract nested error object messages', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
error: {
|
|
message: 'Resource not found',
|
|
},
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Resource not found')
|
|
})
|
|
|
|
it('should handle string error field', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
error: 'Bad request',
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Bad request')
|
|
})
|
|
|
|
it('should extract errors array with strings', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
errors: ['Email is required', 'Password is too short'],
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Email is required')
|
|
})
|
|
|
|
it('should extract plain text error responses', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 401,
|
|
statusText: 'Unauthorized',
|
|
data: 'Invalid access token',
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Invalid access token')
|
|
})
|
|
|
|
it('should extract plain text error from APIs like Apollo', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
data: 'Invalid API key provided',
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Invalid API key provided')
|
|
})
|
|
|
|
it('should trim whitespace from plain text errors', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
statusText: 'Bad Request',
|
|
data: ' Error message with spaces ',
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Error message with spaces')
|
|
})
|
|
|
|
it('should skip empty plain text and use HTTP status text fallback', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 500,
|
|
statusText: 'Internal Server Error',
|
|
data: ' ',
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Internal Server Error')
|
|
})
|
|
|
|
it('should use HTTP status text as fallback', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 404,
|
|
statusText: 'Not Found',
|
|
data: {},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Not Found')
|
|
})
|
|
|
|
it('should use final fallback when no pattern matches', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 500,
|
|
data: {},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Request failed with status 500')
|
|
})
|
|
|
|
it('should handle undefined errorInfo', () => {
|
|
expect(extractErrorMessage(undefined)).toBe('Request failed with status unknown')
|
|
})
|
|
|
|
it('should handle empty strings gracefully', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
message: '',
|
|
},
|
|
}
|
|
expect(extractErrorMessage(errorInfo)).toBe('Request failed with status 400')
|
|
})
|
|
})
|
|
|
|
describe('extractErrorMessage with explicit extractorId', () => {
|
|
it('should use specified extractor directly (deterministic)', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 403,
|
|
data: {
|
|
description: "Forbidden: bots can't send messages to bots",
|
|
message: 'Some other message',
|
|
},
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.TELEGRAM_DESCRIPTION)).toBe(
|
|
"Forbidden: bots can't send messages to bots"
|
|
)
|
|
})
|
|
|
|
it('should use specified extractor even when other patterns match first', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
errors: [{ message: 'GraphQL error' }],
|
|
message: 'Standard message',
|
|
},
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.STANDARD_MESSAGE)).toBe(
|
|
'Standard message'
|
|
)
|
|
})
|
|
|
|
it('should fallback when specified extractor does not find message', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 404,
|
|
data: {
|
|
someOtherField: 'value',
|
|
},
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.TELEGRAM_DESCRIPTION)).toBe(
|
|
'Request failed with status 404'
|
|
)
|
|
})
|
|
|
|
it('should warn and fallback for non-existent extractor ID', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 500,
|
|
data: {
|
|
message: 'Error message',
|
|
},
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, 'non-existent-extractor')).toBe(
|
|
'Request failed with status 500'
|
|
)
|
|
})
|
|
|
|
it('should use plain text extractor when explicitly specified', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
data: 'Plain text error message',
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.PLAIN_TEXT_DATA)).toBe(
|
|
'Plain text error message'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('smartlead-errors', () => {
|
|
it('should extract the domain error string', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: { error: 'Invalid value for status. Allowed values are - START,STOPPED,PAUSED' },
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
|
'Invalid value for status. Allowed values are - START,STOPPED,PAUSED'
|
|
)
|
|
})
|
|
|
|
it('should prefer message over the generic "Bad Request" on validation payloads', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 400,
|
|
data: {
|
|
statusCode: 400,
|
|
error: 'Bad Request',
|
|
message: '"email_status" must be one of [null, opened, clicked, replied]',
|
|
validation: { source: 'query', keys: ['email_status'] },
|
|
},
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
|
'"email_status" must be one of [null, opened, clicked, replied]'
|
|
)
|
|
})
|
|
|
|
it('should extract the auth failure message', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 401,
|
|
data: { message: 'Invalid API Key' },
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
|
'Invalid API Key'
|
|
)
|
|
})
|
|
|
|
it('should fall back to the status message when no error fields are present', () => {
|
|
const errorInfo: ErrorInfo = {
|
|
status: 500,
|
|
data: {},
|
|
}
|
|
|
|
expect(extractErrorMessage(errorInfo, ErrorExtractorId.SMARTLEAD_ERRORS)).toBe(
|
|
'Request failed with status 500'
|
|
)
|
|
})
|
|
})
|
|
})
|