Skip to content

Commit 61ad3d1

Browse files
committed
chore(FR-2616): record Story 1 PR links in dev-plan metadata
1 parent 0470513 commit 61ad3d1

2 files changed

Lines changed: 130 additions & 25 deletions

File tree

.specs/draft-stoken-login-boundary/metadata.json

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,109 @@
1515
"title": "Story 1: Introduce STokenLoginBoundary component",
1616
"prStackBranch": "feat/stoken-login-boundary-story-1-component",
1717
"subtasks": [
18-
{ "key": "FR-2628", "title": "[1.1] Rename useEduAppApiEndpoint to useResolvedApiEndpoint" },
19-
{ "key": "FR-2629", "title": "[1.2] Extend tokenLogin helper with optional extraParams" },
20-
{ "key": "FR-2630", "title": "[1.3] Add useSToken hook (nuqs) with stoken fallback and deprecation warning" },
21-
{ "key": "FR-2631", "title": "[1.4] Implement STokenLoginBoundary component core" },
22-
{ "key": "FR-2632", "title": "[1.5] Default fallback and error card UI" },
23-
{ "key": "FR-2633", "title": "[1.6] Unit tests for STokenLoginBoundary state transitions and invariants" },
24-
{ "key": "FR-2634", "title": "[1.7] CI grep rule forbidding URL APIs in STokenLoginBoundary" },
25-
{ "key": "FR-2635", "title": "[1.8] Verify Manager/Webserver cookie decoding for sToken (Q5)" }
18+
{
19+
"key": "FR-2628",
20+
"title": "[1.1] Rename useEduAppApiEndpoint to useResolvedApiEndpoint",
21+
"prNumber": 6850,
22+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6850"
23+
},
24+
{
25+
"key": "FR-2629",
26+
"title": "[1.2] Extend tokenLogin helper with optional extraParams",
27+
"prNumber": 6851,
28+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6851"
29+
},
30+
{
31+
"key": "FR-2630",
32+
"title": "[1.3] Add useSToken hook (nuqs) with stoken fallback and deprecation warning",
33+
"prNumber": 6852,
34+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6852"
35+
},
36+
{
37+
"key": "FR-2631",
38+
"title": "[1.4] Implement STokenLoginBoundary component core",
39+
"prNumber": 6853,
40+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6853"
41+
},
42+
{
43+
"key": "FR-2632",
44+
"title": "[1.5] Default fallback and error card UI",
45+
"prNumber": 6855,
46+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6855"
47+
},
48+
{
49+
"key": "FR-2633",
50+
"title": "[1.6] Unit tests for STokenLoginBoundary state transitions and invariants",
51+
"prNumber": 6856,
52+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6856"
53+
},
54+
{
55+
"key": "FR-2634",
56+
"title": "[1.7] CI grep rule forbidding URL APIs in STokenLoginBoundary",
57+
"prNumber": 6854,
58+
"prUrl": "https://github.com/lablup/backend.ai-webui/pull/6854"
59+
},
60+
{
61+
"key": "FR-2635",
62+
"title": "[1.8] Verify Manager/Webserver cookie decoding for sToken (Q5)",
63+
"prNumber": null,
64+
"prUrl": null,
65+
"note": "investigation only; no PR; Jira comment with findings"
66+
}
2667
]
2768
},
2869
{
2970
"key": "FR-2626",
3071
"url": "https://lablup.atlassian.net/browse/FR-2626",
3172
"title": "Story 2: Migrate LoginView sToken path to STokenLoginBoundary",
3273
"prStackBranch": "feat/stoken-login-boundary-story-2-loginview",
33-
"blockedBy": ["FR-2625"],
74+
"blockedBy": [
75+
"FR-2625"
76+
],
3477
"subtasks": [
35-
{ "key": "FR-2636", "title": "[2.1] Wrap / and /interactive-login routes with STokenLoginBoundary conditionally" },
36-
{ "key": "FR-2637", "title": "[2.2] Move postConnectSetup side effects into route-level onSuccess" },
37-
{ "key": "FR-2638", "title": "[2.3] Remove connectUsingSession sToken branch and URL parsing in LoginView" },
38-
{ "key": "FR-2639", "title": "[2.4] E2E regression for LoginView sToken entry" }
78+
{
79+
"key": "FR-2636",
80+
"title": "[2.1] Wrap / and /interactive-login routes with STokenLoginBoundary conditionally"
81+
},
82+
{
83+
"key": "FR-2637",
84+
"title": "[2.2] Move postConnectSetup side effects into route-level onSuccess"
85+
},
86+
{
87+
"key": "FR-2638",
88+
"title": "[2.3] Remove connectUsingSession sToken branch and URL parsing in LoginView"
89+
},
90+
{
91+
"key": "FR-2639",
92+
"title": "[2.4] E2E regression for LoginView sToken entry"
93+
}
3994
]
4095
},
4196
{
4297
"key": "FR-2627",
4398
"url": "https://lablup.atlassian.net/browse/FR-2627",
4499
"title": "Story 3: Migrate EduAppLauncher sToken path to STokenLoginBoundary",
45100
"prStackBranch": "feat/stoken-login-boundary-story-3-eduapplauncher",
46-
"blockedBy": ["FR-2625"],
101+
"blockedBy": [
102+
"FR-2625"
103+
],
47104
"subtasks": [
48-
{ "key": "FR-2640", "title": "[3.1] Investigate get_user_credential(sToken) liveness (Q8)" },
49-
{ "key": "FR-2641", "title": "[3.2] Wrap /edu-applauncher and /applauncher routes with STokenLoginBoundary" },
50-
{ "key": "FR-2642", "title": "[3.3] Remove _token_login and manual backend-ai-connected dispatch in EduAppLauncher" },
51-
{ "key": "FR-2643", "title": "[3.4] E2E regression for EduApp sToken entry with and without session_id" }
105+
{
106+
"key": "FR-2640",
107+
"title": "[3.1] Investigate get_user_credential(sToken) liveness (Q8)"
108+
},
109+
{
110+
"key": "FR-2641",
111+
"title": "[3.2] Wrap /edu-applauncher and /applauncher routes with STokenLoginBoundary"
112+
},
113+
{
114+
"key": "FR-2642",
115+
"title": "[3.3] Remove _token_login and manual backend-ai-connected dispatch in EduAppLauncher"
116+
},
117+
{
118+
"key": "FR-2643",
119+
"title": "[3.4] E2E regression for EduApp sToken entry with and without session_id"
120+
}
52121
]
53122
}
54123
],
@@ -78,5 +147,5 @@
78147
},
79148
"sourceIssues": [],
80149
"createdAt": "2026-04-21T00:00:00Z",
81-
"notes": "Epic FR-2616 filed. Spec Task FR-2618 created as sub-issue under the Epic. Spec PR #6828 resolves FR-2618. Dev plan landed on same PR stack. 3 Stories + 16 sub-tasks created; see stories[] for keys. Directory still uses draft- prefix; rename to FR-2616-stoken-login-boundary as a follow-up."
150+
"notes": "Epic FR-2616 filed. Spec Task FR-2618 created as sub-issue under the Epic. Spec PR #6828 resolves FR-2618. Dev plan landed on same PR stack. Story 1 implementation PRs #6850-#6856 submitted (FR-2628-FR-2634 + test). FR-2635 closed as investigation-only (comment on issue). Directory still uses draft- prefix; rename to FR-2616-stoken-login-boundary as a follow-up."
82151
}

