merge: integrate remote learning module safely
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
consumeWorksSquareStartupRuntimeCleanupRequired,
|
||||
clearWorksSquareSession,
|
||||
flushWorksSquareSessionPersistence,
|
||||
getValidWorksSquareAccessToken,
|
||||
getWorksSquareAccountBinding,
|
||||
getWorksSquareSessionSnapshot,
|
||||
initializeWorksSquareSession,
|
||||
markWorksSquareSessionActive,
|
||||
resetWorksSquareSessionForTests,
|
||||
storeWorksSquareSession,
|
||||
storeWorksSquareSessionFromTokenPayload,
|
||||
subscribeWorksSquareSession,
|
||||
WORKS_SQUARE_SESSION_IDLE_TIMEOUT_MS,
|
||||
type WorksSquareSessionInput,
|
||||
@@ -16,6 +19,10 @@ import {
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
function jwt(claims: Record<string, unknown>): string {
|
||||
return `header.${Buffer.from(JSON.stringify(claims)).toString('base64url')}.signature`;
|
||||
}
|
||||
|
||||
describe('works-square-session service', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
@@ -402,4 +409,77 @@ describe('works-square-session service', () => {
|
||||
const body = secondFetch.mock.calls[0][1].body as URLSearchParams;
|
||||
expect(String(body)).toContain('refresh_token=rotated-refresh-token');
|
||||
});
|
||||
|
||||
it('uses user_id ahead of username for a stable opaque account partition', () => {
|
||||
storeWorksSquareSessionFromTokenPayload({
|
||||
access_token: 'access-a',
|
||||
user_id: 'raw-user-123',
|
||||
username: 'first@example.com',
|
||||
expires_in: 3600,
|
||||
});
|
||||
const first = getWorksSquareAccountBinding();
|
||||
|
||||
storeWorksSquareSessionFromTokenPayload({
|
||||
access_token: 'access-b',
|
||||
user_id: 'raw-user-123',
|
||||
username: 'renamed@example.com',
|
||||
expires_in: 3600,
|
||||
});
|
||||
const second = getWorksSquareAccountBinding();
|
||||
|
||||
expect(first?.accountKey).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(second?.accountKey).toBe(first?.accountKey);
|
||||
expect(second?.accountKey).not.toContain('raw-user-123');
|
||||
expect(second?.epoch).toBe(first?.epoch);
|
||||
});
|
||||
|
||||
it('retains the account partition across refresh, clears it on logout, and changes it on account switch', async () => {
|
||||
storeWorksSquareSessionFromTokenPayload({
|
||||
access_token: 'old-a',
|
||||
refresh_token: 'refresh-a',
|
||||
user_id: 'account-a',
|
||||
expires_in: 1,
|
||||
});
|
||||
const accountA = getWorksSquareAccountBinding();
|
||||
const fetchImpl = vi.fn().mockResolvedValue(new Response(JSON.stringify({
|
||||
access_token: 'refreshed-a',
|
||||
refresh_token: 'refresh-a-2',
|
||||
user_id: 'unexpected-account-b',
|
||||
expires_in: 3600,
|
||||
}), { status: 200 }));
|
||||
|
||||
await getValidWorksSquareAccessToken({ fetchImpl, forceRefresh: true });
|
||||
expect(getWorksSquareAccountBinding()).toEqual(accountA);
|
||||
|
||||
clearWorksSquareSession();
|
||||
expect(getWorksSquareAccountBinding()).toBeNull();
|
||||
storeWorksSquareSessionFromTokenPayload({
|
||||
access_token: 'access-b',
|
||||
user_id: 'account-b',
|
||||
expires_in: 3600,
|
||||
});
|
||||
expect(getWorksSquareAccountBinding()?.accountKey).not.toBe(accountA?.accountKey);
|
||||
expect(getWorksSquareAccountBinding()?.epoch).toBeGreaterThan(accountA?.epoch ?? 0);
|
||||
});
|
||||
|
||||
it('migrates a legacy persisted Renderer session from safe JWT identity claims', async () => {
|
||||
const persistence: WorksSquareSessionPersistence = {
|
||||
load: vi.fn().mockResolvedValue({
|
||||
accessToken: jwt({ user_id: 'legacy-user', username: 'legacy@example.com' }),
|
||||
refreshToken: 'legacy-refresh',
|
||||
expiresAt: Date.now() + 3600_000,
|
||||
lastActiveAt: Date.now(),
|
||||
}),
|
||||
save: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
await initializeWorksSquareSession({ persistence });
|
||||
|
||||
const binding = getWorksSquareAccountBinding();
|
||||
expect(binding?.accountKey).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(binding?.accountKey).not.toContain('legacy-user');
|
||||
expect(persistence.save).toHaveBeenCalledWith(expect.objectContaining({
|
||||
accountPartitionKey: binding?.accountKey,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user