Skip to content
This repository was archived by the owner on Jul 24, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Tests
on: push
jobs:
default:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'npm'
- run: npm ci
- run: npm test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
*.log
.DS_Store
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"type": "module",
"main": "index.js",
"repository": "github:adapt-security/adapt-authoring-errors",
"scripts": {
"test": "node --test 'tests/**/*.spec.js'"
},
"dependencies": {
"glob": "^13.0.0"
},
Expand Down
222 changes: 222 additions & 0 deletions tests/AdaptError.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import AdaptError from '../lib/AdaptError.js'

describe('AdaptError', () => {
describe('constructor', () => {
it('should create an error with code only', () => {
const error = new AdaptError('TEST_ERROR')
assert.equal(error.code, 'TEST_ERROR')
assert.equal(error.statusCode, 500)
assert.deepEqual(error.meta, {})
assert.ok(error instanceof Error)
assert.ok(error instanceof AdaptError)
})

it('should create an error with code and statusCode', () => {
const error = new AdaptError('NOT_FOUND', 404)
assert.equal(error.code, 'NOT_FOUND')
assert.equal(error.statusCode, 404)
assert.deepEqual(error.meta, {})
})

it('should create an error with code, statusCode, and metadata', () => {
const metadata = { description: 'Test error', data: { id: '123' } }
const error = new AdaptError('CUSTOM_ERROR', 400, metadata)
assert.equal(error.code, 'CUSTOM_ERROR')
assert.equal(error.statusCode, 400)
assert.deepEqual(error.meta, metadata)
})

it('should use default statusCode of 500 when not provided', () => {
const error = new AdaptError('SERVER_ERROR')
assert.equal(error.statusCode, 500)
})

it('should use empty object as default metadata when not provided', () => {
const error = new AdaptError('TEST_ERROR', 400)
assert.deepEqual(error.meta, {})
})

it('should set the message property to the error code', () => {
const error = new AdaptError('MY_ERROR')
assert.equal(error.message, 'MY_ERROR')
})
})

describe('#setData()', () => {
it('should set data on the error', () => {
const error = new AdaptError('TEST_ERROR')
const data = { userId: '123', action: 'delete' }
error.setData(data)
assert.deepEqual(error.data, data)
})

it('should return the error instance for chaining', () => {
const error = new AdaptError('TEST_ERROR')
const returnValue = error.setData({ test: 'value' })
assert.equal(returnValue, error)
})

it('should allow method chaining', () => {
const error = new AdaptError('TEST_ERROR')
const data = { key: 'value' }
const result = error.setData(data)
assert.equal(result.data, data)
assert.ok(result instanceof AdaptError)
})

it('should overwrite existing data', () => {
const error = new AdaptError('TEST_ERROR')
error.setData({ first: 'data' })
error.setData({ second: 'data' })
assert.deepEqual(error.data, { second: 'data' })
})
})

describe('#toString()', () => {
it('should return formatted string without data', () => {
const error = new AdaptError('TEST_ERROR')
const result = error.toString()
// Note: trailing space after code is part of the current implementation
assert.equal(result, 'AdaptError: TEST_ERROR ')
})

it('should return formatted string with data', () => {
const error = new AdaptError('TEST_ERROR')
error.setData({ userId: '123' })
const result = error.toString()
assert.equal(result, 'AdaptError: TEST_ERROR {"userId":"123"}')
})

it('should include class name in output', () => {
const error = new AdaptError('MY_ERROR')
const result = error.toString()
assert.ok(result.startsWith('AdaptError:'))
})

it('should handle complex data objects', () => {
const error = new AdaptError('COMPLEX_ERROR')
const complexData = { nested: { key: 'value' }, array: [1, 2, 3] }
error.setData(complexData)
const result = error.toString()
assert.ok(result.includes(JSON.stringify(complexData)))
})
})

describe('Error properties', () => {
it('should have code property', () => {
const error = new AdaptError('TEST_CODE')
assert.equal(typeof error.code, 'string')
assert.equal(error.code, 'TEST_CODE')
})

it('should have statusCode property', () => {
const error = new AdaptError('TEST_ERROR', 404)
assert.equal(typeof error.statusCode, 'number')
assert.equal(error.statusCode, 404)
})

it('should have meta property', () => {
const meta = { description: 'Test' }
const error = new AdaptError('TEST_ERROR', 500, meta)
assert.equal(typeof error.meta, 'object')
assert.deepEqual(error.meta, meta)
})

it('should have data property after setData is called', () => {
const error = new AdaptError('TEST_ERROR')
error.setData({ test: 'data' })
assert.equal(typeof error.data, 'object')
assert.deepEqual(error.data, { test: 'data' })
})
})

describe('Error inheritance', () => {
it('should have a stack trace', () => {
const error = new AdaptError('TEST_ERROR')
assert.equal(typeof error.stack, 'string')
assert.ok(error.stack.length > 0)
})

it('should be catchable as a generic Error', () => {
let caught = false
try {
throw new AdaptError('THROWN_ERROR', 400)
} catch (e) {
caught = true
assert.ok(e instanceof Error)
assert.equal(e.code, 'THROWN_ERROR')
assert.equal(e.statusCode, 400)
}
assert.ok(caught)
})

it('should inherit name from Error', () => {
const error = new AdaptError('TEST_ERROR')
assert.equal(error.name, 'Error')
})
})

describe('setData and throw pattern', () => {
it('should support throw with chained setData', () => {
const data = { userId: '456' }
let caught
try {
throw new AdaptError('AUTH_ERROR', 401).setData(data)
} catch (e) {
caught = e
}
assert.ok(caught instanceof AdaptError)
assert.deepEqual(caught.data, data)
assert.equal(caught.code, 'AUTH_ERROR')
})
})

describe('Edge cases', () => {
it('should handle empty string as error code', () => {
const error = new AdaptError('')
assert.equal(error.code, '')
assert.equal(error.message, '')
})

it('should handle null metadata', () => {
const error = new AdaptError('TEST_ERROR', 500, null)
assert.equal(error.meta, null)
})

it('should handle zero statusCode', () => {
const error = new AdaptError('TEST_ERROR', 0)
assert.equal(error.statusCode, 0)
})

it('should handle setData with null', () => {
const error = new AdaptError('TEST_ERROR')
error.setData(null)
assert.equal(error.data, null)
})

it('should handle setData with undefined', () => {
const error = new AdaptError('TEST_ERROR')
error.setData(undefined)
assert.equal(error.data, undefined)
})

it('should handle setData with a string value', () => {
const error = new AdaptError('TEST_ERROR')
error.setData('simple string')
assert.equal(error.data, 'simple string')
})

it('should handle toString with empty data object', () => {
const error = new AdaptError('TEST_ERROR')
error.setData({})
assert.equal(error.toString(), 'AdaptError: TEST_ERROR {}')
})

it('should not have data property before setData is called', () => {
const error = new AdaptError('TEST_ERROR')
assert.equal(error.data, undefined)
})
})
})
Loading