react/src/components/STokenLoginBoundary.test.tsx

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,19 @@
1313
* can provoke without network (missing-token, endpoint-unresolved,
1414
* server-unreachable, token-invalid).
1515
* - `errorFallback` prop replaces the built-in card for every kind (Q4).
16-
* - Source file does not reference forbidden URL APIs (mirrors the CI
17-
* grep in FR-2634 so local `pnpm test` catches regressions too).
16+
* - Source file does not reference forbidden URL APIs — this is the
17+
* CI-enforced gate for the spec FR-2616 "URL 파라미터 파싱 규약" rule.
1818
*
1919
* The helper module is mocked entirely because the real
2020
* `createBackendAIClient` instantiates a global `BackendAIClient` that is
2121
* only available at runtime in the browser bundle.
2222
*/
2323
import '../../__test__/matchMedia.mock.js';
24-
import { createBackendAIClient, tokenLogin } from '../helper/loginSessionAuth';
24+
import {
25+
connectViaGQL,
26+
createBackendAIClient,
27+
tokenLogin,
28+
} from '../helper/loginSessionAuth';
2529
import * as endpointModule from '../hooks/useResolvedApiEndpoint';
2630
import {
2731
STokenLoginBoundary,
@@ -60,6 +64,7 @@ jest.mock('../helper/loginSessionAuth', () => ({
6064
__esModule: true,
6165
createBackendAIClient: jest.fn(),
6266
tokenLogin: jest.fn(),
67+
connectViaGQL: jest.fn(),
6368
}));
6469

6570
jest.mock('backend.ai-ui', () => {
@@ -88,6 +93,9 @@ jest.mock('jotai', () => {
8893
const mockedCreateBackendAIClient =
8994
createBackendAIClient as jest.MockedFunction<typeof createBackendAIClient>;
9095
const mockedTokenLogin = tokenLogin as jest.MockedFunction<typeof tokenLogin>;
96+
const mockedConnectViaGQL = connectViaGQL as jest.MockedFunction<
97+
typeof connectViaGQL
98+
>;
9199
const endpointState = (
92100
endpointModule as unknown as { __endpointState: { endpoint: string } }
93101
).__endpointState;
@@ -97,12 +105,15 @@ const setEndpoint = (next: string) => {
97105

98106
type FakeClient = {
99107
get_manager_version: jest.Mock;
108+
check_login: jest.Mock;
100109
token_login: jest.Mock;
101110
};
102111

103-
const buildFakeClient = (): FakeClient => ({
112+
const buildFakeClient = (overrides: Partial<FakeClient> = {}): FakeClient => ({
104113
get_manager_version: jest.fn().mockResolvedValue('1.0'),
114+
check_login: jest.fn().mockResolvedValue(false),
105115
token_login: jest.fn().mockResolvedValue(true),
116+
...overrides,
106117
});
107118

108119
const renderBoundary = (
@@ -150,6 +161,7 @@ beforeEach(() => {
150161
clientConfig: {},
151162
}));
152163
mockedTokenLogin.mockResolvedValue([]);
164+
mockedConnectViaGQL.mockResolvedValue([]);
153165
});
154166

155167
afterEach(() => {
@@ -242,6 +254,30 @@ describe('STokenLoginBoundary', () => {
242254
expect(connectedEventCount).toBe(0);
243255
});
244256

257+
test('skips token_login when the browser already holds a valid session', async () => {
258+
// Reuse the existing session: check_login resolves truthy.
259+
const client = buildFakeClient({
260+
check_login: jest.fn().mockResolvedValue(true),
261+
});
262+
mockedCreateBackendAIClient.mockImplementation(() => ({
263+
client,
264+
clientConfig: {},
265+
}));
266+
const onSuccess = jest.fn();
267+
renderBoundary({ onSuccess });
268+
269+
await waitFor(() => {
270+
expect(screen.getByText('children-rendered')).toBeInTheDocument();
271+
});
272+
// Fast-path assertions: no re-authentication was attempted, but the
273+
// GQL wiring still ran and the connected event fired exactly once so
274+
// Relay and plugin subscribers unblock.
275+
expect(mockedTokenLogin).not.toHaveBeenCalled();
276+
expect(mockedConnectViaGQL).toHaveBeenCalledTimes(1);
277+
expect(connectedEventCount).toBe(1);
278+
expect(onSuccess).toHaveBeenCalledTimes(1);
279+
});
280+
245281
test('errorFallback replaces the built-in card for every kind', async () => {
246282
const tokenErr = new Error('bad token');
247283
mockedTokenLogin.mockRejectedValue(tokenErr);
@@ -268,8 +304,8 @@ describe('STokenLoginBoundary source', () => {
268304
'utf8',
269305
);
270306
// Strip block comments and line comments so the rule documentation in
271-
// the file header is not flagged. This mirrors the awk-driven stripping
272-
// inside scripts/check-stoken-login-boundary-url-free.sh.
307+
// the file header (which intentionally names the forbidden APIs) is
308+
// not flagged.
273309
const stripped = source
274310
.replace(/\/\*[\s\S]*?\*\//g, '')
275311
.split('\n')

0 commit comments

Comments
 (0)