@markwharton/pwa-core 1.1.0 → 1.2.1
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.
- package/dist/__tests__/auth/apiKey.test.d.ts +1 -0
- package/dist/__tests__/auth/apiKey.test.js +80 -0
- package/dist/__tests__/auth/token.test.d.ts +1 -0
- package/dist/__tests__/auth/token.test.js +189 -0
- package/dist/__tests__/auth/types.test.d.ts +1 -0
- package/dist/__tests__/auth/types.test.js +77 -0
- package/dist/__tests__/client/api.test.d.ts +1 -0
- package/dist/__tests__/client/api.test.js +269 -0
- package/dist/__tests__/client/apiError.test.d.ts +1 -0
- package/dist/__tests__/client/apiError.test.js +58 -0
- package/dist/__tests__/http/responses.test.d.ts +1 -0
- package/dist/__tests__/http/responses.test.js +112 -0
- package/dist/__tests__/http/status.test.d.ts +1 -0
- package/dist/__tests__/http/status.test.js +27 -0
- package/dist/__tests__/storage/client.test.d.ts +1 -0
- package/dist/__tests__/storage/client.test.js +173 -0
- package/dist/__tests__/storage/keys.test.d.ts +1 -0
- package/dist/__tests__/storage/keys.test.js +47 -0
- package/dist/__tests__/types.test.d.ts +1 -0
- package/dist/__tests__/types.test.js +56 -0
- package/dist/auth/apiKey.d.ts +24 -7
- package/dist/auth/apiKey.js +24 -7
- package/dist/auth/token.d.ts +37 -10
- package/dist/auth/token.js +37 -10
- package/dist/auth/types.d.ts +21 -3
- package/dist/auth/types.js +21 -3
- package/dist/client/api.d.ts +70 -9
- package/dist/client/api.js +70 -9
- package/dist/client/apiError.d.ts +22 -5
- package/dist/client/apiError.js +22 -5
- package/dist/http/responses.d.ts +57 -8
- package/dist/http/responses.js +57 -8
- package/dist/storage/client.d.ts +29 -6
- package/dist/storage/client.js +29 -6
- package/dist/types.d.ts +16 -3
- package/dist/types.js +16 -3
- package/package.json +22 -2
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const apiError_1 = require("../../client/apiError");
|
|
5
|
+
(0, vitest_1.describe)('ApiError', () => {
|
|
6
|
+
(0, vitest_1.describe)('constructor', () => {
|
|
7
|
+
(0, vitest_1.it)('sets status, message, and name', () => {
|
|
8
|
+
const error = new apiError_1.ApiError(404, 'Not found');
|
|
9
|
+
(0, vitest_1.expect)(error.status).toBe(404);
|
|
10
|
+
(0, vitest_1.expect)(error.message).toBe('Not found');
|
|
11
|
+
(0, vitest_1.expect)(error.name).toBe('ApiError');
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)('sets optional details', () => {
|
|
14
|
+
const error = new apiError_1.ApiError(400, 'Validation failed', 'Field "email" is required');
|
|
15
|
+
(0, vitest_1.expect)(error.details).toBe('Field "email" is required');
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)('extends Error', () => {
|
|
18
|
+
const error = new apiError_1.ApiError(500, 'Server error');
|
|
19
|
+
(0, vitest_1.expect)(error).toBeInstanceOf(Error);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.describe)('isUnauthorized', () => {
|
|
23
|
+
(0, vitest_1.it)('returns true for 401 status', () => {
|
|
24
|
+
const error = new apiError_1.ApiError(401, 'Unauthorized');
|
|
25
|
+
(0, vitest_1.expect)(error.isUnauthorized()).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('returns false for other statuses', () => {
|
|
28
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(400, 'Bad request').isUnauthorized()).toBe(false);
|
|
29
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(403, 'Forbidden').isUnauthorized()).toBe(false);
|
|
30
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(404, 'Not found').isUnauthorized()).toBe(false);
|
|
31
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(500, 'Server error').isUnauthorized()).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.describe)('isNotFound', () => {
|
|
35
|
+
(0, vitest_1.it)('returns true for 404 status', () => {
|
|
36
|
+
const error = new apiError_1.ApiError(404, 'Not found');
|
|
37
|
+
(0, vitest_1.expect)(error.isNotFound()).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)('returns false for other statuses', () => {
|
|
40
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(400, 'Bad request').isNotFound()).toBe(false);
|
|
41
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(401, 'Unauthorized').isNotFound()).toBe(false);
|
|
42
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(403, 'Forbidden').isNotFound()).toBe(false);
|
|
43
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(500, 'Server error').isNotFound()).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.describe)('isBadRequest', () => {
|
|
47
|
+
(0, vitest_1.it)('returns true for 400 status', () => {
|
|
48
|
+
const error = new apiError_1.ApiError(400, 'Bad request');
|
|
49
|
+
(0, vitest_1.expect)(error.isBadRequest()).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
(0, vitest_1.it)('returns false for other statuses', () => {
|
|
52
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(401, 'Unauthorized').isBadRequest()).toBe(false);
|
|
53
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(403, 'Forbidden').isBadRequest()).toBe(false);
|
|
54
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(404, 'Not found').isBadRequest()).toBe(false);
|
|
55
|
+
(0, vitest_1.expect)(new apiError_1.ApiError(500, 'Server error').isBadRequest()).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const responses_1 = require("../../http/responses");
|
|
5
|
+
const status_1 = require("../../http/status");
|
|
6
|
+
(0, vitest_1.describe)('HTTP response helpers', () => {
|
|
7
|
+
(0, vitest_1.describe)('badRequestResponse', () => {
|
|
8
|
+
(0, vitest_1.it)('returns 400 status with error message', () => {
|
|
9
|
+
const response = (0, responses_1.badRequestResponse)('Invalid input');
|
|
10
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.BAD_REQUEST);
|
|
11
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Invalid input' });
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
(0, vitest_1.describe)('unauthorizedResponse', () => {
|
|
15
|
+
(0, vitest_1.it)('returns 401 status with default message', () => {
|
|
16
|
+
const response = (0, responses_1.unauthorizedResponse)();
|
|
17
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.UNAUTHORIZED);
|
|
18
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Unauthorized' });
|
|
19
|
+
});
|
|
20
|
+
(0, vitest_1.it)('returns 401 status with custom message', () => {
|
|
21
|
+
const response = (0, responses_1.unauthorizedResponse)('Token expired');
|
|
22
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.UNAUTHORIZED);
|
|
23
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Token expired' });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.describe)('forbiddenResponse', () => {
|
|
27
|
+
(0, vitest_1.it)('returns 403 status with default message', () => {
|
|
28
|
+
const response = (0, responses_1.forbiddenResponse)();
|
|
29
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.FORBIDDEN);
|
|
30
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Forbidden' });
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.it)('returns 403 status with custom message', () => {
|
|
33
|
+
const response = (0, responses_1.forbiddenResponse)('Admin access required');
|
|
34
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.FORBIDDEN);
|
|
35
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Admin access required' });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.describe)('notFoundResponse', () => {
|
|
39
|
+
(0, vitest_1.it)('returns 404 status with resource name', () => {
|
|
40
|
+
const response = (0, responses_1.notFoundResponse)('User');
|
|
41
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.NOT_FOUND);
|
|
42
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'User not found' });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.describe)('conflictResponse', () => {
|
|
46
|
+
(0, vitest_1.it)('returns 409 status with error message', () => {
|
|
47
|
+
const response = (0, responses_1.conflictResponse)('Resource already exists');
|
|
48
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.CONFLICT);
|
|
49
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Resource already exists' });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
(0, vitest_1.describe)('handleFunctionError', () => {
|
|
53
|
+
(0, vitest_1.it)('logs error and returns 500 response', () => {
|
|
54
|
+
const mockContext = {
|
|
55
|
+
error: vitest_1.vi.fn()
|
|
56
|
+
};
|
|
57
|
+
const error = new Error('Database connection failed');
|
|
58
|
+
const response = (0, responses_1.handleFunctionError)(error, mockContext);
|
|
59
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.INTERNAL_ERROR);
|
|
60
|
+
(0, vitest_1.expect)(response.jsonBody).toEqual({ error: 'Internal server error' });
|
|
61
|
+
(0, vitest_1.expect)(mockContext.error).toHaveBeenCalledWith('Function error: Database connection failed');
|
|
62
|
+
});
|
|
63
|
+
(0, vitest_1.it)('handles non-Error objects', () => {
|
|
64
|
+
const mockContext = {
|
|
65
|
+
error: vitest_1.vi.fn()
|
|
66
|
+
};
|
|
67
|
+
const response = (0, responses_1.handleFunctionError)('string error', mockContext);
|
|
68
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.INTERNAL_ERROR);
|
|
69
|
+
(0, vitest_1.expect)(mockContext.error).toHaveBeenCalledWith('Function error: Unknown error');
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)('handles null/undefined errors', () => {
|
|
72
|
+
const mockContext = {
|
|
73
|
+
error: vitest_1.vi.fn()
|
|
74
|
+
};
|
|
75
|
+
const response = (0, responses_1.handleFunctionError)(null, mockContext);
|
|
76
|
+
(0, vitest_1.expect)(response.status).toBe(status_1.HTTP_STATUS.INTERNAL_ERROR);
|
|
77
|
+
(0, vitest_1.expect)(mockContext.error).toHaveBeenCalledWith('Function error: Unknown error');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
(0, vitest_1.describe)('isNotFoundError', () => {
|
|
81
|
+
(0, vitest_1.it)('returns true for 404 error', () => {
|
|
82
|
+
const error = Object.assign(new Error('Not found'), { statusCode: 404 });
|
|
83
|
+
(0, vitest_1.expect)((0, responses_1.isNotFoundError)(error)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.it)('returns false for other status codes', () => {
|
|
86
|
+
const error = Object.assign(new Error('Conflict'), { statusCode: 409 });
|
|
87
|
+
(0, vitest_1.expect)((0, responses_1.isNotFoundError)(error)).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)('returns false for non-Error objects', () => {
|
|
90
|
+
(0, vitest_1.expect)((0, responses_1.isNotFoundError)({ statusCode: 404 })).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
(0, vitest_1.it)('returns false for errors without statusCode', () => {
|
|
93
|
+
(0, vitest_1.expect)((0, responses_1.isNotFoundError)(new Error('Not found'))).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
(0, vitest_1.describe)('isConflictError', () => {
|
|
97
|
+
(0, vitest_1.it)('returns true for 409 error', () => {
|
|
98
|
+
const error = Object.assign(new Error('Conflict'), { statusCode: 409 });
|
|
99
|
+
(0, vitest_1.expect)((0, responses_1.isConflictError)(error)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)('returns false for other status codes', () => {
|
|
102
|
+
const error = Object.assign(new Error('Not found'), { statusCode: 404 });
|
|
103
|
+
(0, vitest_1.expect)((0, responses_1.isConflictError)(error)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)('returns false for non-Error objects', () => {
|
|
106
|
+
(0, vitest_1.expect)((0, responses_1.isConflictError)({ statusCode: 409 })).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
(0, vitest_1.it)('returns false for errors without statusCode', () => {
|
|
109
|
+
(0, vitest_1.expect)((0, responses_1.isConflictError)(new Error('Conflict'))).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const status_1 = require("../../http/status");
|
|
5
|
+
(0, vitest_1.describe)('HTTP_STATUS', () => {
|
|
6
|
+
(0, vitest_1.it)('has correct status codes', () => {
|
|
7
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.OK).toBe(200);
|
|
8
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.CREATED).toBe(201);
|
|
9
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.NO_CONTENT).toBe(204);
|
|
10
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.BAD_REQUEST).toBe(400);
|
|
11
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.UNAUTHORIZED).toBe(401);
|
|
12
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.FORBIDDEN).toBe(403);
|
|
13
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.NOT_FOUND).toBe(404);
|
|
14
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.CONFLICT).toBe(409);
|
|
15
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.GONE).toBe(410);
|
|
16
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.INTERNAL_ERROR).toBe(500);
|
|
17
|
+
(0, vitest_1.expect)(status_1.HTTP_STATUS.SERVICE_UNAVAILABLE).toBe(503);
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('is immutable (const assertion)', () => {
|
|
20
|
+
// TypeScript const assertion makes the object readonly
|
|
21
|
+
// This test verifies the values haven't been accidentally modified
|
|
22
|
+
const statusCodes = Object.values(status_1.HTTP_STATUS);
|
|
23
|
+
(0, vitest_1.expect)(statusCodes).toContain(200);
|
|
24
|
+
(0, vitest_1.expect)(statusCodes).toContain(404);
|
|
25
|
+
(0, vitest_1.expect)(statusCodes).toContain(500);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const vitest_1 = require("vitest");
|
|
37
|
+
// Create mock functions
|
|
38
|
+
const mockCreateTable = vitest_1.vi.fn();
|
|
39
|
+
const mockFromConnectionString = vitest_1.vi.fn();
|
|
40
|
+
// Mock @azure/data-tables
|
|
41
|
+
vitest_1.vi.mock('@azure/data-tables', () => {
|
|
42
|
+
class MockTableClient {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.createTable = mockCreateTable;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
MockTableClient.fromConnectionString = mockFromConnectionString;
|
|
48
|
+
return {
|
|
49
|
+
TableClient: MockTableClient,
|
|
50
|
+
TableServiceClient: class MockTableServiceClient {
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
// Mock @azure/identity
|
|
55
|
+
vitest_1.vi.mock('@azure/identity', () => ({
|
|
56
|
+
DefaultAzureCredential: class MockDefaultAzureCredential {
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
59
|
+
(0, vitest_1.describe)('Storage client', () => {
|
|
60
|
+
(0, vitest_1.beforeEach)(() => {
|
|
61
|
+
vitest_1.vi.resetModules();
|
|
62
|
+
vitest_1.vi.clearAllMocks();
|
|
63
|
+
mockCreateTable.mockResolvedValue(undefined);
|
|
64
|
+
mockFromConnectionString.mockReturnValue({
|
|
65
|
+
createTable: vitest_1.vi.fn().mockResolvedValue(undefined)
|
|
66
|
+
});
|
|
67
|
+
// Reset environment
|
|
68
|
+
delete process.env.STORAGE_ACCOUNT_NAME;
|
|
69
|
+
delete process.env.AzureWebJobsStorage;
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.describe)('initStorage', () => {
|
|
72
|
+
(0, vitest_1.it)('accepts configuration object', async () => {
|
|
73
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
74
|
+
initStorage({ accountName: 'myaccount' });
|
|
75
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.it)('accepts connection string', async () => {
|
|
78
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
79
|
+
initStorage({ connectionString: 'DefaultEndpointsProtocol=https;AccountName=test' });
|
|
80
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.describe)('initStorageFromEnv', () => {
|
|
84
|
+
(0, vitest_1.it)('reads STORAGE_ACCOUNT_NAME', async () => {
|
|
85
|
+
process.env.STORAGE_ACCOUNT_NAME = 'testaccount';
|
|
86
|
+
const { initStorageFromEnv, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
87
|
+
initStorageFromEnv();
|
|
88
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)('reads AzureWebJobsStorage', async () => {
|
|
91
|
+
process.env.AzureWebJobsStorage = 'UseDevelopmentStorage=true';
|
|
92
|
+
const { initStorageFromEnv, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
93
|
+
initStorageFromEnv();
|
|
94
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.describe)('useManagedIdentity', () => {
|
|
98
|
+
(0, vitest_1.it)('returns true when accountName is set without development storage', async () => {
|
|
99
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
100
|
+
initStorage({ accountName: 'prodaccount' });
|
|
101
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
(0, vitest_1.it)('returns false when using development storage', async () => {
|
|
104
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
105
|
+
initStorage({
|
|
106
|
+
accountName: 'devaccount',
|
|
107
|
+
connectionString: 'UseDevelopmentStorage=true'
|
|
108
|
+
});
|
|
109
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.it)('returns false when only connection string is set', async () => {
|
|
112
|
+
const { initStorage, useManagedIdentity } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
113
|
+
initStorage({ connectionString: 'DefaultEndpointsProtocol=https' });
|
|
114
|
+
(0, vitest_1.expect)(useManagedIdentity()).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
(0, vitest_1.describe)('getTableClient', () => {
|
|
118
|
+
(0, vitest_1.it)('throws if storage not configured', async () => {
|
|
119
|
+
const { getTableClient } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
120
|
+
await (0, vitest_1.expect)(getTableClient('test')).rejects.toThrow('Storage not configured. Set STORAGE_ACCOUNT_NAME or AzureWebJobsStorage.');
|
|
121
|
+
});
|
|
122
|
+
(0, vitest_1.it)('creates table client with managed identity', async () => {
|
|
123
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
124
|
+
initStorage({ accountName: 'testaccount' });
|
|
125
|
+
clearTableClientCache();
|
|
126
|
+
const client = await getTableClient('mytable');
|
|
127
|
+
(0, vitest_1.expect)(client).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
(0, vitest_1.it)('creates table client with connection string', async () => {
|
|
130
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
131
|
+
initStorage({ connectionString: 'DefaultEndpointsProtocol=https;AccountName=test' });
|
|
132
|
+
clearTableClientCache();
|
|
133
|
+
await getTableClient('mytable');
|
|
134
|
+
(0, vitest_1.expect)(mockFromConnectionString).toHaveBeenCalledWith('DefaultEndpointsProtocol=https;AccountName=test', 'mytable');
|
|
135
|
+
});
|
|
136
|
+
(0, vitest_1.it)('caches table client', async () => {
|
|
137
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
138
|
+
initStorage({ accountName: 'testaccount' });
|
|
139
|
+
clearTableClientCache();
|
|
140
|
+
const client1 = await getTableClient('mytable');
|
|
141
|
+
const client2 = await getTableClient('mytable');
|
|
142
|
+
(0, vitest_1.expect)(client1).toBe(client2);
|
|
143
|
+
});
|
|
144
|
+
(0, vitest_1.it)('handles 409 conflict (table exists)', async () => {
|
|
145
|
+
mockCreateTable.mockRejectedValue({ statusCode: 409 });
|
|
146
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
147
|
+
initStorage({ accountName: 'testaccount' });
|
|
148
|
+
clearTableClientCache();
|
|
149
|
+
// Should not throw
|
|
150
|
+
const client = await getTableClient('existingtable');
|
|
151
|
+
(0, vitest_1.expect)(client).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('throws on other errors', async () => {
|
|
154
|
+
mockCreateTable.mockRejectedValue({ statusCode: 500, message: 'Server error' });
|
|
155
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
156
|
+
initStorage({ accountName: 'testaccount' });
|
|
157
|
+
clearTableClientCache();
|
|
158
|
+
await (0, vitest_1.expect)(getTableClient('mytable')).rejects.toMatchObject({ statusCode: 500 });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
(0, vitest_1.describe)('clearTableClientCache', () => {
|
|
162
|
+
(0, vitest_1.it)('clears cached clients', async () => {
|
|
163
|
+
const { initStorage, getTableClient, clearTableClientCache } = await Promise.resolve().then(() => __importStar(require('../../storage/client')));
|
|
164
|
+
initStorage({ accountName: 'testaccount' });
|
|
165
|
+
clearTableClientCache();
|
|
166
|
+
await getTableClient('mytable');
|
|
167
|
+
clearTableClientCache();
|
|
168
|
+
// After clearing, a new call should create a new client
|
|
169
|
+
const client2 = await getTableClient('mytable');
|
|
170
|
+
(0, vitest_1.expect)(client2).toBeDefined();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const keys_1 = require("../../storage/keys");
|
|
5
|
+
(0, vitest_1.describe)('generateRowKey', () => {
|
|
6
|
+
(0, vitest_1.it)('returns 32-character hex string', () => {
|
|
7
|
+
const key = (0, keys_1.generateRowKey)('test-identifier');
|
|
8
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
9
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
10
|
+
});
|
|
11
|
+
(0, vitest_1.it)('produces consistent hash for same input', () => {
|
|
12
|
+
const key1 = (0, keys_1.generateRowKey)('same-input');
|
|
13
|
+
const key2 = (0, keys_1.generateRowKey)('same-input');
|
|
14
|
+
(0, vitest_1.expect)(key1).toBe(key2);
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)('produces different hashes for different inputs', () => {
|
|
17
|
+
const key1 = (0, keys_1.generateRowKey)('input-1');
|
|
18
|
+
const key2 = (0, keys_1.generateRowKey)('input-2');
|
|
19
|
+
(0, vitest_1.expect)(key1).not.toBe(key2);
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)('handles empty string', () => {
|
|
22
|
+
const key = (0, keys_1.generateRowKey)('');
|
|
23
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
24
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.it)('handles special characters', () => {
|
|
27
|
+
const key = (0, keys_1.generateRowKey)('https://example.com/path?query=value&foo=bar');
|
|
28
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
29
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)('handles unicode characters', () => {
|
|
32
|
+
const key = (0, keys_1.generateRowKey)('日本語テスト');
|
|
33
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
34
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
35
|
+
});
|
|
36
|
+
(0, vitest_1.it)('handles long strings', () => {
|
|
37
|
+
const longString = 'x'.repeat(10000);
|
|
38
|
+
const key = (0, keys_1.generateRowKey)(longString);
|
|
39
|
+
(0, vitest_1.expect)(key).toHaveLength(32);
|
|
40
|
+
(0, vitest_1.expect)(key).toMatch(/^[a-f0-9]+$/);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)('produces URL-safe keys', () => {
|
|
43
|
+
const key = (0, keys_1.generateRowKey)('some-identifier');
|
|
44
|
+
// Row keys should not contain these characters
|
|
45
|
+
(0, vitest_1.expect)(key).not.toMatch(/[\/\\#?\s]/);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
(0, vitest_1.describe)('Result helpers', () => {
|
|
6
|
+
(0, vitest_1.describe)('ok', () => {
|
|
7
|
+
(0, vitest_1.it)('creates success result with data', () => {
|
|
8
|
+
const result = (0, types_1.ok)({ name: 'test' });
|
|
9
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
10
|
+
(0, vitest_1.expect)(result.data).toEqual({ name: 'test' });
|
|
11
|
+
(0, vitest_1.expect)(result.error).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)('works with primitive values', () => {
|
|
14
|
+
(0, vitest_1.expect)((0, types_1.ok)(42).data).toBe(42);
|
|
15
|
+
(0, vitest_1.expect)((0, types_1.ok)('hello').data).toBe('hello');
|
|
16
|
+
(0, vitest_1.expect)((0, types_1.ok)(true).data).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.it)('works with null', () => {
|
|
19
|
+
const result = (0, types_1.ok)(null);
|
|
20
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
21
|
+
(0, vitest_1.expect)(result.data).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.describe)('okVoid', () => {
|
|
25
|
+
(0, vitest_1.it)('creates success result without data', () => {
|
|
26
|
+
const result = (0, types_1.okVoid)();
|
|
27
|
+
(0, vitest_1.expect)(result.ok).toBe(true);
|
|
28
|
+
(0, vitest_1.expect)(result.data).toBeUndefined();
|
|
29
|
+
(0, vitest_1.expect)(result.error).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
(0, vitest_1.describe)('err', () => {
|
|
33
|
+
(0, vitest_1.it)('creates failure result with error message', () => {
|
|
34
|
+
const result = (0, types_1.err)('Something went wrong');
|
|
35
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
36
|
+
(0, vitest_1.expect)(result.error).toBe('Something went wrong');
|
|
37
|
+
(0, vitest_1.expect)(result.data).toBeUndefined();
|
|
38
|
+
(0, vitest_1.expect)(result.statusCode).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
(0, vitest_1.it)('creates failure result with status code', () => {
|
|
41
|
+
const result = (0, types_1.err)('Not found', 404);
|
|
42
|
+
(0, vitest_1.expect)(result.ok).toBe(false);
|
|
43
|
+
(0, vitest_1.expect)(result.error).toBe('Not found');
|
|
44
|
+
(0, vitest_1.expect)(result.statusCode).toBe(404);
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)('excludes statusCode when not provided', () => {
|
|
47
|
+
const result = (0, types_1.err)('Error');
|
|
48
|
+
(0, vitest_1.expect)('statusCode' in result).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)('includes statusCode when provided', () => {
|
|
51
|
+
const result = (0, types_1.err)('Error', 500);
|
|
52
|
+
(0, vitest_1.expect)('statusCode' in result).toBe(true);
|
|
53
|
+
(0, vitest_1.expect)(result.statusCode).toBe(500);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
package/dist/auth/apiKey.d.ts
CHANGED
|
@@ -3,7 +3,11 @@ import { Result } from '../types';
|
|
|
3
3
|
* API Key utilities for machine-to-machine authentication
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Extracts API key from the X-API-Key header.
|
|
7
|
+
* @param request - Request object with headers.get() method
|
|
8
|
+
* @returns The API key string, or null if not present
|
|
9
|
+
* @example
|
|
10
|
+
* const apiKey = extractApiKey(request);
|
|
7
11
|
*/
|
|
8
12
|
export declare function extractApiKey(request: {
|
|
9
13
|
headers: {
|
|
@@ -11,17 +15,30 @@ export declare function extractApiKey(request: {
|
|
|
11
15
|
};
|
|
12
16
|
}): string | null;
|
|
13
17
|
/**
|
|
14
|
-
*
|
|
15
|
-
* Store this hash,
|
|
18
|
+
* Hashes an API key using SHA-256 for secure storage.
|
|
19
|
+
* Store this hash in your database, never the raw key.
|
|
20
|
+
* @param apiKey - The raw API key to hash
|
|
21
|
+
* @returns The SHA-256 hash as a hex string
|
|
22
|
+
* @example
|
|
23
|
+
* const hash = hashApiKey(rawKey);
|
|
24
|
+
* await db.save({ apiKeyHash: hash });
|
|
16
25
|
*/
|
|
17
26
|
export declare function hashApiKey(apiKey: string): string;
|
|
18
27
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
28
|
+
* Validates an API key against a stored hash.
|
|
29
|
+
* @param apiKey - The API key from the request
|
|
30
|
+
* @param storedHash - The hash stored in your database
|
|
31
|
+
* @returns Result with ok=true if valid, or error message if invalid
|
|
32
|
+
* @example
|
|
33
|
+
* const result = validateApiKey(apiKey, user.apiKeyHash);
|
|
34
|
+
* if (!result.ok) return httpUnauthorized();
|
|
22
35
|
*/
|
|
23
36
|
export declare function validateApiKey(apiKey: string, storedHash: string): Result<void>;
|
|
24
37
|
/**
|
|
25
|
-
*
|
|
38
|
+
* Generates a cryptographically secure API key.
|
|
39
|
+
* @returns A random 64-character hex string (32 bytes)
|
|
40
|
+
* @example
|
|
41
|
+
* const apiKey = generateApiKey();
|
|
42
|
+
* // Return to user once, store hash in database
|
|
26
43
|
*/
|
|
27
44
|
export declare function generateApiKey(): string;
|
package/dist/auth/apiKey.js
CHANGED
|
@@ -10,22 +10,35 @@ const types_1 = require("../types");
|
|
|
10
10
|
* API Key utilities for machine-to-machine authentication
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Extracts API key from the X-API-Key header.
|
|
14
|
+
* @param request - Request object with headers.get() method
|
|
15
|
+
* @returns The API key string, or null if not present
|
|
16
|
+
* @example
|
|
17
|
+
* const apiKey = extractApiKey(request);
|
|
14
18
|
*/
|
|
15
19
|
function extractApiKey(request) {
|
|
16
20
|
return request.headers.get('X-API-Key');
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
20
|
-
* Store this hash,
|
|
23
|
+
* Hashes an API key using SHA-256 for secure storage.
|
|
24
|
+
* Store this hash in your database, never the raw key.
|
|
25
|
+
* @param apiKey - The raw API key to hash
|
|
26
|
+
* @returns The SHA-256 hash as a hex string
|
|
27
|
+
* @example
|
|
28
|
+
* const hash = hashApiKey(rawKey);
|
|
29
|
+
* await db.save({ apiKeyHash: hash });
|
|
21
30
|
*/
|
|
22
31
|
function hashApiKey(apiKey) {
|
|
23
32
|
return (0, crypto_1.createHash)('sha256').update(apiKey).digest('hex');
|
|
24
33
|
}
|
|
25
34
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @
|
|
35
|
+
* Validates an API key against a stored hash.
|
|
36
|
+
* @param apiKey - The API key from the request
|
|
37
|
+
* @param storedHash - The hash stored in your database
|
|
38
|
+
* @returns Result with ok=true if valid, or error message if invalid
|
|
39
|
+
* @example
|
|
40
|
+
* const result = validateApiKey(apiKey, user.apiKeyHash);
|
|
41
|
+
* if (!result.ok) return httpUnauthorized();
|
|
29
42
|
*/
|
|
30
43
|
function validateApiKey(apiKey, storedHash) {
|
|
31
44
|
const keyHash = hashApiKey(apiKey);
|
|
@@ -35,7 +48,11 @@ function validateApiKey(apiKey, storedHash) {
|
|
|
35
48
|
return (0, types_1.err)('Invalid API key');
|
|
36
49
|
}
|
|
37
50
|
/**
|
|
38
|
-
*
|
|
51
|
+
* Generates a cryptographically secure API key.
|
|
52
|
+
* @returns A random 64-character hex string (32 bytes)
|
|
53
|
+
* @example
|
|
54
|
+
* const apiKey = generateApiKey();
|
|
55
|
+
* // Return to user once, store hash in database
|
|
39
56
|
*/
|
|
40
57
|
function generateApiKey() {
|
|
41
58
|
return (0, crypto_1.randomBytes)(32).toString('hex');
|