Skip to content

Commit 6d4f3e5

Browse files
Default --single-user to root pod (#348) (#349)
* Default --single-user to root pod (closes #348) Change the default `singleUserName` from `'me'` to `null`. Without the flag, `jss start --single-user` now serves the pod at the server origin (`/profile/card#me`) instead of `/me/profile/card#me`. Why: in single-user mode there is by definition exactly one pod, so the `/me/` prefix has no namespace-disambiguation purpose. It only adds friction — most Solid clients and tutorials assume the pod coincides with the origin. Operators who actually want a named pod still pass `--single-user-name X` explicitly; that path is unchanged. Behaviour summary: - `jss start --single-user` → root pod (new default) - `jss start --single-user --single-user-name me` → `/me/` (legacy) - `jss start --single-user --single-user-name alice` → `/alice/` Migration note: anyone upgrading a fresh-default install in place needs to either move `data/me/*` → `data/*` or pass `--single-user-name me` to keep the old pod. With the project still at 0.0.x and the userbase small this is an acceptable break; deployed servers in our orbit (solid.social, melvin.me, melvincarvalho.com) all pass `--single-user-name` explicitly and are unaffected. Implementation: - `src/config.js`: defaultConfig.singleUserName: 'me' → null; printConfig now formats the root-pod case cleanly. - `src/server.js`: drop the `?? 'me'` fallback; null = root pod. - `bin/jss.js`: --single-user-name help text updated. - `test/idp.test.js`: new "Single-user default — root pod (#348)" describe asserting the seeded profile lands at /profile/card.jsonld with WebID at the server origin when no name flag is passed. All 592 tests pass. * PR #349 round-1 review (Copilot): seed IDP account for root pod Round-1 review caught a real gap: my first commit moved single-user mode's default to root pod but left `seedSingleUserIdpAccount()` gated on `!isRootPod`, so a fresh `jss start --single-user --idp` produced a pod no operator could log in to (registration is disabled in single-user mode, no fallback). Fix: also seed the IDP account for root-pod mode, defaulting the login username to "me". The pod URL now lives at the origin while the login flow stays the same as a named pod — operator types "me" + password into the login form, gets a token for the WebID at `${origin}/profile/card.jsonld#me`. - src/server.js: drop `&& !isRootPod` from the IDP-seed guard; derive `username = isRootPod ? 'me' : singleUserName` and `podName = isRootPod ? null : singleUserName`. - src/config.js: print-config now reads `Single-user: / (root pod, login as "me") (password: ...)` — removes the misleading "password not seeded" wording. - docs/configuration.md: rewrite the Single-User section to document the new default (root pod, WebID at origin, login as "me") and show `--single-user-name me` as the explicit-legacy knob. - test/idp.test.js: extend the #348 describe to assert that `POST /idp/credentials` with `{username: "me", password}` returns a 200 + access_token — black-box check that the seed actually produces a loggable account. All 593 tests pass. * PR #349 round-2 review (Copilot) Addresses 5 genuinely new points (the other 4 inline comments re-flag round-1 items already in HEAD). 1. Normalize singleUserName at the top of createServer(). '/' / '' / null all collapse to null, so downstream code (remoteStoragePlugin at line 292: `singleUserName || 'me'`) doesn't get a literal '/' string and end up registering /storage/%2F/ instead of /storage/me/. 2. Migration warning. When seeding a fresh root pod, check whether `/me/profile/card.jsonld` (or legacy extensionless card) already exists on disk. If it does, log a clear warning that the default changed in #348 and suggest restarting with --single-user-name me. This catches the silent-upgrade case where an operator on the pre-#348 default ends up with a fresh empty root pod alongside their stranded /me/ data. 3. printConfig wording. The "(login as me)" / "(password: ...)" bits only fire under --idp now, so a `--single-user --no-idp` deployment doesn't claim a built-in login that doesn't exist. 4. WebID examples corrected. Fresh JSS pods seed `/profile/card.jsonld#me`, not `/profile/card#me` — the docs and config.js comment now reflect the canonical URI. Legacy extensionless pods (created before the .jsonld convention) continue to work via the existing fallback in onReady. 5. Now that singleUserName is normalized at the top, the inline `singleUserName === '/'` check in onReady is redundant; replace with a plain truthiness test. All 593 tests pass. * PR #349 round-3 review: in-place upgrade fallback Round-3 surfaced a concrete bad outcome from the prior approach: when an operator upgrades a pre-#348 install without moving data, the wildcard LDP routes still serve /me/* directly from disk, the old IDP account "me" still authenticates against /me/profile/card, and the new code seeds an *empty* root pod alongside it. That split-brain leaves clients reading/writing the legacy pod while the operator believes they're on the new default. Switch from "warn loudly and seed anyway" to "auto-fall-back to --single-user-name me when /me/ data exists". This is strictly better: - Fresh install (no /me/ data) → root pod, the new default. - Pre-#348 install (default 'me' was used) → keeps working exactly as before, no surprise pod, no IDP account collision. - Operator who actively wants to migrate to root → moves data/me/* → data/* themselves, then restart picks up root. Implementation: - src/server.js: detect pre-existing /me/ pod via existsSync before plugin registration (remoteStoragePlugin captures the username at registration time, so the check has to be sync). When detected, set effective singleUserName = 'me' and stash a flag for the onReady warning. - onReady logs a one-liner explaining the fallback so operators know why their pod is at /me/. - The previous round-2 "split-brain" warning becomes redundant (the auto-fallback prevents the split-brain from happening at all) and is removed. Test: - New describe `Single-user upgrade fallback — pre-existing /me/ pod (#348)`. Phase 1 seeds the legacy layout (explicit --single-user-name me + password). Phase 2 restarts on the same data dir with no name flag. Asserts: - GET /me/profile/card.jsonld is still 200 (no fresh root pod overwrites or hides it) - POST /idp/credentials with {username: me, password} still returns an access_token (existing IDP account intact) - Both phases reuse the same port so ACLs (which carry absolute URIs) keep matching. 595/595 tests pass. * PR #349: simplify — drop the upgrade auto-fallback The user feedback on round-3 was clear: the original ask was a simple default change, and "people will figure it out" was explicit license to skip migration cleverness. Round 3's auto-fallback (and round 4's tweaks to it) were scope creep. Revert the auto-fallback. Pre-#348 installs that upgrade in place without flag changes will get a fresh empty root pod alongside their existing /me/ data — operators who hit that pick one of two explicit paths on restart, both documented: 1. Add `--single-user-name me` → legacy layout, no further work. 2. Move `<root>/me/*` to `<root>/`, delete the legacy IDP account for "me", restart → new root pod, new account seeded. At v0.0.x with a small operator base, that one-time intervention is acceptable and keeps the codebase clean. Changes from round 3 / unpushed round 4: - src/server.js: drop the `existsSync(/me/...)` auto-detect block, the `migratedFromMeDefault` flag, and the corresponding warning in onReady. Drop the `existsSync` import. - test/idp.test.js: drop the `Single-user upgrade fallback` suite (no fallback to test). - test/idp.test.js: tighten the `seeds the profile at root` assertion — also fs.pathExists() check that no /me/ files were written, so a regression that left /me/ behind under a 401 wouldn't slip through (round-4 review #7). - docs/configuration.md: add an explicit "Upgrading from a pre-#348 install" callout under Single-User Mode listing both migration paths (round-4 review #11). 593/593 tests pass. * PR #349 round-5 review: fix two real points Of the 11 inline comments, 5 re-flag round-1/2/3 work already on the branch, 3 re-raise the pre-#348 upgrade incompatibility we deliberately decline (documented in the migration callout), 1 is a pre-existing printConfig accuracy issue out of scope here. The remaining two are new and worth fixing: 1. Root-pod podName was null, but src/idp/accounts.js:438-440 surfaces account.podName as the `name` claim under the OIDC `profile` scope. A null there propagates as missing/null profile.name on every login. Use 'me' for the root-pod case so the claim matches the username. 2. Add a getPodName regression test for `singleUserName: null`. The existing url.test.js coverage tests `''` and `'/'` but not the normalized null shape that most root-pod requests now reach it with after the createServer-level normalization. 594/594 tests pass. * PR #349 round-6 review: two real points Of the 10 inline comments, 8 re-flag earlier-round work or re-raise the deliberately-declined upgrade trade-off. Two new points are worth fixing: 1. test/config.test.js: pin the singleUserName: null default at the config layer. createServer() has its own root-pod tests, but a future refactor of loadConfig() could silently restore the old 'me' default and only behavioural tests would catch it. Add three focused assertions: default is null, explicit CLI arg is preserved, and JSS_SINGLE_USER_NAME env is honoured (operator's escape hatch back to /me/). 2. docs/configuration.md: the previous migration command `mv <root>/me/* <root>/` silently skips dotfiles (`.acl`, `.meta`, `.quota.json`), which would leave the migrated root pod without ACL or quota state. Replace with two explicit options that handle dotfiles correctly: rsync (default) or bash+dotglob. 597/597 tests pass.
1 parent c6aed96 commit 6d4f3e5

7 files changed

Lines changed: 186 additions & 26 deletions

File tree

bin/jss.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ program
126126
.option('--invite-only', 'Require invite code for registration')
127127
.option('--no-invite-only', 'Allow open registration')
128128
.option('--single-user', 'Single-user mode (creates pod on startup, disables registration)')
129-
.option('--single-user-name <name>', 'Username for single-user mode (default: me)')
129+
.option('--single-user-name <name>', 'Mount the pod at /<name>/ instead of at the server root (default: root pod at /)')
130130
.option('--single-user-password <pw>', 'Initial IDP password to seed when creating the single-user pod (or set JSS_SINGLE_USER_PASSWORD)')
131131
.option('--webid-tls', 'Enable WebID-TLS client certificate authentication')
132132
.option('--no-webid-tls', 'Disable WebID-TLS authentication')

docs/configuration.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,21 @@ Response:
258258
For personal pod servers where only one user needs access:
259259

260260
```bash
261-
# Basic single-user mode (creates pod at /me/)
262-
# On first run JSS will prompt for an initial password (TTY only).
261+
# Default: pod served at server root (#348). WebID is
262+
# /profile/card.jsonld#me; the IDP login username is "me". On first
263+
# run JSS will prompt for an initial password (TTY only).
263264
jss start --single-user --idp
264265

265266
# Provide the initial IDP password non-interactively (systemd, containers, CI):
266267
jss start --single-user --idp --single-user-password 'choose-a-good-one'
267268
JSS_SINGLE_USER_PASSWORD='choose-a-good-one' jss start --single-user --idp
268269

269-
# Custom username
270+
# Mount the pod at a named path instead of the origin. WebID becomes
271+
# /alice/profile/card.jsonld#me; login as "alice".
270272
jss start --single-user --single-user-name alice --idp
271273

272-
# Root-level pod (pod at /, WebID at /profile/card#me)
273-
jss start --single-user --single-user-name '' --idp
274+
# Legacy /me/ pod — same as the old default before #348.
275+
jss start --single-user --single-user-name me --idp
274276

275277
# Via environment
276278
JSS_SINGLE_USER=true jss start --idp
@@ -283,6 +285,18 @@ JSS_SINGLE_USER=true jss start --idp
283285
- Login works for the single user via password (`POST /idp/credentials`) or any other configured method
284286
- Proper ACLs generated automatically
285287

288+
**Upgrading from a pre-#348 install:** if your existing pod was created with the old default (data lives under `<root>/me/`), JSS no longer auto-detects it — restarting plain `jss start --single-user` will start seeding a fresh empty root pod alongside your legacy `/me/` data, and your existing IDP account will keep authenticating against `/me/`. Pick one path on the next restart:
289+
- **Keep the legacy layout:** add `--single-user-name me` to your launch command. No data movement needed.
290+
- **Migrate to root pod:** move the *entire* contents of `<root>/me/` (including dotfiles like `.acl`, `.meta`, `.quota.json` — a plain `mv <root>/me/* <root>/` skips them) to `<root>/`, delete the IDP account for `me` (so the new root pod's `me` account can be seeded), then restart without the name flag. Use one of:
291+
292+
```bash
293+
# Option A: rsync handles dotfiles correctly with the trailing slash.
294+
rsync -a <root>/me/ <root>/ && rm -rf <root>/me
295+
296+
# Option B: bash with dotglob enabled so * matches dotfiles too.
297+
shopt -s dotglob && mv <root>/me/* <root>/ && rmdir <root>/me
298+
```
299+
286300
**Initial password sources, in priority order:**
287301
1. `--single-user-password <pw>` CLI flag
288302
2. `JSS_SINGLE_USER_PASSWORD` env var

src/config.js

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ export const defaults = {
7474

7575
// Single-user mode (personal pod server)
7676
singleUser: false,
77-
singleUserName: 'me',
77+
// null = root pod (mounted at server origin, WebID at
78+
// /profile/card.jsonld#me). A string mounts the pod at /<name>/ —
79+
// useful when more than one Solid identity coexists on the same
80+
// origin, or when the operator wants the pre-#348 /me/ shape.
81+
singleUserName: null,
7882
// Initial IDP password seeded on first single-user pod creation. If
7983
// unset and --idp is enabled, the server prompts on a TTY or logs a
8084
// warning and continues startup on non-TTY (so the pod is created but
@@ -399,20 +403,17 @@ export function printConfig(config) {
399403
console.log(` SSL: ${config.ssl ? 'enabled' : 'disabled'}`);
400404
console.log(` Multi-user: ${config.multiuser}`);
401405
if (config.singleUser) {
402-
let details = `${config.singleUserName}`;
403-
// Password seeding only runs when --idp is on AND the pod isn't the
404-
// root-level case ('/'). Reflect both gates in the printed line so
405-
// operators don't see a misleading "missing — login disabled" when
406-
// login isn't governed by an IDP password at all.
406+
const isRootPod = config.singleUserName === '/' || !config.singleUserName;
407+
let details = isRootPod ? '/ (root pod)' : config.singleUserName;
408+
// The "login as me" hint and password line only make sense when
409+
// the built-in IdP is on. With --no-idp / external issuer there's
410+
// no built-in login form, so don't imply one exists.
407411
if (config.idp) {
408-
if (config.singleUserName === '/' || !config.singleUserName) {
409-
details += ' (root pod; password not seeded)';
410-
} else {
411-
const pwSource = config.singleUserPassword
412-
? 'provided'
413-
: (process.stdin.isTTY ? 'will prompt at startup' : 'missing — login disabled');
414-
details += ` (password: ${pwSource})`;
415-
}
412+
if (isRootPod) details += ', login as "me"';
413+
const pwSource = config.singleUserPassword
414+
? 'provided'
415+
: (process.stdin.isTTY ? 'will prompt at startup' : 'missing — login disabled');
416+
details += ` (password: ${pwSource})`;
416417
}
417418
console.log(` Single-user: ${details}`);
418419
}

src/server.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,22 @@ export function createServer(options = {}) {
9393
const inviteOnly = options.inviteOnly ?? false;
9494
// Single-user mode - creates pod on startup, disables registration
9595
const singleUser = options.singleUser ?? false;
96-
const singleUserName = options.singleUserName ?? 'me';
96+
// Default null = root pod (#348). Pass an explicit singleUserName
97+
// to mount the pod at /<name>/ instead. Normalize the
98+
// historical `'/'` / `''` forms to null up front so downstream
99+
// code (remoteStoragePlugin, decorators, etc.) doesn't have to
100+
// re-check for the same three shapes.
101+
//
102+
// Pre-#348 installs (default 'me') that upgrade in place will see
103+
// a fresh empty root pod alongside their /me/ data. The fix is to
104+
// pass `--single-user-name me` on restart (or move data/me/* out
105+
// to the data root). At v0.0.x we accept that one-time
106+
// intervention rather than carrying detection magic in the code.
107+
const rawSingleUserName = options.singleUserName ?? null;
108+
const singleUserName =
109+
(rawSingleUserName === '/' || rawSingleUserName === '')
110+
? null
111+
: rawSingleUserName;
97112
const singleUserPassword = options.singleUserPassword ?? null;
98113
// Default storage quota per pod (50MB default, 0 = unlimited)
99114
const defaultQuota = options.defaultQuota ?? 50 * 1024 * 1024;
@@ -558,8 +573,10 @@ export function createServer(options = {}) {
558573
const baseUrl = idpIssuer?.replace(/\/$/, '') || `${protocol}://${host}:${port}`;
559574
const issuer = idpIssuer || `${baseUrl}/`;
560575

561-
// Root-level pod (empty or '/' name) vs named pod
562-
const isRootPod = !singleUserName || singleUserName === '/';
576+
// Root pod (no name) vs named pod. After the singleUserName
577+
// normalization at the top of createServer(), null is the only
578+
// root-pod shape we need to recognize here.
579+
const isRootPod = !singleUserName;
563580
const podPath = isRootPod ? '/' : `/${singleUserName}/`;
564581
const podUri = isRootPod ? `${baseUrl}/` : `${baseUrl}/${singleUserName}/`;
565582
const displayName = isRootPod ? 'me' : singleUserName;
@@ -595,12 +612,22 @@ export function createServer(options = {}) {
595612
// this, single-user + --idp produces a pod but no credential, and
596613
// registration is intentionally disabled in single-user mode — so
597614
// the pod is unloggable until a password is set externally (#323).
598-
if (idpEnabled && !isRootPod) {
615+
//
616+
// Root pods (#348) need this too: the pod has no name, but the IDP
617+
// still needs *some* username for the login form. Default to 'me'
618+
// — matches the WebID fragment, fits the historical convention.
619+
if (idpEnabled) {
620+
// The IDP also persists `podName` and surfaces it as the
621+
// `name` claim under the OIDC `profile` scope (see
622+
// src/idp/accounts.js). For root pods we use 'me' here too —
623+
// a null podName would leak through as a null/missing
624+
// profile.name on every login, which OIDC clients expect to
625+
// be a non-empty human-readable string.
599626
await seedSingleUserIdpAccount({
600627
fastify,
601-
username: singleUserName,
628+
username: isRootPod ? 'me' : singleUserName,
602629
webId,
603-
podName: singleUserName,
630+
podName: isRootPod ? 'me' : singleUserName,
604631
providedPassword: singleUserPassword
605632
});
606633
}

test/config.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,35 @@ describe('config — --single-user implies --idp (#331)', () => {
158158
'--no-idp without --single-user should not trigger the #331 warning');
159159
});
160160
});
161+
162+
// #348: the user-visible default change — `jss start --single-user`
163+
// (no name flag) must produce a config where singleUserName is null,
164+
// so createServer() takes the root-pod path. createServer() has its
165+
// own tests but a future refactor of loadConfig() could silently
166+
// restore the old `'me'` default and only the server-level tests
167+
// would catch it via behaviour, not the config layer directly.
168+
describe('config — singleUserName default (#348)', () => {
169+
it('loadConfig() returns singleUserName=null when no flag/env is set', async () => {
170+
delete process.env.JSS_SINGLE_USER_NAME;
171+
const cfg = await loadConfig({}, null);
172+
assert.strictEqual(cfg.singleUserName, null,
173+
'default must be null (= root pod), not the legacy "me"');
174+
});
175+
176+
it('loadConfig() preserves an explicit singleUserName CLI arg', async () => {
177+
delete process.env.JSS_SINGLE_USER_NAME;
178+
const cfg = await loadConfig({ singleUserName: 'alice' }, null);
179+
assert.strictEqual(cfg.singleUserName, 'alice');
180+
});
181+
182+
it('loadConfig() respects JSS_SINGLE_USER_NAME from env', async () => {
183+
process.env.JSS_SINGLE_USER_NAME = 'me';
184+
try {
185+
const cfg = await loadConfig({}, null);
186+
assert.strictEqual(cfg.singleUserName, 'me',
187+
'env var should restore the legacy "me" pod path on demand');
188+
} finally {
189+
delete process.env.JSS_SINGLE_USER_NAME;
190+
}
191+
});
192+
});

test/idp.test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,84 @@ describe('Identity Provider - Root pod type index ACLs', () => {
458458
});
459459
});
460460

461+
// #348: --single-user with no name flag now defaults to a root pod
462+
// (was '/me/' historically). The server-side seed must land the
463+
// profile at /profile/card.jsonld, not /me/profile/card.jsonld.
464+
describe('Single-user default — root pod (#348)', () => {
465+
let server;
466+
let baseUrl;
467+
const DEFAULT_DATA_DIR = './test-data-348-default-root';
468+
const ROOT_POD_PASSWORD = 'root-pod-test-pw';
469+
470+
before(async () => {
471+
await fs.remove(DEFAULT_DATA_DIR);
472+
await fs.ensureDir(DEFAULT_DATA_DIR);
473+
474+
const port = await getAvailablePort();
475+
baseUrl = `http://${TEST_HOST}:${port}`;
476+
477+
server = createServer({
478+
logger: false,
479+
root: DEFAULT_DATA_DIR,
480+
idp: true,
481+
idpIssuer: baseUrl,
482+
singleUser: true,
483+
// singleUserName intentionally omitted — exercises the new default.
484+
// Provide a password so the seeding path runs non-interactively.
485+
singleUserPassword: ROOT_POD_PASSWORD,
486+
forceCloseConnections: true,
487+
});
488+
489+
await server.listen({ port, host: TEST_HOST });
490+
});
491+
492+
after(async () => {
493+
await server.close();
494+
await fs.remove(DEFAULT_DATA_DIR);
495+
});
496+
497+
it('seeds the profile at /profile/card.jsonld (not /me/profile/...)', async () => {
498+
const root = await fetch(`${baseUrl}/profile/card.jsonld`);
499+
assert.strictEqual(root.status, 200,
500+
'--single-user with no name should default to a root pod');
501+
// Check the filesystem directly — an HTTP-only check could pass
502+
// on a 401 even if /me/ data was somehow seeded, which would
503+
// hide the regression we care about (root vs /me/ pod).
504+
assert.strictEqual(await fs.pathExists(path.join(DEFAULT_DATA_DIR, 'me/profile/card.jsonld')), false,
505+
'no /me/ pod files should be created when singleUserName is unset');
506+
assert.strictEqual(await fs.pathExists(path.join(DEFAULT_DATA_DIR, 'me/profile/card')), false,
507+
'no legacy /me/ pod files should be created either');
508+
});
509+
510+
it('WebID resolves at the server origin', async () => {
511+
const res = await fetch(`${baseUrl}/profile/card.jsonld`);
512+
const body = await res.json();
513+
const webId = `${baseUrl}/profile/card.jsonld#me`;
514+
const matches = Array.isArray(body)
515+
? body.some(n => n['@id'] === webId)
516+
: body['@id'] === webId || (body['@graph'] || []).some(n => n['@id'] === webId);
517+
assert.ok(matches, `profile should declare WebID ${webId}, got: ${JSON.stringify(body).slice(0, 200)}`);
518+
});
519+
520+
it('seeds an IDP account for "me" so the root pod is loggable', async () => {
521+
// Round-2 review of #348: a regression here would mean a fresh
522+
// `jss start --single-user --idp` produces a pod nobody can log
523+
// in to (registration is disabled in single-user mode, so there
524+
// would be no recovery path other than out-of-band account
525+
// creation). Use the credentials endpoint as a black-box login
526+
// probe — if it issues a token, the seed worked.
527+
const res = await fetch(`${baseUrl}/idp/credentials`, {
528+
method: 'POST',
529+
headers: { 'Content-Type': 'application/json' },
530+
body: JSON.stringify({ username: 'me', password: ROOT_POD_PASSWORD }),
531+
});
532+
assert.strictEqual(res.status, 200,
533+
`login as "me" should succeed for the default root pod (got ${res.status})`);
534+
const body = await res.json();
535+
assert.ok(body.access_token, 'response should carry an access token');
536+
});
537+
});
538+
461539
describe('Identity Provider - Accounts', () => {
462540
let server;
463541
let accountsUrl;

test/url.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ describe('getPodName', () => {
3333
assert.strictEqual(getPodName(req), '.');
3434
});
3535

36+
it("returns '.' for a root pod (singleUserName null — #348 default)", () => {
37+
// server.js normalizes '/' and '' to null at the top of
38+
// createServer, so most root-pod requests now reach getPodName
39+
// with singleUserName === null. Pin that path explicitly.
40+
const req = { singleUser: true, singleUserName: null, url: '/index.html' };
41+
assert.strictEqual(getPodName(req), '.');
42+
});
43+
3644
it('returns singleUserName for a named pod, regardless of URL', () => {
3745
const req = { singleUser: true, singleUserName: 'me', url: '/index.html' };
3846
assert.strictEqual(getPodName(req), 'me');

0 commit comments

Comments
 (0)