Skip to content

Commit fa97279

Browse files
committed
test: Cover the error guards and token predicates
All six exported error guard functions and the token predicates had no test coverage. Add direct tests for each guard and predicate, including subclass and negative cases. Also correct the README claim that client options are deep merged, they are shallow merged, and stop excluding index files from the coverage report, which hid the per-route re-export surface. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8xeJm2Hd923k8uo6eoFd2
1 parent 4c71913 commit fa97279

4 files changed

Lines changed: 197 additions & 2 deletions

File tree

‎.c8rc.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"exclude": [
3-
"**/index.ts",
43
"package/**/*.ts",
54
"examples/**/*.ts",
65
"**/*.test.ts",

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,8 @@ default.
474474

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

479480
By default, the SDK makes up to three attempts: the initial request and two
480481
retries. Retries are limited to `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE`
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import test from 'ava'
2+
3+
import {
4+
type ActionAttempt,
5+
type FailedActionAttempt,
6+
isSeamActionAttemptError,
7+
isSeamActionAttemptFailedError,
8+
isSeamActionAttemptTimeoutError,
9+
isSeamHttpApiError,
10+
isSeamHttpInvalidInputError,
11+
isSeamHttpUnauthorizedError,
12+
SeamActionAttemptError,
13+
SeamActionAttemptFailedError,
14+
SeamActionAttemptTimeoutError,
15+
SeamHttpApiError,
16+
SeamHttpInvalidInputError,
17+
SeamHttpUnauthorizedError,
18+
} from '@seamapi/http/connect'
19+
20+
const apiError = new SeamHttpApiError(
21+
{ type: 'device_not_found', message: 'Device not found' },
22+
404,
23+
'request-1',
24+
)
25+
26+
const unauthorizedError = new SeamHttpUnauthorizedError('request-1')
27+
28+
const invalidInputError = new SeamHttpInvalidInputError(
29+
{
30+
type: 'invalid_input',
31+
message: 'Invalid input',
32+
validation_errors: { device_id: { _errors: ['Required'] } },
33+
},
34+
400,
35+
'request-1',
36+
)
37+
38+
const pendingActionAttempt = {
39+
action_attempt_id: 'e2192660-0e45-4a11-9800-eb4d086cca09',
40+
action_type: 'UNLOCK_DOOR',
41+
status: 'pending',
42+
error: null,
43+
result: null,
44+
} as unknown as ActionAttempt
45+
46+
const failedActionAttempt = {
47+
...pendingActionAttempt,
48+
status: 'error',
49+
error: { message: 'Failed', type: 'foo' },
50+
} as unknown as FailedActionAttempt<ActionAttempt>
51+
52+
const actionAttemptError = new SeamActionAttemptError(
53+
'Something happened',
54+
pendingActionAttempt,
55+
)
56+
57+
const actionAttemptFailedError = new SeamActionAttemptFailedError(
58+
failedActionAttempt,
59+
)
60+
61+
const actionAttemptTimeoutError = new SeamActionAttemptTimeoutError(
62+
pendingActionAttempt,
63+
100,
64+
)
65+
66+
const unrelatedValues = [new Error('unrelated'), null, undefined, {}, 'error']
67+
68+
test('isSeamHttpApiError: matches every Seam API error', (t) => {
69+
t.true(isSeamHttpApiError(apiError))
70+
t.true(isSeamHttpApiError(unauthorizedError))
71+
t.true(isSeamHttpApiError(invalidInputError))
72+
t.false(isSeamHttpApiError(actionAttemptError))
73+
for (const value of unrelatedValues) t.false(isSeamHttpApiError(value))
74+
})
75+
76+
test('isSeamHttpUnauthorizedError: matches only the unauthorized error', (t) => {
77+
t.true(isSeamHttpUnauthorizedError(unauthorizedError))
78+
t.false(isSeamHttpUnauthorizedError(apiError))
79+
t.false(isSeamHttpUnauthorizedError(invalidInputError))
80+
for (const value of unrelatedValues) {
81+
t.false(isSeamHttpUnauthorizedError(value))
82+
}
83+
})
84+
85+
test('isSeamHttpInvalidInputError: matches only the invalid input error', (t) => {
86+
t.true(isSeamHttpInvalidInputError(invalidInputError))
87+
t.false(isSeamHttpInvalidInputError(apiError))
88+
t.false(isSeamHttpInvalidInputError(unauthorizedError))
89+
for (const value of unrelatedValues) {
90+
t.false(isSeamHttpInvalidInputError(value))
91+
}
92+
})
93+
94+
test('isSeamActionAttemptError: matches every action attempt error', (t) => {
95+
t.true(isSeamActionAttemptError(actionAttemptError))
96+
t.true(isSeamActionAttemptError(actionAttemptFailedError))
97+
t.true(isSeamActionAttemptError(actionAttemptTimeoutError))
98+
t.false(isSeamActionAttemptError(apiError))
99+
for (const value of unrelatedValues) t.false(isSeamActionAttemptError(value))
100+
})
101+
102+
test('isSeamActionAttemptFailedError: matches only the failed error', (t) => {
103+
t.true(isSeamActionAttemptFailedError(actionAttemptFailedError))
104+
t.false(isSeamActionAttemptFailedError(actionAttemptError))
105+
t.false(isSeamActionAttemptFailedError(actionAttemptTimeoutError))
106+
for (const value of unrelatedValues) {
107+
t.false(isSeamActionAttemptFailedError(value))
108+
}
109+
})
110+
111+
test('isSeamActionAttemptTimeoutError: matches only the timeout error', (t) => {
112+
t.true(isSeamActionAttemptTimeoutError(actionAttemptTimeoutError))
113+
t.false(isSeamActionAttemptTimeoutError(actionAttemptError))
114+
t.false(isSeamActionAttemptTimeoutError(actionAttemptFailedError))
115+
for (const value of unrelatedValues) {
116+
t.false(isSeamActionAttemptTimeoutError(value))
117+
}
118+
})

‎test/seam/connect/token.test.ts‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import test from 'ava'
2+
3+
import {
4+
isApiKey,
5+
isClientSessionToken,
6+
isConsoleSessionToken,
7+
isPersonalAccessToken,
8+
isPublishableKey,
9+
} from '@seamapi/http/connect'
10+
11+
import { isAccessToken, isJwt, isSeamToken } from 'lib/token.js'
12+
13+
const apiKey = 'seam_apikey1_token'
14+
const accessToken = 'seam_at1_token'
15+
const clientSessionToken = 'seam_cst1_token'
16+
const publishableKey = 'seam_pk1_token'
17+
const jwt = 'ey1.ey2.token'
18+
const unknownToken = 'some-other-token'
19+
20+
test('isApiKey: only matches an api key', (t) => {
21+
t.true(isApiKey(apiKey))
22+
t.false(isApiKey(accessToken))
23+
t.false(isApiKey(clientSessionToken))
24+
t.false(isApiKey(publishableKey))
25+
t.false(isApiKey(jwt))
26+
t.false(isApiKey(unknownToken))
27+
})
28+
29+
test('isAccessToken: matches the access token prefix', (t) => {
30+
t.true(isAccessToken(accessToken))
31+
t.false(isAccessToken(apiKey))
32+
t.false(isAccessToken(jwt))
33+
t.false(isAccessToken(unknownToken))
34+
})
35+
36+
test('isPersonalAccessToken: matches an access token', (t) => {
37+
t.true(isPersonalAccessToken(accessToken))
38+
t.false(isPersonalAccessToken(apiKey))
39+
t.false(isPersonalAccessToken(clientSessionToken))
40+
t.false(isPersonalAccessToken(unknownToken))
41+
})
42+
43+
test('isClientSessionToken: matches the client session token prefix', (t) => {
44+
t.true(isClientSessionToken(clientSessionToken))
45+
t.false(isClientSessionToken(apiKey))
46+
t.false(isClientSessionToken(publishableKey))
47+
t.false(isClientSessionToken(unknownToken))
48+
})
49+
50+
test('isPublishableKey: matches the publishable key prefix', (t) => {
51+
t.true(isPublishableKey(publishableKey))
52+
t.false(isPublishableKey(apiKey))
53+
t.false(isPublishableKey(clientSessionToken))
54+
t.false(isPublishableKey(unknownToken))
55+
})
56+
57+
test('isJwt: matches the jwt prefix', (t) => {
58+
t.true(isJwt(jwt))
59+
t.false(isJwt(apiKey))
60+
t.false(isJwt(unknownToken))
61+
})
62+
63+
test('isConsoleSessionToken: matches a jwt', (t) => {
64+
t.true(isConsoleSessionToken(jwt))
65+
t.false(isConsoleSessionToken(apiKey))
66+
t.false(isConsoleSessionToken(accessToken))
67+
t.false(isConsoleSessionToken(unknownToken))
68+
})
69+
70+
test('isSeamToken: matches the seam token prefix', (t) => {
71+
t.true(isSeamToken(apiKey))
72+
t.true(isSeamToken(accessToken))
73+
t.true(isSeamToken(clientSessionToken))
74+
t.true(isSeamToken(publishableKey))
75+
t.false(isSeamToken(jwt))
76+
t.false(isSeamToken(unknownToken))
77+
})

0 commit comments

Comments
 (0)