Skip to content
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
1 change: 0 additions & 1 deletion .c8rc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"exclude": [
"**/index.ts",
"package/**/*.ts",
"examples/**/*.ts",
"**/*.test.ts",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,8 @@ default.

The Axios client and retry behavior may be configured with custom initiation options
via [`axiosOptions`][axiosOptions] and [`axiosRetryOptions`][axiosRetryOptions].
Options are deep merged with the default options.
Options are shallow merged with the default options:
each provided top-level option replaces the default value.

By default, the SDK makes up to three attempts: the initial request and two
retries. Retries are limited to `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE`
Expand Down
118 changes: 118 additions & 0 deletions test/seam/connect/error-guards.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import test from 'ava'

import {
type ActionAttempt,
type FailedActionAttempt,
isSeamActionAttemptError,
isSeamActionAttemptFailedError,
isSeamActionAttemptTimeoutError,
isSeamHttpApiError,
isSeamHttpInvalidInputError,
isSeamHttpUnauthorizedError,
SeamActionAttemptError,
SeamActionAttemptFailedError,
SeamActionAttemptTimeoutError,
SeamHttpApiError,
SeamHttpInvalidInputError,
SeamHttpUnauthorizedError,
} from '@seamapi/http/connect'

const apiError = new SeamHttpApiError(
{ type: 'device_not_found', message: 'Device not found' },
404,
'request-1',
)

const unauthorizedError = new SeamHttpUnauthorizedError('request-1')

const invalidInputError = new SeamHttpInvalidInputError(
{
type: 'invalid_input',
message: 'Invalid input',
validation_errors: { device_id: { _errors: ['Required'] } },
},
400,
'request-1',
)

const pendingActionAttempt = {
action_attempt_id: 'e2192660-0e45-4a11-9800-eb4d086cca09',
action_type: 'UNLOCK_DOOR',
status: 'pending',
error: null,
result: null,
} as unknown as ActionAttempt

const failedActionAttempt = {
...pendingActionAttempt,
status: 'error',
error: { message: 'Failed', type: 'foo' },
} as unknown as FailedActionAttempt<ActionAttempt>

const actionAttemptError = new SeamActionAttemptError(
'Something happened',
pendingActionAttempt,
)

const actionAttemptFailedError = new SeamActionAttemptFailedError(
failedActionAttempt,
)

const actionAttemptTimeoutError = new SeamActionAttemptTimeoutError(
pendingActionAttempt,
100,
)

const unrelatedValues = [new Error('unrelated'), null, undefined, {}, 'error']

test('isSeamHttpApiError: matches every Seam API error', (t) => {
t.true(isSeamHttpApiError(apiError))
t.true(isSeamHttpApiError(unauthorizedError))
t.true(isSeamHttpApiError(invalidInputError))
t.false(isSeamHttpApiError(actionAttemptError))
for (const value of unrelatedValues) t.false(isSeamHttpApiError(value))
})

test('isSeamHttpUnauthorizedError: matches only the unauthorized error', (t) => {
t.true(isSeamHttpUnauthorizedError(unauthorizedError))
t.false(isSeamHttpUnauthorizedError(apiError))
t.false(isSeamHttpUnauthorizedError(invalidInputError))
for (const value of unrelatedValues) {
t.false(isSeamHttpUnauthorizedError(value))
}
})

test('isSeamHttpInvalidInputError: matches only the invalid input error', (t) => {
t.true(isSeamHttpInvalidInputError(invalidInputError))
t.false(isSeamHttpInvalidInputError(apiError))
t.false(isSeamHttpInvalidInputError(unauthorizedError))
for (const value of unrelatedValues) {
t.false(isSeamHttpInvalidInputError(value))
}
})

test('isSeamActionAttemptError: matches every action attempt error', (t) => {
t.true(isSeamActionAttemptError(actionAttemptError))
t.true(isSeamActionAttemptError(actionAttemptFailedError))
t.true(isSeamActionAttemptError(actionAttemptTimeoutError))
t.false(isSeamActionAttemptError(apiError))
for (const value of unrelatedValues) t.false(isSeamActionAttemptError(value))
})

test('isSeamActionAttemptFailedError: matches only the failed error', (t) => {
t.true(isSeamActionAttemptFailedError(actionAttemptFailedError))
t.false(isSeamActionAttemptFailedError(actionAttemptError))
t.false(isSeamActionAttemptFailedError(actionAttemptTimeoutError))
for (const value of unrelatedValues) {
t.false(isSeamActionAttemptFailedError(value))
}
})

test('isSeamActionAttemptTimeoutError: matches only the timeout error', (t) => {
t.true(isSeamActionAttemptTimeoutError(actionAttemptTimeoutError))
t.false(isSeamActionAttemptTimeoutError(actionAttemptError))
t.false(isSeamActionAttemptTimeoutError(actionAttemptFailedError))
for (const value of unrelatedValues) {
t.false(isSeamActionAttemptTimeoutError(value))
}
})
77 changes: 77 additions & 0 deletions test/seam/connect/token.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import test from 'ava'

import {
isApiKey,
isClientSessionToken,
isConsoleSessionToken,
isPersonalAccessToken,
isPublishableKey,
} from '@seamapi/http/connect'

import { isAccessToken, isJwt, isSeamToken } from 'lib/token.js'

const apiKey = 'seam_apikey1_token'
const accessToken = 'seam_at1_token'
const clientSessionToken = 'seam_cst1_token'
const publishableKey = 'seam_pk1_token'
const jwt = 'ey1.ey2.token'
const unknownToken = 'some-other-token'

test('isApiKey: only matches an api key', (t) => {
t.true(isApiKey(apiKey))
t.false(isApiKey(accessToken))
t.false(isApiKey(clientSessionToken))
t.false(isApiKey(publishableKey))
t.false(isApiKey(jwt))
t.false(isApiKey(unknownToken))
})

test('isAccessToken: matches the access token prefix', (t) => {
t.true(isAccessToken(accessToken))
t.false(isAccessToken(apiKey))
t.false(isAccessToken(jwt))
t.false(isAccessToken(unknownToken))
})

test('isPersonalAccessToken: matches an access token', (t) => {
t.true(isPersonalAccessToken(accessToken))
t.false(isPersonalAccessToken(apiKey))
t.false(isPersonalAccessToken(clientSessionToken))
t.false(isPersonalAccessToken(unknownToken))
})

test('isClientSessionToken: matches the client session token prefix', (t) => {
t.true(isClientSessionToken(clientSessionToken))
t.false(isClientSessionToken(apiKey))
t.false(isClientSessionToken(publishableKey))
t.false(isClientSessionToken(unknownToken))
})

test('isPublishableKey: matches the publishable key prefix', (t) => {
t.true(isPublishableKey(publishableKey))
t.false(isPublishableKey(apiKey))
t.false(isPublishableKey(clientSessionToken))
t.false(isPublishableKey(unknownToken))
})

test('isJwt: matches the jwt prefix', (t) => {
t.true(isJwt(jwt))
t.false(isJwt(apiKey))
t.false(isJwt(unknownToken))
})

test('isConsoleSessionToken: matches a jwt', (t) => {
t.true(isConsoleSessionToken(jwt))
t.false(isConsoleSessionToken(apiKey))
t.false(isConsoleSessionToken(accessToken))
t.false(isConsoleSessionToken(unknownToken))
})

test('isSeamToken: matches the seam token prefix', (t) => {
t.true(isSeamToken(apiKey))
t.true(isSeamToken(accessToken))
t.true(isSeamToken(clientSessionToken))
t.true(isSeamToken(publishableKey))
t.false(isSeamToken(jwt))
t.false(isSeamToken(unknownToken))
})
Loading