-
Notifications
You must be signed in to change notification settings - Fork 413
/
Copy pathclient.test.ts
217 lines (185 loc) · 7.95 KB
/
client.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import { NextResponse, type NextRequest } from "next/server";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AccessTokenError, AccessTokenErrorCode } from "../errors";
import { SessionData } from "../types";
import { AuthClient } from "./auth-client"; // Import the actual class for spyOn
import { Auth0Client } from "./client.js";
// Define ENV_VARS at the top level for broader scope
const ENV_VARS = {
DOMAIN: "AUTH0_DOMAIN",
CLIENT_ID: "AUTH0_CLIENT_ID",
CLIENT_SECRET: "AUTH0_CLIENT_SECRET",
CLIENT_ASSERTION_SIGNING_KEY: "AUTH0_CLIENT_ASSERTION_SIGNING_KEY",
APP_BASE_URL: "APP_BASE_URL",
SECRET: "AUTH0_SECRET",
SCOPE: "AUTH0_SCOPE"
};
describe("Auth0Client", () => {
// Store original env vars
const originalEnv = { ...process.env };
// Clear env vars before each test
beforeEach(() => {
vi.resetModules();
// Clear all environment variables that might affect the tests
delete process.env[ENV_VARS.DOMAIN];
delete process.env[ENV_VARS.CLIENT_ID];
delete process.env[ENV_VARS.CLIENT_SECRET];
delete process.env[ENV_VARS.CLIENT_ASSERTION_SIGNING_KEY];
delete process.env[ENV_VARS.APP_BASE_URL];
delete process.env[ENV_VARS.SECRET];
delete process.env[ENV_VARS.SCOPE];
});
// Restore env vars after each test
afterEach(() => {
process.env = { ...originalEnv };
vi.restoreAllMocks(); // Restore mocks created within tests/beforeEach
});
describe("constructor validation", () => {
it("should accept clientSecret as authentication method", () => {
// Set required environment variables with clientSecret
process.env[ENV_VARS.DOMAIN] = "env.auth0.com";
process.env[ENV_VARS.CLIENT_ID] = "env_client_id";
process.env[ENV_VARS.CLIENT_SECRET] = "env_client_secret";
process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.com";
process.env[ENV_VARS.SECRET] = "env_secret";
// Should not throw
const client = new Auth0Client();
// The client should be instantiated successfully
expect(client).toBeInstanceOf(Auth0Client);
});
it("should accept clientAssertionSigningKey as authentication method", () => {
// Set required environment variables with clientAssertionSigningKey instead of clientSecret
process.env[ENV_VARS.DOMAIN] = "env.auth0.com";
process.env[ENV_VARS.CLIENT_ID] = "env_client_id";
process.env[ENV_VARS.CLIENT_ASSERTION_SIGNING_KEY] = "some-signing-key";
process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.com";
process.env[ENV_VARS.SECRET] = "env_secret";
// Should not throw
const client = new Auth0Client();
// The client should be instantiated successfully
expect(client).toBeInstanceOf(Auth0Client);
});
it("should prioritize options over environment variables", () => {
// Set environment variables
process.env[ENV_VARS.DOMAIN] = "env.auth0.com";
process.env[ENV_VARS.CLIENT_ID] = "env_client_id";
process.env[ENV_VARS.CLIENT_SECRET] = "env_client_secret";
process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.com";
process.env[ENV_VARS.SECRET] = "env_secret";
// Provide conflicting options
const options = {
domain: "options.auth0.com",
clientId: "options_client_id",
clientSecret: "options_client_secret",
appBaseUrl: "https://options-app.com",
secret: "options_secret"
};
// Mock the validateAndExtractRequiredOptions to verify which values are used
const mockValidateAndExtractRequiredOptions = vi
.fn()
.mockReturnValue(options);
const originalValidateAndExtractRequiredOptions =
Auth0Client.prototype["validateAndExtractRequiredOptions"];
Auth0Client.prototype["validateAndExtractRequiredOptions"] =
mockValidateAndExtractRequiredOptions;
try {
new Auth0Client(options);
// Check that validateAndExtractRequiredOptions was called with our options
expect(mockValidateAndExtractRequiredOptions).toHaveBeenCalledWith(
options
);
// The first argument of the first call should be our options object
const passedOptions =
mockValidateAndExtractRequiredOptions.mock.calls[0][0];
expect(passedOptions.domain).toBe("options.auth0.com");
expect(passedOptions.clientId).toBe("options_client_id");
} finally {
// Restore the original method
Auth0Client.prototype["validateAndExtractRequiredOptions"] =
originalValidateAndExtractRequiredOptions;
}
});
});
describe("getAccessToken", () => {
const mockSession: SessionData = {
user: { sub: "user123" },
tokenSet: {
accessToken: "old_access_token",
idToken: "old_id_token",
refreshToken: "old_refresh_token",
expiresAt: Date.now() / 1000 - 3600 // Expired
},
internal: {
sid: "mock_sid",
createdAt: Date.now() / 1000 - 7200 // Some time in the past
},
createdAt: Date.now() / 1000
};
// Restore original mock for refreshed token set
const mockRefreshedTokenSet = {
accessToken: "new_access_token",
idToken: "new_id_token",
refreshToken: "new_refresh_token",
expiresAt: Date.now() / 1000 + 3600, // Not expired
scope: "openid profile email"
};
let client: Auth0Client;
let mockGetSession: ReturnType<typeof vi.spyOn>;
let mockSaveToSession: ReturnType<typeof vi.spyOn>;
let mockGetTokenSet: ReturnType<typeof vi.spyOn>; // Re-declare mockGetTokenSet
beforeEach(() => {
// Reset mocks specifically if vi.restoreAllMocks isn't enough
// vi.resetAllMocks(); // Alternative to restoreAllMocks in afterEach
// Set necessary environment variables
process.env[ENV_VARS.DOMAIN] = "test.auth0.com";
process.env[ENV_VARS.CLIENT_ID] = "test_client_id";
process.env[ENV_VARS.CLIENT_SECRET] = "test_client_secret";
process.env[ENV_VARS.APP_BASE_URL] = "https://myapp.test";
process.env[ENV_VARS.SECRET] = "test_secret";
client = new Auth0Client();
// Mock internal methods of Auth0Client
mockGetSession = vi
.spyOn(Auth0Client.prototype as any, "getSession")
.mockResolvedValue(mockSession);
mockSaveToSession = vi
.spyOn(Auth0Client.prototype as any, "saveToSession")
.mockResolvedValue(undefined);
// Restore mocking of getTokenSet directly
mockGetTokenSet = vi
.spyOn(AuthClient.prototype as any, "getTokenSet")
.mockResolvedValue([null, mockRefreshedTokenSet]); // Simulate successful refresh
// Remove mocks for discoverAuthorizationServerMetadata and getClientAuth
// Remove fetch mock
});
it("should throw AccessTokenError if no session exists", async () => {
// Override getSession mock for this specific test
mockGetSession.mockResolvedValue(null);
// Mock request and response objects
const mockReq = { headers: new Headers() } as NextRequest;
const mockRes = new NextResponse();
await expect(
client.getAccessToken(mockReq, mockRes)
).rejects.toThrowError(
new AccessTokenError(
AccessTokenErrorCode.MISSING_SESSION,
"The user does not have an active session."
)
);
// Ensure getTokenSet was not called
expect(mockGetTokenSet).not.toHaveBeenCalled();
});
it("should throw error from getTokenSet if refresh fails", async () => {
const refreshError = new Error("Refresh failed");
// Restore overriding the getTokenSet mock directly
mockGetTokenSet.mockResolvedValue([refreshError, null]);
// Mock request and response objects
const mockReq = { headers: new Headers() } as NextRequest;
const mockRes = new NextResponse();
await expect(
client.getAccessToken(mockReq, mockRes, { refresh: true })
).rejects.toThrowError(refreshError);
// Verify save was not called
expect(mockSaveToSession).not.toHaveBeenCalled();
});
});
});