@strivacity/sdk-next 3.0.2 → 4.0.0-beta.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.
Files changed (91) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1983 -428
  3. package/dist/client/components.cjs +1 -0
  4. package/dist/client/components.d.ts +1 -0
  5. package/dist/client/components.mjs +1 -0
  6. package/dist/client/hooks.cjs +1 -0
  7. package/dist/client/hooks.d.ts +1 -0
  8. package/dist/client/hooks.mjs +1 -0
  9. package/dist/client/index.cjs +1 -0
  10. package/dist/client/index.d.ts +9 -0
  11. package/dist/client/index.mjs +1 -0
  12. package/dist/client/storages.cjs +1 -0
  13. package/dist/client/storages.d.ts +1 -0
  14. package/dist/client/storages.mjs +1 -0
  15. package/dist/client/types.cjs +1 -0
  16. package/dist/client/types.d.ts +1 -0
  17. package/dist/client/types.mjs +1 -0
  18. package/dist/client/utils.cjs +1 -0
  19. package/dist/client/utils.d.ts +1 -0
  20. package/dist/client/utils.mjs +1 -0
  21. package/dist/errors.cjs +1 -0
  22. package/dist/errors.d.ts +1 -0
  23. package/dist/errors.mjs +1 -0
  24. package/dist/server/handlers/api.cjs +2 -0
  25. package/dist/server/handlers/api.cjs.map +1 -0
  26. package/dist/server/handlers/api.d.ts +4 -0
  27. package/dist/server/handlers/api.mjs +2 -0
  28. package/dist/server/handlers/api.mjs.map +1 -0
  29. package/dist/server/handlers/route.cjs +2 -0
  30. package/dist/server/handlers/route.cjs.map +1 -0
  31. package/dist/server/handlers/route.d.ts +4 -0
  32. package/dist/server/handlers/route.mjs +2 -0
  33. package/dist/server/handlers/route.mjs.map +1 -0
  34. package/dist/server/index.cjs +1 -0
  35. package/dist/server/index.d.ts +8 -0
  36. package/dist/server/index.mjs +1 -0
  37. package/dist/server/sdk.cjs +2 -0
  38. package/dist/server/sdk.cjs.map +1 -0
  39. package/dist/server/sdk.d.ts +8 -0
  40. package/dist/server/sdk.mjs +2 -0
  41. package/dist/server/sdk.mjs.map +1 -0
  42. package/dist/server/storages.cjs +2 -0
  43. package/dist/server/storages.cjs.map +1 -0
  44. package/dist/server/storages.d.ts +20 -0
  45. package/dist/server/storages.mjs +2 -0
  46. package/dist/server/storages.mjs.map +1 -0
  47. package/dist/server/types.cjs +1 -0
  48. package/dist/server/types.d.ts +101 -0
  49. package/dist/server/types.mjs +1 -0
  50. package/dist/server/utils.cjs +2 -0
  51. package/dist/server/utils.cjs.map +1 -0
  52. package/dist/server/utils.d.ts +28 -0
  53. package/dist/server/utils.mjs +2 -0
  54. package/dist/server/utils.mjs.map +1 -0
  55. package/dist/types.cjs +1 -2
  56. package/dist/types.d.ts +2 -176
  57. package/dist/types.mjs +1 -2
  58. package/package.json +48 -10
  59. package/testing/tests/client/components.spec.ts +10 -0
  60. package/testing/tests/client/hooks.spec.ts +10 -0
  61. package/testing/tests/client/index.spec.ts +106 -0
  62. package/testing/tests/client/storages.spec.ts +10 -0
  63. package/testing/tests/client/utils.spec.ts +10 -0
  64. package/testing/tests/server/handlers/api.spec.ts +110 -0
  65. package/testing/tests/server/handlers/route.spec.ts +173 -0
  66. package/testing/tests/server/index.spec.ts +103 -0
  67. package/testing/tests/server/sdk.spec.ts +250 -0
  68. package/testing/tests/server/storages.spec.ts +353 -0
  69. package/testing/tests/server/utils.spec.ts +110 -0
  70. package/dist/AuthProvider.cjs +0 -2
  71. package/dist/AuthProvider.cjs.map +0 -1
  72. package/dist/AuthProvider.d.ts +0 -7
  73. package/dist/AuthProvider.mjs +0 -2
  74. package/dist/AuthProvider.mjs.map +0 -1
  75. package/dist/LoginRenderer.cjs +0 -2
  76. package/dist/LoginRenderer.cjs.map +0 -1
  77. package/dist/LoginRenderer.d.ts +0 -20
  78. package/dist/LoginRenderer.mjs +0 -2
  79. package/dist/LoginRenderer.mjs.map +0 -1
  80. package/dist/composables.cjs +0 -2
  81. package/dist/composables.cjs.map +0 -1
  82. package/dist/composables.d.ts +0 -12
  83. package/dist/composables.mjs +0 -2
  84. package/dist/composables.mjs.map +0 -1
  85. package/dist/index.cjs +0 -2
  86. package/dist/index.cjs.map +0 -1
  87. package/dist/index.d.ts +0 -13
  88. package/dist/index.mjs +0 -2
  89. package/dist/index.mjs.map +0 -1
  90. package/dist/types.cjs.map +0 -1
  91. package/dist/types.mjs.map +0 -1
