@barndoor-ai/sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Tests for exception hierarchy and error handling.
3
+ */
4
+
5
+ import {
6
+ BarndoorError,
7
+ AuthenticationError,
8
+ TokenError,
9
+ TokenExpiredError,
10
+ TokenValidationError,
11
+ ConnectionError,
12
+ HTTPError,
13
+ ServerNotFoundError,
14
+ OAuthError,
15
+ ConfigurationError,
16
+ TimeoutError,
17
+ } from '../dist/index.esm.js';
18
+
19
+ describe('Exception Hierarchy', () => {
20
+ test('exception inheritance is correct', () => {
21
+ expect(AuthenticationError.prototype).toBeInstanceOf(BarndoorError);
22
+ expect(TokenError.prototype).toBeInstanceOf(AuthenticationError);
23
+ expect(TokenExpiredError.prototype).toBeInstanceOf(TokenError);
24
+ expect(TokenValidationError.prototype).toBeInstanceOf(TokenError);
25
+ expect(ConnectionError.prototype).toBeInstanceOf(BarndoorError);
26
+ expect(HTTPError.prototype).toBeInstanceOf(BarndoorError);
27
+ expect(ServerNotFoundError.prototype).toBeInstanceOf(BarndoorError);
28
+ expect(OAuthError.prototype).toBeInstanceOf(AuthenticationError);
29
+ expect(ConfigurationError.prototype).toBeInstanceOf(BarndoorError);
30
+ expect(TimeoutError.prototype).toBeInstanceOf(BarndoorError);
31
+ });
32
+
33
+ test('BarndoorError is base exception', () => {
34
+ const error = new BarndoorError('test message');
35
+ expect(error).toBeInstanceOf(Error);
36
+ expect(error.name).toBe('BarndoorError');
37
+ expect(error.message).toBe('test message');
38
+ });
39
+
40
+ test('AuthenticationError stores error code', () => {
41
+ const error = new AuthenticationError('auth failed', 'AUTH001');
42
+ expect(error).toBeInstanceOf(BarndoorError);
43
+ expect(error.errorCode).toBe('AUTH001');
44
+ expect(error.message).toBe('auth failed');
45
+ });
46
+
47
+ test('TokenError adds help text', () => {
48
+ const errorWithHelp = new TokenError('token invalid', 'Custom help text');
49
+ expect(errorWithHelp.message).toBe('token invalid Custom help text');
50
+ expect(errorWithHelp.helpText).toBe('Custom help text');
51
+
52
+ const errorWithoutHelp = new TokenError('token invalid');
53
+ expect(errorWithoutHelp.message).toBe("token invalid Run 'barndoor-login' to authenticate.");
54
+ expect(errorWithoutHelp.helpText).toBeNull();
55
+ });
56
+
57
+ test('ConnectionError creates user-friendly messages', () => {
58
+ const timeoutError = new ConnectionError('https://api.example.com', new Error('timeout'));
59
+ expect(timeoutError.message).toContain('timed out');
60
+ expect(timeoutError.url).toBe('https://api.example.com');
61
+
62
+ const refusedError = new ConnectionError(
63
+ 'https://api.example.com',
64
+ new Error('connection refused')
65
+ );
66
+ expect(refusedError.message).toContain('Could not connect');
67
+
68
+ const dnsError = new ConnectionError(
69
+ 'https://api.example.com',
70
+ new Error('name resolution failed')
71
+ );
72
+ expect(dnsError.message).toContain('Could not resolve hostname');
73
+
74
+ const genericError = new ConnectionError('https://api.example.com', new Error('generic error'));
75
+ expect(genericError.message).toContain('Failed to connect');
76
+ });
77
+
78
+ test('HTTPError creates status-specific messages', () => {
79
+ const badRequest = new HTTPError(400, 'Bad Request');
80
+ expect(badRequest.message).toContain('Invalid request');
81
+ expect(badRequest.statusCode).toBe(400);
82
+
83
+ const unauthorized = new HTTPError(401, 'Unauthorized');
84
+ expect(unauthorized.message).toContain('Authentication failed');
85
+
86
+ const forbidden = new HTTPError(403, 'Forbidden');
87
+ expect(forbidden.message).toContain('Access denied');
88
+
89
+ const notFound = new HTTPError(404, 'Not Found');
90
+ expect(notFound.message).toContain('Resource not found');
91
+
92
+ const rateLimit = new HTTPError(429, 'Too Many Requests');
93
+ expect(rateLimit.message).toContain('Rate limit exceeded');
94
+
95
+ const serverError = new HTTPError(500, 'Internal Server Error');
96
+ expect(serverError.message).toContain('Server error');
97
+
98
+ const unknownError = new HTTPError(418, "I'm a teapot");
99
+ expect(unknownError.message).toContain("I'm a teapot");
100
+ });
101
+
102
+ test('ServerNotFoundError with available servers', () => {
103
+ const availableServers = ['server1', 'server2', 'server3'];
104
+ const error = new ServerNotFoundError('missing-server', availableServers);
105
+
106
+ expect(error.message).toContain('missing-server');
107
+ expect(error.message).toContain('server1, server2, server3');
108
+ expect(error.serverIdentifier).toBe('missing-server');
109
+ expect(error.availableServers).toEqual(availableServers);
110
+ });
111
+
112
+ test('ServerNotFoundError without available servers', () => {
113
+ const error = new ServerNotFoundError('missing-server');
114
+
115
+ expect(error.message).toContain('missing-server');
116
+ expect(error.message).toContain('Use listServers()');
117
+ expect(error.availableServers).toBeNull();
118
+ });
119
+ });
120
+
121
+ describe('Error Instantiation', () => {
122
+ test('all error types can be instantiated', () => {
123
+ expect(() => new BarndoorError('test')).not.toThrow();
124
+ expect(() => new AuthenticationError('test')).not.toThrow();
125
+ expect(() => new TokenError('test')).not.toThrow();
126
+ expect(() => new TokenExpiredError('test')).not.toThrow();
127
+ expect(() => new TokenValidationError('test')).not.toThrow();
128
+ expect(() => new ConnectionError('url', new Error('test'))).not.toThrow();
129
+ expect(() => new HTTPError(500, 'test')).not.toThrow();
130
+ expect(() => new ServerNotFoundError('test')).not.toThrow();
131
+ expect(() => new OAuthError('test')).not.toThrow();
132
+ expect(() => new ConfigurationError('test')).not.toThrow();
133
+ expect(() => new TimeoutError('test')).not.toThrow();
134
+ });
135
+
136
+ test('error names are set correctly', () => {
137
+ expect(new BarndoorError('test').name).toBe('BarndoorError');
138
+ expect(new AuthenticationError('test').name).toBe('AuthenticationError');
139
+ expect(new TokenError('test').name).toBe('TokenError');
140
+ expect(new HTTPError(500, 'test').name).toBe('HTTPError');
141
+ });
142
+ });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Integration tests for the Barndoor SDK.
3
+ *
4
+ * These tests verify that the main components work together correctly
5
+ * and that the API is compatible with the Python SDK.
6
+ */
7
+
8
+ import {
9
+ BarndoorSDK,
10
+ BarndoorError,
11
+ HTTPError,
12
+ ConfigurationError,
13
+ TokenError,
14
+ ServerSummary,
15
+ ServerDetail,
16
+ BarndoorConfig,
17
+ } from '../dist/index.esm.js';
18
+
19
+ describe('SDK Integration Tests', () => {
20
+ const validToken =
21
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
22
+
23
+ test('SDK can be instantiated with valid parameters', () => {
24
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
25
+ expect(sdk).toBeInstanceOf(BarndoorSDK);
26
+ expect(sdk.base).toBe('https://api.example.com');
27
+ expect(sdk.token).toBe(validToken);
28
+ });
29
+
30
+ test('SDK throws appropriate errors for invalid parameters', () => {
31
+ expect(() => new BarndoorSDK('invalid-url', { token: validToken })).toThrow(ConfigurationError);
32
+
33
+ expect(() => new BarndoorSDK('https://api.example.com', { token: 'invalid' })).toThrow(
34
+ TokenError
35
+ );
36
+ });
37
+
38
+ test('Exception hierarchy is correct', () => {
39
+ const authError = new HTTPError(401, 'Unauthorized');
40
+ expect(authError).toBeInstanceOf(BarndoorError);
41
+ expect(authError.statusCode).toBe(401);
42
+ });
43
+
44
+ test('Models can be created from API data', () => {
45
+ const serverData = {
46
+ id: '123e4567-e89b-12d3-a456-426614174000',
47
+ name: 'Test Server',
48
+ slug: 'test-server',
49
+ provider: 'github',
50
+ connection_status: 'connected',
51
+ };
52
+
53
+ const server = ServerSummary.fromApiResponse(serverData);
54
+ expect(server).toBeInstanceOf(ServerSummary);
55
+ expect(server.id).toBe(serverData.id);
56
+ expect(server.name).toBe(serverData.name);
57
+
58
+ const detailData = { ...serverData, url: 'https://api.example.com/mcp' };
59
+ const detail = ServerDetail.fromApiResponse(detailData);
60
+ expect(detail).toBeInstanceOf(ServerDetail);
61
+ expect(detail).toBeInstanceOf(ServerSummary);
62
+ expect(detail.url).toBe(detailData.url);
63
+ });
64
+
65
+ test('Configuration works with environment variables', () => {
66
+ const originalEnv = process.env.AUTH_DOMAIN;
67
+ process.env.AUTH_DOMAIN = 'test.auth0.com';
68
+
69
+ const config = new BarndoorConfig();
70
+ expect(config.authDomain).toBe('test.auth0.com');
71
+
72
+ // Restore original environment
73
+ if (originalEnv) {
74
+ process.env.AUTH_DOMAIN = originalEnv;
75
+ } else {
76
+ delete process.env.AUTH_DOMAIN;
77
+ }
78
+ });
79
+
80
+ test('Configuration validation works', () => {
81
+ const config = new BarndoorConfig({ authDomain: '' });
82
+ expect(() => config.validate()).toThrow(ConfigurationError);
83
+ expect(() => config.validate()).toThrow('authDomain is required');
84
+ });
85
+
86
+ test('SDK can be closed properly', async () => {
87
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
88
+ await sdk.close();
89
+ expect(sdk._closed).toBe(true);
90
+ });
91
+ });
92
+
93
+ describe('API Compatibility', () => {
94
+ test('method names match Python SDK (camelCase conversion)', () => {
95
+ const sdk = new BarndoorSDK('https://api.example.com', {
96
+ token:
97
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
98
+ });
99
+
100
+ // Check that JavaScript methods exist (Python equivalents in comments)
101
+ expect(typeof sdk.listServers).toBe('function'); // list_servers
102
+ expect(typeof sdk.getServer).toBe('function'); // get_server
103
+ expect(typeof sdk.initiateConnection).toBe('function'); // initiate_connection
104
+ expect(typeof sdk.getConnectionStatus).toBe('function'); // get_connection_status
105
+ expect(typeof sdk.disconnectServer).toBe('function'); // disconnect_server
106
+ expect(typeof sdk.ensureServerConnected).toBe('function'); // ensure_server_connected
107
+ expect(typeof sdk.validateCachedToken).toBe('function'); // validate_cached_token
108
+ expect(typeof sdk.close).toBe('function'); // close
109
+ expect(typeof sdk.aclose).toBe('function'); // aclose
110
+ });
111
+
112
+ test('error messages are user-friendly like Python SDK', () => {
113
+ const httpError = new HTTPError(404, 'Not Found');
114
+ expect(httpError.message).toContain('Resource not found');
115
+ expect(httpError.message).toContain('check the server ID');
116
+
117
+ const authError = new HTTPError(401, 'Unauthorized');
118
+ expect(authError.message).toContain('Authentication failed');
119
+ expect(authError.message).toContain('check your token');
120
+ });
121
+
122
+ test('model structure matches Python SDK', () => {
123
+ const serverData = {
124
+ id: '123e4567-e89b-12d3-a456-426614174000',
125
+ name: 'Test Server',
126
+ slug: 'test-server',
127
+ provider: 'github',
128
+ connection_status: 'connected',
129
+ };
130
+
131
+ const server = new ServerSummary(serverData);
132
+
133
+ // Check that all Python SDK fields are present
134
+ expect(server).toHaveProperty('id');
135
+ expect(server).toHaveProperty('name');
136
+ expect(server).toHaveProperty('slug');
137
+ expect(server).toHaveProperty('provider');
138
+ expect(server).toHaveProperty('connection_status');
139
+
140
+ // Check field types match expectations
141
+ expect(typeof server.id).toBe('string');
142
+ expect(typeof server.name).toBe('string');
143
+ expect(typeof server.slug).toBe('string');
144
+ expect(typeof server.connection_status).toBe('string');
145
+ expect(server.provider === null || typeof server.provider === 'string').toBe(true);
146
+ });
147
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tests for data models.
3
+ */
4
+
5
+ import { ServerSummary, ServerDetail, AgentToken } from '../dist/index.esm.js';
6
+
7
+ describe('ServerSummary', () => {
8
+ const validServerData = {
9
+ id: '123e4567-e89b-12d3-a456-426614174000',
10
+ name: 'Test Server',
11
+ slug: 'test-server',
12
+ provider: 'github',
13
+ connection_status: 'connected',
14
+ };
15
+
16
+ test('creates ServerSummary with valid data', () => {
17
+ const server = new ServerSummary(validServerData);
18
+
19
+ expect(server.id).toBe(validServerData.id);
20
+ expect(server.name).toBe(validServerData.name);
21
+ expect(server.slug).toBe(validServerData.slug);
22
+ expect(server.provider).toBe(validServerData.provider);
23
+ expect(server.connection_status).toBe(validServerData.connection_status);
24
+ });
25
+
26
+ test('handles null provider', () => {
27
+ const dataWithoutProvider = { ...validServerData };
28
+ delete dataWithoutProvider.provider;
29
+
30
+ const server = new ServerSummary(dataWithoutProvider);
31
+ expect(server.provider).toBeNull();
32
+ });
33
+
34
+ test('throws error for missing required fields', () => {
35
+ expect(() => new ServerSummary({})).toThrow('ServerSummary missing required fields');
36
+
37
+ expect(
38
+ () =>
39
+ new ServerSummary({
40
+ name: 'Test',
41
+ slug: 'test',
42
+ connection_status: 'available',
43
+ })
44
+ ).toThrow('ServerSummary missing required fields');
45
+ });
46
+
47
+ test('fromApiResponse creates ServerSummary', () => {
48
+ const server = ServerSummary.fromApiResponse(validServerData);
49
+ expect(server).toBeInstanceOf(ServerSummary);
50
+ expect(server.id).toBe(validServerData.id);
51
+ });
52
+ });
53
+
54
+ describe('ServerDetail', () => {
55
+ const validDetailData = {
56
+ id: '123e4567-e89b-12d3-a456-426614174000',
57
+ name: 'Test Server',
58
+ slug: 'test-server',
59
+ provider: 'github',
60
+ connection_status: 'connected',
61
+ url: 'https://api.example.com/mcp',
62
+ };
63
+
64
+ test('creates ServerDetail with valid data', () => {
65
+ const server = new ServerDetail(validDetailData);
66
+
67
+ expect(server.id).toBe(validDetailData.id);
68
+ expect(server.name).toBe(validDetailData.name);
69
+ expect(server.slug).toBe(validDetailData.slug);
70
+ expect(server.provider).toBe(validDetailData.provider);
71
+ expect(server.connection_status).toBe(validDetailData.connection_status);
72
+ expect(server.url).toBe(validDetailData.url);
73
+ });
74
+
75
+ test('extends ServerSummary', () => {
76
+ const server = new ServerDetail(validDetailData);
77
+ expect(server).toBeInstanceOf(ServerSummary);
78
+ });
79
+
80
+ test('handles null url', () => {
81
+ const dataWithoutUrl = { ...validDetailData };
82
+ delete dataWithoutUrl.url;
83
+
84
+ const server = new ServerDetail(dataWithoutUrl);
85
+ expect(server.url).toBeNull();
86
+ });
87
+
88
+ test('fromApiResponse creates ServerDetail', () => {
89
+ const server = ServerDetail.fromApiResponse(validDetailData);
90
+ expect(server).toBeInstanceOf(ServerDetail);
91
+ expect(server.url).toBe(validDetailData.url);
92
+ });
93
+ });
94
+
95
+ describe('AgentToken', () => {
96
+ const validTokenData = {
97
+ agent_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
98
+ expires_in: 3600,
99
+ };
100
+
101
+ test('creates AgentToken with valid data', () => {
102
+ const token = new AgentToken(validTokenData);
103
+
104
+ expect(token.agent_token).toBe(validTokenData.agent_token);
105
+ expect(token.expires_in).toBe(validTokenData.expires_in);
106
+ });
107
+
108
+ test('throws error for missing agent_token', () => {
109
+ expect(() => new AgentToken({ expires_in: 3600 })).toThrow(
110
+ 'AgentToken missing required fields'
111
+ );
112
+ });
113
+
114
+ test('throws error for missing expires_in', () => {
115
+ expect(() => new AgentToken({ agent_token: 'token' })).toThrow(
116
+ 'AgentToken missing required fields'
117
+ );
118
+ });
119
+
120
+ test('throws error for invalid expires_in type', () => {
121
+ expect(
122
+ () =>
123
+ new AgentToken({
124
+ agent_token: 'token',
125
+ expires_in: 'not-a-number',
126
+ })
127
+ ).toThrow('AgentToken missing required fields');
128
+ });
129
+
130
+ test('fromApiResponse creates AgentToken', () => {
131
+ const token = AgentToken.fromApiResponse(validTokenData);
132
+ expect(token).toBeInstanceOf(AgentToken);
133
+ expect(token.agent_token).toBe(validTokenData.agent_token);
134
+ });
135
+ });
136
+
137
+ describe('Model Validation', () => {
138
+ test('models validate required fields strictly', () => {
139
+ // ServerSummary requires all core fields
140
+ expect(
141
+ () =>
142
+ new ServerSummary({
143
+ id: 'test-id',
144
+ name: 'Test',
145
+ slug: 'test',
146
+ // missing connection_status
147
+ })
148
+ ).toThrow();
149
+
150
+ // AgentToken requires both fields
151
+ expect(
152
+ () =>
153
+ new AgentToken({
154
+ agent_token: 'token',
155
+ // missing expires_in
156
+ })
157
+ ).toThrow();
158
+ });
159
+
160
+ test('models handle optional fields correctly', () => {
161
+ const serverWithoutProvider = new ServerSummary({
162
+ id: 'test-id',
163
+ name: 'Test',
164
+ slug: 'test',
165
+ connection_status: 'available',
166
+ });
167
+ expect(serverWithoutProvider.provider).toBeNull();
168
+
169
+ const detailWithoutUrl = new ServerDetail({
170
+ id: 'test-id',
171
+ name: 'Test',
172
+ slug: 'test',
173
+ connection_status: 'available',
174
+ });
175
+ expect(detailWithoutUrl.url).toBeNull();
176
+ });
177
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Tests for enhanced token management features.
3
+ *
4
+ * These tests verify the new JWT verification, token refresh, and file locking
5
+ * functionality that mirrors the Python SDK's capabilities.
6
+ */
7
+
8
+ import { verifyJWTLocal, JWTVerificationResult, TokenManager } from '../dist/index.esm.js';
9
+
10
+ describe('Enhanced Token Management', () => {
11
+ describe('JWT Verification', () => {
12
+ const validToken =
13
+ 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5In0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk5OTk5OTk5OTksImF1ZCI6Imh0dHBzOi8vYmFybmRvb3IuYWkvIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmJhcm5kb29yLmFpLyJ9.test-signature';
14
+ const expiredToken =
15
+ 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5In0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjIsImF1ZCI6Imh0dHBzOi8vYmFybmRvb3IuYWkvIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmJhcm5kb29yLmFpLyJ9.test-signature';
16
+
17
+ test('verifyJWTLocal returns INVALID for malformed tokens', async () => {
18
+ const result = await verifyJWTLocal(
19
+ 'invalid-token',
20
+ 'auth.barndoor.ai',
21
+ 'https://barndoor.ai/'
22
+ );
23
+ expect(result).toBe(JWTVerificationResult.INVALID);
24
+ });
25
+
26
+ test('verifyJWTLocal handles network errors gracefully', async () => {
27
+ // This will fail to verify due to network/JWKS issues, but should return INVALID not throw
28
+ const result = await verifyJWTLocal(validToken, 'nonexistent.domain', 'https://barndoor.ai/');
29
+ expect(result).toBe(JWTVerificationResult.INVALID);
30
+ }, 10000); // 10 second timeout
31
+ });
32
+
33
+ describe('TokenManager', () => {
34
+ test('TokenManager can be instantiated', () => {
35
+ const manager = new TokenManager('https://api.example.com');
36
+ expect(manager).toBeInstanceOf(TokenManager);
37
+ });
38
+
39
+ test('TokenManager throws error when no token is found', async () => {
40
+ const manager = new TokenManager('https://api.example.com');
41
+
42
+ // This should throw since there's no token stored
43
+ await expect(manager.getValidToken()).rejects.toThrow();
44
+ });
45
+ });
46
+
47
+ describe('Token Storage Integration', () => {
48
+ test('Token storage functions are available', async () => {
49
+ const { loadUserToken, saveUserToken, clearCachedToken } = await import(
50
+ '../dist/index.esm.js'
51
+ );
52
+
53
+ expect(typeof loadUserToken).toBe('function');
54
+ expect(typeof saveUserToken).toBe('function');
55
+ expect(typeof clearCachedToken).toBe('function');
56
+ });
57
+
58
+ test('validateToken function works with invalid tokens', async () => {
59
+ const { validateToken } = await import('../dist/index.esm.js');
60
+
61
+ const result = await validateToken('invalid-token');
62
+ expect(result).toEqual({ valid: false });
63
+ });
64
+
65
+ test('setTokenLogger function is available', async () => {
66
+ const { setTokenLogger } = await import('../dist/index.esm.js');
67
+
68
+ expect(typeof setTokenLogger).toBe('function');
69
+
70
+ // Test that we can set a custom logger
71
+ const mockLogger = {
72
+ debug: () => {},
73
+ info: () => {},
74
+ warn: () => {},
75
+ error: () => {},
76
+ };
77
+
78
+ expect(() => setTokenLogger(mockLogger)).not.toThrow();
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Tests for token validation fixes from code review.
3
+ *
4
+ * These tests verify that the token validation race condition fix works correctly,
5
+ * ensuring tokens are only validated in appropriate environments and that the
6
+ * _tokenValidated flag is only set when tokens are actually valid.
7
+ */
8
+
9
+ import { BarndoorSDK } from '../dist/index.esm.js';
10
+
11
+ describe('Token Validation Fixes', () => {
12
+ describe('Environment-based validation behavior', () => {
13
+ const validToken =
14
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
15
+ const originalEnv = process.env.BARNDOOR_ENV;
16
+ const originalFetch = global.fetch;
17
+
18
+ afterEach(() => {
19
+ process.env.BARNDOOR_ENV = originalEnv;
20
+ global.fetch = originalFetch;
21
+ });
22
+
23
+ test('skips validation in test environment', async () => {
24
+ process.env.BARNDOOR_ENV = 'test';
25
+
26
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
27
+
28
+ // Should not throw since we're in test environment
29
+ await expect(sdk.ensureValidToken()).resolves.toBeUndefined();
30
+ expect(sdk._tokenValidated).toBe(true);
31
+ });
32
+
33
+ test('skips validation in ci environment', async () => {
34
+ process.env.BARNDOOR_ENV = 'ci';
35
+
36
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
37
+
38
+ // Should not throw since we're in ci environment
39
+ await expect(sdk.ensureValidToken()).resolves.toBeUndefined();
40
+ expect(sdk._tokenValidated).toBe(true);
41
+ });
42
+
43
+ test('validates token in development environment', async () => {
44
+ process.env.BARNDOOR_ENV = 'development';
45
+
46
+ // Mock fetch to simulate token validation failure
47
+ global.fetch = () =>
48
+ Promise.resolve({
49
+ ok: false,
50
+ status: 401,
51
+ });
52
+
53
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
54
+
55
+ // Should throw since we're in development and token validation fails
56
+ await expect(sdk.ensureValidToken()).rejects.toThrow('Token validation failed');
57
+ });
58
+
59
+ test('validates token in production environment', async () => {
60
+ process.env.BARNDOOR_ENV = 'production';
61
+
62
+ // Mock fetch to simulate token validation failure
63
+ global.fetch = () =>
64
+ Promise.resolve({
65
+ ok: false,
66
+ status: 401,
67
+ });
68
+
69
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
70
+
71
+ // Should throw since we're in production and token validation fails
72
+ await expect(sdk.ensureValidToken()).rejects.toThrow('Token validation failed');
73
+ });
74
+
75
+ test('only sets _tokenValidated to true when token is actually valid', async () => {
76
+ process.env.BARNDOOR_ENV = 'production';
77
+
78
+ // Test 1: Valid token should set _tokenValidated to true
79
+ global.fetch = () =>
80
+ Promise.resolve({
81
+ ok: true,
82
+ status: 200,
83
+ });
84
+
85
+ const sdk = new BarndoorSDK('https://api.example.com', { token: validToken });
86
+
87
+ const isValid = await sdk.validateCachedToken();
88
+ expect(isValid).toBe(true);
89
+ expect(sdk._tokenValidated).toBe(true);
90
+
91
+ // Test 2: Invalid token should NOT set _tokenValidated to true
92
+ sdk._tokenValidated = false;
93
+ global.fetch = () =>
94
+ Promise.resolve({
95
+ ok: false,
96
+ status: 401,
97
+ });
98
+
99
+ const isValidSecond = await sdk.validateCachedToken();
100
+ expect(isValidSecond).toBe(false);
101
+ expect(sdk._tokenValidated).toBe(false); // Critical: should remain false for invalid tokens
102
+ });
103
+ });
104
+ });