Skip to content

Commit 126af7c

Browse files
jtomaszewskiclaude
andcommitted
test(auth): add integration tests for API token refresh flow
TC-AUTH-017 covers: - Login with remember=true returns refreshToken - Login without remember does not return refreshToken - POST /api/session/refresh returns new accessToken - Error handling for missing/invalid refresh tokens - New access token can be used for authenticated requests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 1e6a292 commit 126af7c

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { test, expect } from '@playwright/test';
2+
import { postForm } from '@open-mercato/core/modules/core/__integration__/helpers/api';
3+
import { DEFAULT_CREDENTIALS } from '@open-mercato/core/modules/core/__integration__/helpers/auth';
4+
5+
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
6+
7+
/**
8+
* TC-AUTH-017: API Token Refresh Flow
9+
*
10+
* Tests the header-based token refresh for mobile/API clients:
11+
* - Login with remember=true returns refreshToken in response
12+
* - POST /api/session/refresh accepts refreshToken in JSON body
13+
* - Returns new accessToken on success
14+
* - Returns proper errors for invalid/missing tokens
15+
*/
16+
test.describe('TC-AUTH-017: API Token Refresh Flow', () => {
17+
test('login with remember=true returns refreshToken in response', async ({ request }) => {
18+
const { email, password } = DEFAULT_CREDENTIALS.admin;
19+
20+
const response = await postForm(request, '/api/auth/login', {
21+
email,
22+
password,
23+
remember: '1',
24+
});
25+
26+
expect(response.status()).toBe(200);
27+
const body = await response.json();
28+
expect(body.ok).toBe(true);
29+
expect(body.token).toBeTruthy();
30+
expect(body.refreshToken).toBeTruthy();
31+
expect(typeof body.refreshToken).toBe('string');
32+
expect(body.refreshToken.length).toBeGreaterThan(0);
33+
});
34+
35+
test('login without remember does not return refreshToken', async ({ request }) => {
36+
const { email, password } = DEFAULT_CREDENTIALS.admin;
37+
38+
const response = await postForm(request, '/api/auth/login', {
39+
email,
40+
password,
41+
});
42+
43+
expect(response.status()).toBe(200);
44+
const body = await response.json();
45+
expect(body.ok).toBe(true);
46+
expect(body.token).toBeTruthy();
47+
expect(body.refreshToken).toBeUndefined();
48+
});
49+
50+
test('POST /api/session/refresh returns new accessToken for valid refreshToken', async ({ request }) => {
51+
const { email, password } = DEFAULT_CREDENTIALS.admin;
52+
53+
// First, login with remember=true to get a refresh token
54+
const loginResponse = await postForm(request, '/api/auth/login', {
55+
email,
56+
password,
57+
remember: '1',
58+
});
59+
expect(loginResponse.status()).toBe(200);
60+
const loginBody = await loginResponse.json();
61+
const refreshToken = loginBody.refreshToken;
62+
expect(refreshToken).toBeTruthy();
63+
64+
// Now use the refresh token to get a new access token
65+
const refreshResponse = await request.post(`${BASE_URL}/api/session/refresh`, {
66+
headers: { 'Content-Type': 'application/json' },
67+
data: JSON.stringify({ refreshToken }),
68+
});
69+
70+
expect(refreshResponse.status()).toBe(200);
71+
const refreshBody = await refreshResponse.json();
72+
expect(refreshBody.ok).toBe(true);
73+
expect(refreshBody.accessToken).toBeTruthy();
74+
expect(typeof refreshBody.accessToken).toBe('string');
75+
expect(refreshBody.expiresIn).toBe(60 * 60 * 8); // 8 hours in seconds
76+
});
77+
78+
test('POST /api/session/refresh returns 400 for missing refreshToken', async ({ request }) => {
79+
const refreshResponse = await request.post(`${BASE_URL}/api/session/refresh`, {
80+
headers: { 'Content-Type': 'application/json' },
81+
data: JSON.stringify({}),
82+
});
83+
84+
expect(refreshResponse.status()).toBe(400);
85+
const body = await refreshResponse.json();
86+
expect(body.ok).toBe(false);
87+
expect(body.error).toBeTruthy();
88+
});
89+
90+
test('POST /api/session/refresh returns 401 for invalid refreshToken', async ({ request }) => {
91+
const refreshResponse = await request.post(`${BASE_URL}/api/session/refresh`, {
92+
headers: { 'Content-Type': 'application/json' },
93+
data: JSON.stringify({ refreshToken: 'invalid-token-that-does-not-exist' }),
94+
});
95+
96+
expect(refreshResponse.status()).toBe(401);
97+
const body = await refreshResponse.json();
98+
expect(body.ok).toBe(false);
99+
expect(body.error).toBeTruthy();
100+
});
101+
102+
test('POST /api/session/refresh returns 400 for invalid JSON body', async ({ request }) => {
103+
const refreshResponse = await request.post(`${BASE_URL}/api/session/refresh`, {
104+
headers: { 'Content-Type': 'application/json' },
105+
data: 'not-valid-json',
106+
});
107+
108+
expect(refreshResponse.status()).toBe(400);
109+
const body = await refreshResponse.json();
110+
expect(body.ok).toBe(false);
111+
});
112+
113+
test('new accessToken from refresh can be used for authenticated requests', async ({ request }) => {
114+
const { email, password } = DEFAULT_CREDENTIALS.admin;
115+
116+
// Login with remember=true
117+
const loginResponse = await postForm(request, '/api/auth/login', {
118+
email,
119+
password,
120+
remember: '1',
121+
});
122+
const { refreshToken } = await loginResponse.json();
123+
124+
// Get new access token via refresh
125+
const refreshResponse = await request.post(`${BASE_URL}/api/session/refresh`, {
126+
headers: { 'Content-Type': 'application/json' },
127+
data: JSON.stringify({ refreshToken }),
128+
});
129+
const { accessToken } = await refreshResponse.json();
130+
131+
// Use the new access token for an authenticated API call
132+
const profileResponse = await request.get(`${BASE_URL}/api/auth/profile`, {
133+
headers: { Authorization: `Bearer ${accessToken}` },
134+
});
135+
136+
expect(profileResponse.status()).toBe(200);
137+
const profile = await profileResponse.json();
138+
expect(profile.email).toBe(email);
139+
});
140+
});

0 commit comments

Comments
 (0)