@@ -0,0 +1,173 @@
1
+ import { describe, test, expect, vi, afterEach } from 'vitest';
2
+ import { createMockBaseServerSDK } from '@strivacity/testing/mocks/sdk';
3
+ import { appRouteHandlerFactory, pageRouteHandlerFactory } from '../../../../src/server/handlers/route';
4
+ import type { BaseServerSDK, PagesRouterRequest } from '../../../../src/server/types';
5
+
6
+ vi.mock('next/navigation', () => ({ redirect: vi.fn() }));
7
+
8
+ type Sdk = BaseServerSDK<Request | PagesRouterRequest | undefined>;
9
+
10
+ afterEach(() => {
11
+ vi.resetModules();
12
+ });
13
+
14
+ describe('appRouteHandlerFactory', () => {
15
+ test('redirects to the bare loginUri when there is no session and no returnTo', async () => {
16
+ const { redirect } = await import('next/navigation');
17
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
18
+ const handler = vi.fn();
19
+ const route = appRouteHandlerFactory(sdk)(handler);
20
+
21
+ await route({});
22
+
23
+ expect(redirect).toHaveBeenCalledWith('/login');
24
+ });
25
+
26
+ test('redirects with an encoded returnTo query param when returnTo is a string', async () => {
27
+ const { redirect } = await import('next/navigation');
28
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
29
+ const handler = vi.fn();
30
+ const route = appRouteHandlerFactory(sdk)(handler, { returnTo: 'https://brandtegrity.io/dashboard' });
31
+
32
+ await route({});
33
+
34
+ expect(redirect).toHaveBeenCalledWith('/login?returnTo=https%3A%2F%2Fbrandtegrity.io%2Fdashboard');
35
+ });
36
+
37
+ test('resolves a function returnTo with the route params before redirecting', async () => {
38
+ const { redirect } = await import('next/navigation');
39
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
40
+ const handler = vi.fn();
41
+ const returnTo = vi.fn().mockResolvedValue('/profile');
42
+ const route = appRouteHandlerFactory(sdk)(handler, { returnTo });
43
+ const params = { params: Promise.resolve({ id: '1' }) };
44
+
45
+ await route(params);
46
+
47
+ expect(returnTo).toHaveBeenCalledWith(params);
48
+ expect(redirect).toHaveBeenCalledWith('/login?returnTo=%2Fprofile');
49
+ });
50
+
51
+ test('still calls the wrapped handler after an unauthenticated redirect that does not itself abort execution', async () => {
52
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
53
+ const handler = vi.fn().mockResolvedValue('handler-result');
54
+ const route = appRouteHandlerFactory(sdk)(handler);
55
+ const params = { id: '1' };
56
+
57
+ const result = await route(params);
58
+
59
+ expect(handler).toHaveBeenCalledWith({ id: '1', session: null });
60
+ expect(result).toBe('handler-result');
61
+ });
62
+
63
+ test('does not call the wrapped handler when the redirect throws, as a real Next.js redirect() does', async () => {
64
+ const { redirect } = await import('next/navigation');
65
+ vi.mocked(redirect).mockImplementationOnce(() => {
66
+ throw new Error('NEXT_REDIRECT');
67
+ });
68
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
69
+ const handler = vi.fn();
70
+ const route = appRouteHandlerFactory(sdk)(handler);
71
+
72
+ await expect(route({})).rejects.toThrow('NEXT_REDIRECT');
73
+ expect(handler).not.toHaveBeenCalled();
74
+ });
75
+
76
+ test('calls the wrapped handler with the params and session, without redirecting, when there is a session', async () => {
77
+ const { redirect } = await import('next/navigation');
78
+ const session = { access_token: 'access-token' };
79
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' }, getSession: vi.fn().mockResolvedValue(session) });
80
+ const handler = vi.fn().mockResolvedValue('handler-result');
81
+ const route = appRouteHandlerFactory(sdk)(handler);
82
+
83
+ const result = await route({ id: '1' });
84
+
85
+ expect(redirect).not.toHaveBeenCalled();
86
+ expect(handler).toHaveBeenCalledWith({ id: '1', session });
87
+ expect(result).toBe('handler-result');
88
+ });
89
+ });
90
+
91
+ describe('pageRouteHandlerFactory', () => {
92
+ test('returns a redirect to loginUri with the resolvedUrl as returnTo when there is no session and no returnTo option', async () => {
93
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
94
+ const route = pageRouteHandlerFactory(sdk)();
95
+
96
+ const result = await route({ resolvedUrl: '/dashboard' } as never);
97
+
98
+ expect(result).toEqual({ redirect: { destination: '/login?returnTo=%2Fdashboard', permanent: false } });
99
+ });
100
+
101
+ test('prefers the returnTo option over ctx.resolvedUrl', async () => {
102
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
103
+ const route = pageRouteHandlerFactory(sdk)({ returnTo: '/profile' });
104
+
105
+ const result = await route({ resolvedUrl: '/dashboard' } as never);
106
+
107
+ expect(result).toEqual({ redirect: { destination: '/login?returnTo=%2Fprofile', permanent: false } });
108
+ });
109
+
110
+ test('does not call getServerSideProps when there is no session', async () => {
111
+ const sdk = createMockBaseServerSDK<Sdk>({ options: { loginUri: '/login' } });
112
+ const getServerSideProps = vi.fn();
113
+ const route = pageRouteHandlerFactory(sdk)({ getServerSideProps });
114
+
115
+ await route({ resolvedUrl: '/dashboard' } as never);
116
+
117
+ expect(getServerSideProps).not.toHaveBeenCalled();
118
+ });
119
+
120
+ test('returns only the session prop when there is a session and no getServerSideProps', async () => {
121
+ const session = { access_token: 'access-token' };
122
+ const sdk = createMockBaseServerSDK<Sdk>({ getSession: vi.fn().mockResolvedValue(session) });
123
+ const route = pageRouteHandlerFactory(sdk)();
124
+
125
+ const result = await route({} as never);
126
+
127
+ expect(result).toEqual({ props: { session } });
128
+ });
129
+
130
+ test('merges the session into the props returned synchronously by getServerSideProps', async () => {
131
+ const session = { access_token: 'access-token' };
132
+ const sdk = createMockBaseServerSDK<Sdk>({ getSession: vi.fn().mockResolvedValue(session) });
133
+ const getServerSideProps = vi.fn().mockReturnValue({ props: { foo: 'bar' } });
134
+ const route = pageRouteHandlerFactory(sdk)({ getServerSideProps });
135
+
136
+ const result = await route({} as never);
137
+
138
+ expect(result).toEqual({ props: { session, foo: 'bar' } });
139
+ });
140
+
141
+ test('merges the session into props returned as a Promise by getServerSideProps', async () => {
142
+ const session = { access_token: 'access-token' };
143
+ const sdk = createMockBaseServerSDK<Sdk>({ getSession: vi.fn().mockResolvedValue(session) });
144
+ const getServerSideProps = vi.fn().mockReturnValue({ props: Promise.resolve({ foo: 'bar' }) });
145
+ const route = pageRouteHandlerFactory(sdk)({ getServerSideProps });
146
+
147
+ const result = await route({} as never);
148
+
149
+ expect(result).toEqual({ props: { session, foo: 'bar' } });
150
+ });
151
+
152
+ test('awaits an async getServerSideProps that itself returns a Promise', async () => {
153
+ const session = { access_token: 'access-token' };
154
+ const sdk = createMockBaseServerSDK<Sdk>({ getSession: vi.fn().mockResolvedValue(session) });
155
+ const getServerSideProps = vi.fn().mockResolvedValue({ props: { foo: 'bar' } });
156
+ const route = pageRouteHandlerFactory(sdk)({ getServerSideProps });
157
+
158
+ const result = await route({} as never);
159
+
160
+ expect(result).toEqual({ props: { session, foo: 'bar' } });
161
+ });
162
+
163
+ test('still adds a session prop when getServerSideProps returns a result with no props, such as notFound', async () => {
164
+ const session = { access_token: 'access-token' };
165
+ const sdk = createMockBaseServerSDK<Sdk>({ getSession: vi.fn().mockResolvedValue(session) });
166
+ const getServerSideProps = vi.fn().mockReturnValue({ notFound: true });
167
+ const route = pageRouteHandlerFactory(sdk)({ getServerSideProps });
168
+
169
+ const result = await route({} as never);
170
+
171
+ expect(result).toEqual({ notFound: true, props: { session } });
172
+ });
173
+ });
@@ -0,0 +1,103 @@
1
+ import { test, expect } from 'vitest';
2
+ import * as serverIndex from '../../../src/server/index';
3
+
4
+ test('should export the correct things', () => {
5
+ const expectedExports = [
6
+ 'AlgorithmTypeList',
7
+ 'BACKCHANNEL_LOGOUT_EVENT',
8
+ 'COOKIE_CHUNK_SIZE',
9
+ 'COOKIE_CONTEXT',
10
+ 'ConfigurationError',
11
+ 'DEFAULT_COOKIE_MAX_AGE',
12
+ 'DISALLOWED_PROXY_HEADERS',
13
+ 'FallbackError',
14
+ 'GrantTypeList',
15
+ 'InternalError',
16
+ 'NetworkError',
17
+ 'OidcError',
18
+ 'PopupBlockedError',
19
+ 'PopupClosedError',
20
+ 'ProtocolError',
21
+ 'RETURN_TO_COOKIE',
22
+ 'ResponseModeList',
23
+ 'ResponseTypeList',
24
+ 'ServerError',
25
+ 'SessionExpiredError',
26
+ 'SubjectTypeList',
27
+ 'TokenEndpointAuthMethodList',
28
+ 'UnsupportedFlowError',
29
+ 'assertWebAuthnCredential',
30
+ 'buildAuthorizationUrl',
31
+ 'buildCookieString',
32
+ 'buildEndSessionUrl',
33
+ 'collectFromNextUrl',
34
+ 'createBaseFlow',
35
+ 'createCacheAPIStorage',
36
+ 'createDefaultLogging',
37
+ 'createEmbeddedFlow',
38
+ 'createEncryptedCookieStorage',
39
+ 'createHttpClient',
40
+ 'createIndexedDBStorage',
41
+ 'createLocalStorage',
42
+ 'createMemoryStorage',
43
+ 'createNativeFlow',
44
+ 'createPopupFlow',
45
+ 'createRedirectFlow',
46
+ 'createServerMemoryStorage',
47
+ 'createServerSDK',
48
+ 'createServerStateStorage',
49
+ 'createSession',
50
+ 'createSessionIdCookieStorage',
51
+ 'createSessionStorage',
52
+ 'createState',
53
+ 'createWebAuthnCredential',
54
+ 'createWorkerStorage',
55
+ 'decodeBase64URL',
56
+ 'decodeJwt',
57
+ 'decryptString',
58
+ 'defaultOptions',
59
+ 'encodeBase64URL',
60
+ 'encryptString',
61
+ 'exchangeCode',
62
+ 'fetchFlowEntry',
63
+ 'finalizeLoginSession',
64
+ 'flushSetCookies',
65
+ 'generateCodeChallenge',
66
+ 'generateCodeVerifier',
67
+ 'generateJwt',
68
+ 'generateRandomHex',
69
+ 'getDefaultFlowState',
70
+ 'getDiscoveryDocument',
71
+ 'getSDKOptions',
72
+ 'getServerSDKOptions',
73
+ 'handleWorkerStorageRequests',
74
+ 'injectScript',
75
+ 'isSessionExpired',
76
+ 'loadSession',
77
+ 'parseCookieHeader',
78
+ 'parseState',
79
+ 'popupCallbackHandler',
80
+ 'popupUrlHandler',
81
+ 'proxyResponse',
82
+ 'pushSetCookie',
83
+ 'redirectCallbackHandler',
84
+ 'redirectUrlHandler',
85
+ 'refreshToken',
86
+ 'revokeToken',
87
+ 'serializeCookie',
88
+ 'serializeSession',
89
+ 'serializeState',
90
+ 'submitLoginForm',
91
+ 'throwHttpError',
92
+ 'throwTokenEndpointError',
93
+ 'timestamp',
94
+ 'toNextRequest',
95
+ 'toSafeRedirect',
96
+ 'unflattenObject',
97
+ 'verifyIdToken',
98
+ 'verifyJwt',
99
+ ];
100
+
101
+ expect(Object.keys(serverIndex)).toHaveLength(expectedExports.length);
102
+ expect(Object.keys(serverIndex).sort()).toEqual(expectedExports.sort());
103
+ });
@@ -0,0 +1,250 @@
1
+ import type { NextApiRequest, NextApiResponse } from 'next';
2
+ import { describe, test, expect, vi } from 'vitest';
3
+ import { NextRequest } from 'next/server';
4
+ import { cookies } from 'next/headers';
5
+ import { createBaseServerSDK } from '@strivacity/sdk-core/server';
6
+ import { ConfigurationError } from '@strivacity/sdk-core/utils/errors';
7
+ import { createMockBaseServerSDK } from '@strivacity/testing/mocks/sdk';
8
+ import { createServerSDK } from '../../../src/server/sdk';
9
+ import { apiAppRouteHandlerFactory, apiPageRouteHandlerFactory } from '../../../src/server/handlers/api';
10
+ import { appRouteHandlerFactory, pageRouteHandlerFactory } from '../../../src/server/handlers/route';
11
+ import type { BaseServerSDK, NextServerSDKInitConfig, PagesRouterRequest } from '../../../src/server/types';
12
+
13
+ vi.mock('@strivacity/sdk-core/server', async (importOriginal) => ({
14
+ ...(await importOriginal<typeof import('@strivacity/sdk-core/server')>()),
15
+ createBaseServerSDK: vi.fn(),
16
+ }));
17
+
18
+ vi.mock('../../../src/server/handlers/api', () => ({
19
+ apiAppRouteHandlerFactory: vi.fn(),
20
+ apiPageRouteHandlerFactory: vi.fn(),
21
+ }));
22
+
23
+ vi.mock('../../../src/server/handlers/route', () => ({
24
+ appRouteHandlerFactory: vi.fn(),
25
+ pageRouteHandlerFactory: vi.fn(),
26
+ }));
27
+
28
+ vi.mock('next/headers', () => ({ cookies: vi.fn() }));
29
+
30
+ type Event = NextRequest | Request | PagesRouterRequest | undefined;
31
+
32
+ const validInitConfig = { issuer: 'https://brandtegrity.io', clientId: 'client-id', redirectUri: 'https://brandtegrity.io/callback', secret: 'my-secret' };
33
+
34
+ function installBase(overrides?: Record<string, unknown>) {
35
+ const base = createMockBaseServerSDK<BaseServerSDK<Event>>(overrides);
36
+ vi.mocked(createBaseServerSDK).mockReturnValue(base);
37
+
38
+ return base;
39
+ }
40
+
41
+ describe('createServerSDK', () => {
42
+ test('throws a ConfigurationError when neither a storage nor a secret is given', () => {
43
+ expect(() => createServerSDK({ issuer: 'https://brandtegrity.io', clientId: 'client-id', redirectUri: 'https://brandtegrity.io/callback' })).toThrow(
44
+ new ConfigurationError('Secret is required for encrypted cookie storage'),
45
+ );
46
+ expect(createBaseServerSDK).not.toHaveBeenCalled();
47
+ });
48
+
49
+ test('builds a default encrypted cookie storage and a default cookieMaxAge from the secret when no storage is given', () => {
50
+ installBase();
51
+
52
+ createServerSDK(validInitConfig);
53
+
54
+ const initConfig = vi.mocked(createBaseServerSDK).mock.calls[0][1] as NextServerSDKInitConfig;
55
+ expect(initConfig.storage).toBeDefined();
56
+ expect(initConfig.cookieMaxAge).toBe(30 * 24 * 60 * 60);
57
+ });
58
+
59
+ test('does not override an explicitly given cookieMaxAge', () => {
60
+ installBase();
61
+
62
+ createServerSDK({ ...validInitConfig, cookieMaxAge: 100 });
63
+
64
+ const initConfig = vi.mocked(createBaseServerSDK).mock.calls[0][1] as NextServerSDKInitConfig;
65
+ expect(initConfig.cookieMaxAge).toBe(100);
66
+ });
67
+
68
+ test('does not require a secret or build a default storage when a storage is explicitly given', () => {
69
+ installBase();
70
+ const storage = { get: vi.fn(), set: vi.fn(), delete: vi.fn() };
71
+
72
+ createServerSDK({
73
+ issuer: 'https://brandtegrity.io',
74
+ clientId: 'client-id',
75
+ redirectUri: 'https://brandtegrity.io/callback',
76
+ storage: storage,
77
+ });
78
+
79
+ const initConfig = vi.mocked(createBaseServerSDK).mock.calls[0][1] as NextServerSDKInitConfig;
80
+ expect(initConfig.storage).toBe(storage);
81
+ });
82
+
83
+ test('wires a real toRequest/redirect adapter into the (unmocked) base server sdk', async () => {
84
+ const actual = await vi.importActual<typeof import('@strivacity/sdk-core/server')>('@strivacity/sdk-core/server');
85
+ vi.mocked(createBaseServerSDK).mockImplementation(actual.createBaseServerSDK);
86
+ // NOTE: the default encrypted cookie storage's set/delete branch on `res` rather than `req`, so cleaning up the (nonexistent) session
87
+ // during a revoke still falls back to next/headers' cookies(), even though a real request is given here.
88
+ vi.mocked(cookies).mockResolvedValue({ get: vi.fn(), set: vi.fn(), delete: vi.fn() } as never);
89
+ const sdk = createServerSDK(validInitConfig);
90
+
91
+ const response = await sdk.handleRevoke(new Request('https://brandtegrity.io/auth/revoke'));
92
+
93
+ expect(response.status).toBe(302);
94
+ expect(response.headers.get('location')).toBe('https://brandtegrity.io/');
95
+ });
96
+
97
+ test('exposes the base server sdk options', () => {
98
+ const base = installBase({ options: { loginUri: '/login' } });
99
+ const sdk = createServerSDK(validInitConfig);
100
+
101
+ expect(sdk.options).toBe(base.options);
102
+ });
103
+
104
+ test.each([
105
+ ['getSession', [{}]],
106
+ ['refreshSession', [{}]],
107
+ ['revokeSession', [{}]],
108
+ ['getEntrySession', ['https://brandtegrity.io/entry']],
109
+ ['completeLogin', [{ code: 'abc' }, {}]],
110
+ ['logout', ['https://brandtegrity.io', {}]],
111
+ ['handleLogin', [{}]],
112
+ ['handleRegister', [{}]],
113
+ ['handleCallback', [{}]],
114
+ ['handleRefresh', [{}]],
115
+ ['handleRevoke', [{}]],
116
+ ['handleEntry', [{}]],
117
+ ['handleLogout', [{}]],
118
+ ['handleBackChannelLogout', [{}]],
119
+ ] as const)('%s calls through to the base server sdk with the same arguments', async (method, args) => {
120
+ const base = installBase();
121
+ const sdk = createServerSDK(validInitConfig);
122
+
123
+ await (sdk[method] as (...a: Array<unknown>) => unknown)(...args);
124
+
125
+ expect(base[method]).toHaveBeenCalledWith(...args);
126
+ });
127
+
128
+ test('updateSession calls through to the base server sdk, preserving the (session, event) argument order', async () => {
129
+ const base = installBase();
130
+ const sdk = createServerSDK(validInitConfig);
131
+ const session = { access_token: 'access-token' };
132
+ const event = {};
133
+
134
+ await sdk.updateSession(session, event as never);
135
+
136
+ expect(base.updateSession).toHaveBeenCalledWith(session, event);
137
+ });
138
+
139
+ describe('handler', () => {
140
+ test('returns the base handler response unchanged when it resolves a Response', async () => {
141
+ const response = new Response(null, { status: 200 });
142
+ const base = installBase({ handler: vi.fn().mockResolvedValue(response) });
143
+ const sdk = createServerSDK(validInitConfig);
144
+
145
+ await expect(sdk.handler({})).resolves.toBe(response);
146
+ expect(base.handler).toHaveBeenCalledWith({});
147
+ });
148
+
149
+ test('returns a 404 NextResponse when the base handler resolves null', async () => {
150
+ installBase({ handler: vi.fn().mockResolvedValue(null) });
151
+ const sdk = createServerSDK(validInitConfig);
152
+
153
+ const response = await sdk.handler({});
154
+
155
+ expect(response.status).toBe(404);
156
+ });
157
+ });
158
+
159
+ describe('withApiAuthRequired', () => {
160
+ test('dispatches to the App Router factory when the request is a Request instance', async () => {
161
+ const base = installBase();
162
+ const sdk = createServerSDK(validInitConfig);
163
+ const wrappedAppRoute = vi.fn().mockResolvedValue(new Response());
164
+ const appRouteHandler = vi.fn().mockReturnValue(wrappedAppRoute);
165
+ vi.mocked(apiAppRouteHandlerFactory).mockReturnValue(appRouteHandler as never);
166
+ const apiRoute = vi.fn();
167
+ const req = new NextRequest('https://brandtegrity.io/api/secret');
168
+ const ctx = { params: Promise.resolve({}) };
169
+
170
+ await sdk.withApiAuthRequired(apiRoute)(req, ctx);
171
+
172
+ expect(apiAppRouteHandlerFactory).toHaveBeenCalledWith(base);
173
+ expect(appRouteHandler).toHaveBeenCalledWith(apiRoute);
174
+ expect(wrappedAppRoute).toHaveBeenCalledWith(req, ctx);
175
+ });
176
+
177
+ test('defaults the App Router context to an empty object when none is given', async () => {
178
+ installBase();
179
+ const sdk = createServerSDK(validInitConfig);
180
+ const wrappedAppRoute = vi.fn().mockResolvedValue(new Response());
181
+ vi.mocked(apiAppRouteHandlerFactory).mockReturnValue(vi.fn().mockReturnValue(wrappedAppRoute) as never);
182
+ const req = new NextRequest('https://brandtegrity.io/api/secret');
183
+
184
+ await sdk.withApiAuthRequired(vi.fn())(req);
185
+
186
+ expect(wrappedAppRoute).toHaveBeenCalledWith(req, {});
187
+ });
188
+
189
+ test('dispatches to the Pages Router factory when the request is not a Request instance', async () => {
190
+ const base = installBase();
191
+ const sdk = createServerSDK(validInitConfig);
192
+ const wrappedPageRoute = vi.fn().mockResolvedValue(undefined);
193
+ const pageRouteHandler = vi.fn().mockReturnValue(wrappedPageRoute);
194
+ vi.mocked(apiPageRouteHandlerFactory).mockReturnValue(pageRouteHandler as never);
195
+ const apiRoute = vi.fn();
196
+ const req = {} as NextApiRequest;
197
+ const res = {} as NextApiResponse;
198
+
199
+ await sdk.withApiAuthRequired(apiRoute)(req, res);
200
+
201
+ expect(apiPageRouteHandlerFactory).toHaveBeenCalledWith(base);
202
+ expect(pageRouteHandler).toHaveBeenCalledWith(apiRoute);
203
+ expect(wrappedPageRoute).toHaveBeenCalledWith(req, res);
204
+ });
205
+ });
206
+
207
+ describe('withAuthGuard', () => {
208
+ test('dispatches to the App Router factory when given a function', () => {
209
+ const base = installBase();
210
+ const sdk = createServerSDK(validInitConfig);
211
+ const wrappedRoute = vi.fn();
212
+ const routeHandler = vi.fn().mockReturnValue(wrappedRoute);
213
+ vi.mocked(appRouteHandlerFactory).mockReturnValue(routeHandler as never);
214
+ const fn = vi.fn();
215
+ const opts = { returnTo: '/profile' };
216
+
217
+ const result = sdk.withAuthGuard(fn, opts);
218
+
219
+ expect(appRouteHandlerFactory).toHaveBeenCalledWith(base);
220
+ expect(routeHandler).toHaveBeenCalledWith(fn, opts);
221
+ expect(result).toBe(wrappedRoute);
222
+ });
223
+
224
+ test('dispatches to the Pages Router factory when given options (or nothing)', () => {
225
+ const base = installBase();
226
+ const sdk = createServerSDK(validInitConfig);
227
+ const wrappedRoute = vi.fn();
228
+ const routeHandler = vi.fn().mockReturnValue(wrappedRoute);
229
+ vi.mocked(pageRouteHandlerFactory).mockReturnValue(routeHandler as never);
230
+ const opts = { returnTo: '/profile' };
231
+
232
+ const result = sdk.withAuthGuard(opts);
233
+
234
+ expect(pageRouteHandlerFactory).toHaveBeenCalledWith(base);
235
+ expect(routeHandler).toHaveBeenCalledWith(opts);
236
+ expect(result).toBe(wrappedRoute);
237
+ });
238
+
239
+ test('dispatches to the Pages Router factory with undefined when called with no arguments', () => {
240
+ installBase();
241
+ const sdk = createServerSDK(validInitConfig);
242
+ const routeHandler = vi.fn().mockReturnValue(vi.fn());
243
+ vi.mocked(pageRouteHandlerFactory).mockReturnValue(routeHandler as never);
244
+
245
+ sdk.withAuthGuard();
246
+
247
+ expect(routeHandler).toHaveBeenCalledWith(undefined);
248
+ });
249
+ });
250
+ });