@payload-auth-link/react 0.1.0 → 0.1.1
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.
- package/README.md +45 -0
- package/package.json +15 -3
- package/dist/__tests__/components.test.d.ts +0 -2
- package/dist/__tests__/components.test.d.ts.map +0 -1
- package/dist/__tests__/components.test.js +0 -163
- package/dist/__tests__/components.test.js.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -10
- package/dist/__tests__/index.test.js.map +0 -1
- package/dist/__tests__/provider.test.d.ts +0 -2
- package/dist/__tests__/provider.test.d.ts.map +0 -1
- package/dist/__tests__/provider.test.js +0 -177
- package/dist/__tests__/provider.test.js.map +0 -1
- package/dist/__tests__/session.test.d.ts +0 -2
- package/dist/__tests__/session.test.d.ts.map +0 -1
- package/dist/__tests__/session.test.js +0 -70
- package/dist/__tests__/session.test.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @payload-auth-link/react
|
|
2
|
+
|
|
3
|
+
Provider-agnostic React UI for payload-auth-link: a session context plus unstyled sign-in, sign-out, and user-menu components. It has no Next.js or Payload imports, so it works under any framework adapter.
|
|
4
|
+
|
|
5
|
+
Part of [payload-auth-link](https://github.com/jellologic/payload-auth-link): an auth plugin for Payload CMS with swappable identity providers (Better Auth, Auth.js) and first-class Next.js and TanStack Start adapters.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @payload-auth-link/react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependencies: `react` ^19.0.0.
|
|
14
|
+
|
|
15
|
+
ESM-only, Node 20.9 or later.
|
|
16
|
+
|
|
17
|
+
## What it provides
|
|
18
|
+
|
|
19
|
+
- `AuthLinkProvider`, `useAuthLink`, `useHasRoles`: session context, hydrated from the server through `initialUser`
|
|
20
|
+
- `SignInButton`, `SignOutButton`, `UserMenu`: unstyled primitives
|
|
21
|
+
- `RequireRoles`: hides UI by role (never use it to gate content; enforce on the server)
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { AuthLinkProvider, UserMenu } from '@payload-auth-link/react'
|
|
27
|
+
|
|
28
|
+
export function Layout({ user, children }) {
|
|
29
|
+
return (
|
|
30
|
+
<AuthLinkProvider initialUser={user}>
|
|
31
|
+
<UserMenu signOutRedirectTo="/" />
|
|
32
|
+
{children}
|
|
33
|
+
</AuthLinkProvider>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Docs
|
|
39
|
+
|
|
40
|
+
- [Getting started](https://github.com/jellologic/payload-auth-link/blob/main/docs/getting-started.md)
|
|
41
|
+
- [Compatibility](https://github.com/jellologic/payload-auth-link/blob/main/docs/compatibility.md) · [Troubleshooting](https://github.com/jellologic/payload-auth-link/blob/main/docs/troubleshooting.md) · [Changelog](./CHANGELOG.md)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payload-auth-link/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "payload-auth-link React UI: provider-agnostic sign-in components and user menu.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/jellologic/payload-auth-link.git",
|
|
9
|
+
"directory": "packages/react"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/jellologic/payload-auth-link/tree/main/packages/react#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jellologic/payload-auth-link/issues"
|
|
14
|
+
},
|
|
6
15
|
"type": "module",
|
|
7
16
|
"sideEffects": false,
|
|
8
17
|
"exports": {
|
|
@@ -17,7 +26,10 @@
|
|
|
17
26
|
"dist",
|
|
18
27
|
"!dist/*.tsbuildinfo",
|
|
19
28
|
"src",
|
|
20
|
-
"!src/**/__tests__"
|
|
29
|
+
"!src/**/__tests__",
|
|
30
|
+
"!dist/**/__tests__",
|
|
31
|
+
"!dist/**/__fixtures__",
|
|
32
|
+
"!src/**/__fixtures__"
|
|
21
33
|
],
|
|
22
34
|
"scripts": {
|
|
23
35
|
"build": "tsc -b",
|
|
@@ -25,7 +37,7 @@
|
|
|
25
37
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
26
38
|
},
|
|
27
39
|
"dependencies": {
|
|
28
|
-
"@payload-auth-link/core": "0.1.
|
|
40
|
+
"@payload-auth-link/core": "0.1.1"
|
|
29
41
|
},
|
|
30
42
|
"peerDependencies": {
|
|
31
43
|
"react": "^19.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"components.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/components.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// @vitest-environment happy-dom
|
|
3
|
-
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
4
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
// RTL's auto-cleanup keys off a global afterEach, which Vitest does not
|
|
6
|
-
// provide with `globals` off — clean up explicitly between renders.
|
|
7
|
-
afterEach(() => {
|
|
8
|
-
cleanup();
|
|
9
|
-
});
|
|
10
|
-
import { AuthLinkProvider } from '../provider.js';
|
|
11
|
-
import { RequireRoles, SignInButton, SignOutButton, UserMenu } from '../components.js';
|
|
12
|
-
const user = {
|
|
13
|
-
id: 'user-1',
|
|
14
|
-
collection: 'users',
|
|
15
|
-
email: 'user@example.com',
|
|
16
|
-
roles: ['member', 'admin'],
|
|
17
|
-
};
|
|
18
|
-
function jsonResponse(body, status = 200) {
|
|
19
|
-
return new Response(JSON.stringify(body), {
|
|
20
|
-
status,
|
|
21
|
-
headers: { 'content-type': 'application/json' },
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
function stubFetch(handler) {
|
|
25
|
-
return vi.fn((input, init) => {
|
|
26
|
-
const result = handler(input, init);
|
|
27
|
-
return result instanceof Error ? Promise.reject(result) : Promise.resolve(result);
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
function withinProvider(children, options) {
|
|
31
|
-
const fetchImpl = options?.fetchImpl ?? stubFetch(() => jsonResponse({ user }));
|
|
32
|
-
if (options && 'initialUser' in options) {
|
|
33
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: options.initialUser, children: children }));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: children }));
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Deterministic settling for negative assertions: await every response
|
|
40
|
-
* the stub handed out, then drain the microtask queue (bounded) inside
|
|
41
|
-
* `act` so React flushes whatever those resolutions scheduled. No
|
|
42
|
-
* wall-clock sleep, so the test cannot pass or fail on machine load.
|
|
43
|
-
*/
|
|
44
|
-
async function settle(fetchImpl) {
|
|
45
|
-
await act(async () => {
|
|
46
|
-
await Promise.allSettled(fetchImpl.mock.results.map((result) => result.value));
|
|
47
|
-
for (let tick = 0; tick < 50; tick += 1) {
|
|
48
|
-
await Promise.resolve();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
describe('SignInButton', () => {
|
|
53
|
-
it('links to the sign-in page by default', () => {
|
|
54
|
-
render(_jsx(SignInButton, {}));
|
|
55
|
-
const link = screen.getByText('Sign in');
|
|
56
|
-
expect(link.tagName).toBe('A');
|
|
57
|
-
expect(link.getAttribute('href')).toBe('/sign-in');
|
|
58
|
-
});
|
|
59
|
-
it('honors href, className, and children', () => {
|
|
60
|
-
render(_jsx(SignInButton, { href: "/login", className: "btn", children: "Log in" }));
|
|
61
|
-
const link = screen.getByText('Log in');
|
|
62
|
-
expect(link.getAttribute('href')).toBe('/login');
|
|
63
|
-
expect(link.getAttribute('class')).toBe('btn');
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
describe('SignOutButton', () => {
|
|
67
|
-
it('signs out, notifies, and navigates on success', async () => {
|
|
68
|
-
const fetchImpl = stubFetch(() => jsonResponse({ ok: true }));
|
|
69
|
-
const navigate = vi.fn();
|
|
70
|
-
const onSignedOut = vi.fn();
|
|
71
|
-
withinProvider(_jsx(SignOutButton, { redirectTo: "/", navigate: navigate, onSignedOut: onSignedOut }), {
|
|
72
|
-
fetchImpl,
|
|
73
|
-
initialUser: user,
|
|
74
|
-
});
|
|
75
|
-
fireEvent.click(screen.getByText('Sign out'));
|
|
76
|
-
await waitFor(() => {
|
|
77
|
-
expect(onSignedOut).toHaveBeenCalledTimes(1);
|
|
78
|
-
});
|
|
79
|
-
expect(navigate).toHaveBeenCalledWith('/');
|
|
80
|
-
expect(fetchImpl.mock.calls.some(([, init]) => init?.method === 'DELETE')).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
it('stays put when revocation fails', async () => {
|
|
83
|
-
const fetchImpl = stubFetch(() => jsonResponse({}, 500));
|
|
84
|
-
const navigate = vi.fn();
|
|
85
|
-
const onSignedOut = vi.fn();
|
|
86
|
-
withinProvider(_jsx(SignOutButton, { redirectTo: "/", navigate: navigate, onSignedOut: onSignedOut }), {
|
|
87
|
-
fetchImpl,
|
|
88
|
-
initialUser: user,
|
|
89
|
-
});
|
|
90
|
-
fireEvent.click(screen.getByText('Sign out'));
|
|
91
|
-
await waitFor(() => {
|
|
92
|
-
expect(fetchImpl.mock.calls.some(([, init]) => init?.method === 'DELETE')).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
// Let the failed DELETE settle; nothing must happen afterwards.
|
|
95
|
-
await settle(fetchImpl);
|
|
96
|
-
expect(onSignedOut).not.toHaveBeenCalled();
|
|
97
|
-
expect(navigate).not.toHaveBeenCalled();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe('SignOutButton redirect safety (SEC-12)', () => {
|
|
101
|
-
it.each([
|
|
102
|
-
['https://evil.example/phish', '/'],
|
|
103
|
-
['//evil.example', '/'],
|
|
104
|
-
['/api/users', '/'],
|
|
105
|
-
['/sign-in', '/sign-in'],
|
|
106
|
-
['/goodbye?x=1', '/goodbye?x=1'],
|
|
107
|
-
])('navigates %s as %s', async (redirectTo, expected) => {
|
|
108
|
-
const fetchImpl = stubFetch(() => jsonResponse({ ok: true }));
|
|
109
|
-
const navigate = vi.fn();
|
|
110
|
-
withinProvider(_jsx(SignOutButton, { redirectTo: redirectTo, navigate: navigate }), {
|
|
111
|
-
fetchImpl,
|
|
112
|
-
initialUser: user,
|
|
113
|
-
});
|
|
114
|
-
fireEvent.click(screen.getByText('Sign out'));
|
|
115
|
-
await waitFor(() => {
|
|
116
|
-
expect(navigate).toHaveBeenCalledTimes(1);
|
|
117
|
-
});
|
|
118
|
-
expect(navigate).toHaveBeenCalledWith(expected);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
describe('UserMenu', () => {
|
|
122
|
-
it('renders nothing while loading', async () => {
|
|
123
|
-
const pending = [];
|
|
124
|
-
const fetchImpl = vi.fn(async () => new Promise((resolve) => {
|
|
125
|
-
pending.push(resolve);
|
|
126
|
-
}));
|
|
127
|
-
const { container } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(UserMenu, {}) }));
|
|
128
|
-
expect(container.textContent).toBe('');
|
|
129
|
-
pending[0]?.(jsonResponse({ user }));
|
|
130
|
-
// Settle inside the test so no state update lands after unmount.
|
|
131
|
-
await screen.findByText('user@example.com');
|
|
132
|
-
});
|
|
133
|
-
it('offers sign-in when anonymous', async () => {
|
|
134
|
-
const fetchImpl = stubFetch(() => jsonResponse({}, 401));
|
|
135
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(UserMenu, { signInHref: "/login" }) }));
|
|
136
|
-
const link = await screen.findByText('Sign in');
|
|
137
|
-
expect(link.getAttribute('href')).toBe('/login');
|
|
138
|
-
});
|
|
139
|
-
it('shows the email and signs out when authenticated', async () => {
|
|
140
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
141
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(UserMenu, {}) }));
|
|
142
|
-
expect(await screen.findByText('user@example.com')).toBeDefined();
|
|
143
|
-
expect(screen.getByText('Sign out')).toBeDefined();
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
describe('RequireRoles', () => {
|
|
147
|
-
it('gates on every listed role', async () => {
|
|
148
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
149
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(RequireRoles, { roles: ['admin'], fallback: _jsx("span", { children: "denied" }), children: _jsx("span", { children: "granted" }) }) }));
|
|
150
|
-
expect(await screen.findByText('granted')).toBeDefined();
|
|
151
|
-
});
|
|
152
|
-
it('fails closed while loading and when denied', () => {
|
|
153
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
154
|
-
const { container } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(RequireRoles, { roles: ['owner'], fallback: _jsx("span", { children: "denied" }), children: _jsx("span", { children: "granted" }) }) }));
|
|
155
|
-
expect(container.textContent).toBe('denied');
|
|
156
|
-
});
|
|
157
|
-
it('renders nothing by default when denied', () => {
|
|
158
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
159
|
-
const { container } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(RequireRoles, { roles: ['owner'], children: _jsx("span", { children: "granted" }) }) }));
|
|
160
|
-
expect(container.textContent).toBe('');
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
//# sourceMappingURL=components.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"components.test.js","sourceRoot":"","sources":["../../src/__tests__/components.test.tsx"],"names":[],"mappings":";AAAA,gCAAgC;AAChC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAEzF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE5D,wEAAwE;AACxE,oEAAoE;AACpE,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,EAAE,CAAA;AACX,CAAC,CAAC,CAAA;AAEF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEtF,MAAM,IAAI,GAAgB;IACxB,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,OAAO;IACnB,KAAK,EAAE,kBAAkB;IACzB,KAAK,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3B,CAAA;AAED,SAAS,YAAY,CAAC,IAAa,EAAE,MAAM,GAAG,GAAG;IAC/C,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,OAAgE;IACjF,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,KAAa,EAAE,IAAkB,EAAqB,EAAE;QACpE,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QACnC,OAAO,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IACnF,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,QAAmB,EACnB,OAAwF;IAExF,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC/E,IAAI,OAAO,IAAI,aAAa,IAAI,OAAO,EAAE,CAAC;QACxC,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,YACrE,QAAQ,GACQ,CACpB,CAAA;QACD,OAAM;IACR,CAAC;IACD,MAAM,CAAC,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YAAG,QAAQ,GAAoB,CAAC,CAAA;AAC/E,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,MAAM,CAAC,SAAuC;IAC3D,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAgB,CAAC,CAAC,CAAA;QACzF,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;YACxC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;QACzB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,KAAC,YAAY,KAAG,CAAC,CAAA;QACxB,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACxC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC9B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CACJ,KAAC,YAAY,IAAC,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,KAAK,uBAE5B,CAChB,CAAA;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QACvC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAChD,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QAC3B,cAAc,CAAC,KAAC,aAAa,IAAC,UAAU,EAAC,GAAG,EAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAI,EAAE;YAC7F,SAAS;YACT,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,WAAW,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAA;QAC1C,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QACxD,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QAC3B,cAAc,CAAC,KAAC,aAAa,IAAC,UAAU,EAAC,GAAG,EAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAI,EAAE;YAC7F,SAAS;YACT,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvF,CAAC,CAAC,CAAA;QACF,gEAAgE;QAChE,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;QACvB,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IACzC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,4BAA4B,EAAE,GAAG,CAAC;QACnC,CAAC,gBAAgB,EAAE,GAAG,CAAC;QACvB,CAAC,YAAY,EAAE,GAAG,CAAC;QACnB,CAAC,UAAU,EAAE,UAAU,CAAC;QACxB,CAAC,cAAc,EAAE,cAAc,CAAC;KACjC,CAAC,CAAC,oBAAoB,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE;QACtD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,cAAc,CAAC,KAAC,aAAa,IAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAI,EAAE;YAC5E,SAAS;YACT,WAAW,EAAE,IAAI;SAClB,CAAC,CAAA;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7C,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC3C,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,OAAO,GAAqC,EAAE,CAAA;QACpD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CACrB,KAAK,IAAuB,EAAE,CAC5B,IAAI,OAAO,CAAW,CAAC,OAAO,EAAE,EAAE;YAChC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvB,CAAC,CAAC,CACL,CAAA;QACD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,QAAQ,KAAG,GACK,CACpB,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACtC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACpC,iEAAiE;QACjE,MAAM,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QACxD,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,QAAQ,IAAC,UAAU,EAAC,QAAQ,GAAG,GACf,CACpB,CAAA;QACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,QAAQ,KAAG,GACK,CACpB,CAAA;QACD,MAAM,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IACpD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,CACJ,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,YACpC,KAAC,YAAY,IAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,oCAAmB,YAC3D,qCAAoB,GACP,GACE,CACpB,CAAA;QACD,MAAM,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,YACvD,KAAC,YAAY,IAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,oCAAmB,YAC3D,qCAAoB,GACP,GACE,CACpB,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QACzD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,YACvD,KAAC,YAAY,IAAC,KAAK,EAAE,CAAC,OAAO,CAAC,YAC5B,qCAAoB,GACP,GACE,CACpB,CAAA;QACD,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { fetchSessionUser } from '../index.js';
|
|
3
|
-
describe('react entrypoint', () => {
|
|
4
|
-
it('serves the session reader contract through the barrel', async () => {
|
|
5
|
-
const fetchImpl = vi.fn(() => Promise.resolve(new Response('nope', { status: 401 })));
|
|
6
|
-
await expect(fetchSessionUser('/api/auth-link/session', fetchImpl)).resolves.toBeNull();
|
|
7
|
-
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
8
|
-
});
|
|
9
|
-
});
|
|
10
|
-
//# sourceMappingURL=index.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9C,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;QACrF,MAAM,MAAM,CAAC,gBAAgB,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAA;QACvF,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// @vitest-environment happy-dom
|
|
3
|
-
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
4
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { AuthLinkProvider, useAuthLink, useHasRoles } from '../provider.js';
|
|
6
|
-
// RTL's auto-cleanup keys off a global afterEach, which Vitest does not
|
|
7
|
-
// provide with `globals` off — clean up explicitly between renders.
|
|
8
|
-
afterEach(() => {
|
|
9
|
-
cleanup();
|
|
10
|
-
});
|
|
11
|
-
const user = {
|
|
12
|
-
id: 'user-1',
|
|
13
|
-
collection: 'users',
|
|
14
|
-
email: 'user@example.com',
|
|
15
|
-
roles: ['member', 'admin'],
|
|
16
|
-
};
|
|
17
|
-
function jsonResponse(body, status = 200) {
|
|
18
|
-
return new Response(JSON.stringify(body), {
|
|
19
|
-
status,
|
|
20
|
-
headers: { 'content-type': 'application/json' },
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function stubFetch(handler) {
|
|
24
|
-
return vi.fn((input, init) => {
|
|
25
|
-
const result = handler(input, init);
|
|
26
|
-
return result instanceof Error ? Promise.reject(result) : Promise.resolve(result);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
function StatusProbe() {
|
|
30
|
-
const { user: current, status } = useAuthLink();
|
|
31
|
-
return (_jsxs("div", { children: [_jsx("span", { "data-testid": "status", children: status }), _jsx("span", { "data-testid": "email", children: current?.email ?? 'none' })] }));
|
|
32
|
-
}
|
|
33
|
-
function RolesProbe({ roles }) {
|
|
34
|
-
return _jsx("span", { "data-testid": "has-roles", children: useHasRoles(...roles) ? 'yes' : 'no' });
|
|
35
|
-
}
|
|
36
|
-
describe('AuthLinkProvider', () => {
|
|
37
|
-
it('loads the session on mount', async () => {
|
|
38
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
39
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(StatusProbe, {}) }));
|
|
40
|
-
expect(screen.getByTestId('status').textContent).toBe('loading');
|
|
41
|
-
await waitFor(() => {
|
|
42
|
-
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
43
|
-
});
|
|
44
|
-
expect(screen.getByTestId('email').textContent).toBe('user@example.com');
|
|
45
|
-
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
46
|
-
});
|
|
47
|
-
it('goes anonymous when no session exists', async () => {
|
|
48
|
-
const fetchImpl = stubFetch(() => jsonResponse({ error: 'nope' }, 401));
|
|
49
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, children: _jsx(StatusProbe, {}) }));
|
|
50
|
-
await waitFor(() => {
|
|
51
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
it('hydrates from initialUser without fetching', () => {
|
|
55
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
56
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(StatusProbe, {}) }));
|
|
57
|
-
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
58
|
-
expect(screen.getByTestId('email').textContent).toBe('user@example.com');
|
|
59
|
-
expect(fetchImpl).not.toHaveBeenCalled();
|
|
60
|
-
});
|
|
61
|
-
it('hydrates anonymous from a null initialUser without fetching', () => {
|
|
62
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
63
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(StatusProbe, {}) }));
|
|
64
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
65
|
-
expect(fetchImpl).not.toHaveBeenCalled();
|
|
66
|
-
});
|
|
67
|
-
it('syncs a later initialUser into context (PAR-10)', () => {
|
|
68
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
69
|
-
const other = { ...user, id: 'user-2', email: 'other@example.com' };
|
|
70
|
-
const { rerender } = render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(StatusProbe, {}) }));
|
|
71
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
72
|
-
rerender(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(StatusProbe, {}) }));
|
|
73
|
-
expect(screen.getByTestId('email').textContent).toBe('user@example.com');
|
|
74
|
-
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
75
|
-
rerender(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: other, children: _jsx(StatusProbe, {}) }));
|
|
76
|
-
expect(screen.getByTestId('email').textContent).toBe('other@example.com');
|
|
77
|
-
rerender(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(StatusProbe, {}) }));
|
|
78
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
79
|
-
// Refresh stays explicit: syncing a snapshot never fetches.
|
|
80
|
-
expect(fetchImpl).not.toHaveBeenCalled();
|
|
81
|
-
});
|
|
82
|
-
it('keeps a client sign-out when re-rendered with an equal fresh snapshot', async () => {
|
|
83
|
-
const fetchImpl = stubFetch(() => jsonResponse({ ok: true }));
|
|
84
|
-
function SignOutProbe() {
|
|
85
|
-
const { signOut } = useAuthLink();
|
|
86
|
-
return (_jsx("button", { type: "button", onClick: () => void signOut(), children: "out" }));
|
|
87
|
-
}
|
|
88
|
-
const tree = (snapshot) => (_jsxs(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: snapshot, children: [_jsx(StatusProbe, {}), _jsx(SignOutProbe, {})] }));
|
|
89
|
-
const { rerender } = render(tree(user));
|
|
90
|
-
fireEvent.click(screen.getByText('out'));
|
|
91
|
-
await waitFor(() => {
|
|
92
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
93
|
-
});
|
|
94
|
-
rerender(tree({ ...user, roles: [...user.roles] }));
|
|
95
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
96
|
-
});
|
|
97
|
-
it('refreshes on demand', async () => {
|
|
98
|
-
let current = null;
|
|
99
|
-
const fetchImpl = stubFetch(() => current ? jsonResponse({ user: current }) : jsonResponse({}, 401));
|
|
100
|
-
function RefreshProbe() {
|
|
101
|
-
const { refresh } = useAuthLink();
|
|
102
|
-
return (_jsx("button", { type: "button", onClick: () => {
|
|
103
|
-
void refresh();
|
|
104
|
-
}, children: "refresh" }));
|
|
105
|
-
}
|
|
106
|
-
render(_jsxs(AuthLinkProvider, { fetchImpl: fetchImpl, children: [_jsx(StatusProbe, {}), _jsx(RefreshProbe, {})] }));
|
|
107
|
-
await waitFor(() => {
|
|
108
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
109
|
-
});
|
|
110
|
-
current = user;
|
|
111
|
-
fireEvent.click(screen.getByText('refresh'));
|
|
112
|
-
await waitFor(() => {
|
|
113
|
-
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
it('signOut clears state on success and keeps it on failure', async () => {
|
|
117
|
-
let failRevoke = false;
|
|
118
|
-
const outcomes = [];
|
|
119
|
-
const fetchImpl = stubFetch((_input, init) => {
|
|
120
|
-
if (init?.method === 'DELETE') {
|
|
121
|
-
return failRevoke ? jsonResponse({}, 500) : jsonResponse({ ok: true });
|
|
122
|
-
}
|
|
123
|
-
return jsonResponse({ user });
|
|
124
|
-
});
|
|
125
|
-
function SignOutProbe() {
|
|
126
|
-
const { signOut } = useAuthLink();
|
|
127
|
-
return (_jsx("button", { type: "button", onClick: () => {
|
|
128
|
-
void signOut().then((ok) => outcomes.push(ok));
|
|
129
|
-
}, children: "sign out" }));
|
|
130
|
-
}
|
|
131
|
-
render(_jsxs(AuthLinkProvider, { fetchImpl: fetchImpl, children: [_jsx(StatusProbe, {}), _jsx(SignOutProbe, {})] }));
|
|
132
|
-
await waitFor(() => {
|
|
133
|
-
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
134
|
-
});
|
|
135
|
-
failRevoke = true;
|
|
136
|
-
fireEvent.click(screen.getByText('sign out'));
|
|
137
|
-
await waitFor(() => {
|
|
138
|
-
expect(outcomes).toEqual([false]);
|
|
139
|
-
});
|
|
140
|
-
expect(screen.getByTestId('status').textContent).toBe('authenticated');
|
|
141
|
-
failRevoke = false;
|
|
142
|
-
fireEvent.click(screen.getByText('sign out'));
|
|
143
|
-
await waitFor(() => {
|
|
144
|
-
expect(screen.getByTestId('status').textContent).toBe('anonymous');
|
|
145
|
-
});
|
|
146
|
-
expect(outcomes).toEqual([false, true]);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
describe('useAuthLink', () => {
|
|
150
|
-
it('throws outside the provider', () => {
|
|
151
|
-
const silenced = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
152
|
-
try {
|
|
153
|
-
expect(() => render(_jsx(StatusProbe, {}))).toThrow('useAuthLink must be used within AuthLinkProvider');
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
silenced.mockRestore();
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
describe('useHasRoles', () => {
|
|
161
|
-
it('passes when every listed role is held', () => {
|
|
162
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
163
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(RolesProbe, { roles: ['admin', 'member'] }) }));
|
|
164
|
-
expect(screen.getByTestId('has-roles').textContent).toBe('yes');
|
|
165
|
-
});
|
|
166
|
-
it('fails when any listed role is missing', () => {
|
|
167
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
168
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: user, children: _jsx(RolesProbe, { roles: ['admin', 'owner'] }) }));
|
|
169
|
-
expect(screen.getByTestId('has-roles').textContent).toBe('no');
|
|
170
|
-
});
|
|
171
|
-
it('fails closed when anonymous', () => {
|
|
172
|
-
const fetchImpl = stubFetch(() => jsonResponse({ user }));
|
|
173
|
-
render(_jsx(AuthLinkProvider, { fetchImpl: fetchImpl, initialUser: null, children: _jsx(RolesProbe, { roles: ['admin'] }) }));
|
|
174
|
-
expect(screen.getByTestId('has-roles').textContent).toBe('no');
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
//# sourceMappingURL=provider.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/__tests__/provider.test.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { fetchSessionUser, revokeSession } from '../session.js';
|
|
3
|
-
const user = { id: 'user-1', collection: 'users', email: 'user@example.com', roles: ['member'] };
|
|
4
|
-
function jsonResponse(body, status = 200) {
|
|
5
|
-
return new Response(JSON.stringify(body), {
|
|
6
|
-
status,
|
|
7
|
-
headers: { 'content-type': 'application/json' },
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
function stubFetch(response) {
|
|
11
|
-
const fetchImpl = vi.fn();
|
|
12
|
-
if (response instanceof Error) {
|
|
13
|
-
fetchImpl.mockRejectedValue(response);
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
fetchImpl.mockResolvedValue(response);
|
|
17
|
-
}
|
|
18
|
-
return fetchImpl;
|
|
19
|
-
}
|
|
20
|
-
describe('fetchSessionUser', () => {
|
|
21
|
-
it('reads the user from the default endpoint', async () => {
|
|
22
|
-
const fetchImpl = stubFetch(jsonResponse({ user }));
|
|
23
|
-
const result = await fetchSessionUser(undefined, fetchImpl);
|
|
24
|
-
expect(result).toEqual(user);
|
|
25
|
-
expect(fetchImpl).toHaveBeenCalledWith('/api/auth-link/session', {
|
|
26
|
-
method: 'GET',
|
|
27
|
-
credentials: 'same-origin',
|
|
28
|
-
cache: 'no-store',
|
|
29
|
-
headers: { accept: 'application/json' },
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
it('honors a custom endpoint', async () => {
|
|
33
|
-
const fetchImpl = stubFetch(jsonResponse({ user }));
|
|
34
|
-
await fetchSessionUser('/custom/session', fetchImpl);
|
|
35
|
-
expect(fetchImpl).toHaveBeenCalledWith('/custom/session', expect.objectContaining({ method: 'GET' }));
|
|
36
|
-
});
|
|
37
|
-
it('fails closed on transport and protocol failures', async () => {
|
|
38
|
-
for (const response of [
|
|
39
|
-
stubFetch(new Error('down')),
|
|
40
|
-
stubFetch(jsonResponse({ error: 'nope' }, 401)),
|
|
41
|
-
stubFetch(jsonResponse({ error: 'nope' }, 500)),
|
|
42
|
-
stubFetch(new Response('not-json{', { status: 200 })),
|
|
43
|
-
stubFetch(jsonResponse([user])),
|
|
44
|
-
stubFetch(jsonResponse({})),
|
|
45
|
-
stubFetch(jsonResponse({ user: { ...user, email: 'bad' } })),
|
|
46
|
-
]) {
|
|
47
|
-
await expect(fetchSessionUser(undefined, response)).resolves.toBeNull();
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
describe('revokeSession', () => {
|
|
52
|
-
it('sends a CSRF-proofed DELETE to the default endpoint', async () => {
|
|
53
|
-
const fetchImpl = stubFetch(jsonResponse({ ok: true }));
|
|
54
|
-
await expect(revokeSession(undefined, fetchImpl)).resolves.toBe(true);
|
|
55
|
-
expect(fetchImpl).toHaveBeenCalledWith('/api/auth-link/session', {
|
|
56
|
-
method: 'DELETE',
|
|
57
|
-
credentials: 'same-origin',
|
|
58
|
-
headers: { 'x-auth-link-csrf': '1' },
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
it('treats already-signed-out as success (idempotent logout)', async () => {
|
|
62
|
-
await expect(revokeSession(undefined, stubFetch(jsonResponse({}, 401)))).resolves.toBe(true);
|
|
63
|
-
});
|
|
64
|
-
it('reports failure instead of lying', async () => {
|
|
65
|
-
await expect(revokeSession(undefined, stubFetch(new Error('down')))).resolves.toBe(false);
|
|
66
|
-
await expect(revokeSession(undefined, stubFetch(jsonResponse({}, 500)))).resolves.toBe(false);
|
|
67
|
-
await expect(revokeSession(undefined, stubFetch(jsonResponse({}, 403)))).resolves.toBe(false);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
//# sourceMappingURL=session.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.test.js","sourceRoot":"","sources":["../../src/__tests__/session.test.ts"],"names":[],"mappings":"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"}
|