{"record":{"id":"741188786bfa5547","repo":"santifer/career-ops","slug":"unknown-fixture-state-state-have-states-join-none","errorCode":null,"errorMessage":"Unknown fixture state: ${state} (have: ${states.join(', ') || 'none'})","messagePattern":"Unknown fixture state: (.+?) \\(have: (.+?)\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"seed-fixture.mjs","lineNumber":62,"sourceCode":"  // returns filesystem order, which varies).\n  //\n  // A checkout under test-fixtures/upgrade/ is excluded HERE, not only in\n  // walk(): this list is the state allowlist, and walk() starts AT the state\n  // directory, where the child-only guard cannot see it (the walk root is\n  // deliberately exempt). Leaving it in would make a whole second repository an\n  // allowlisted fixture state and seed it into the install under test (#3762).\n  return readdirSync(FIXTURES).sort()\n    .filter((n) => statSync(join(FIXTURES, n)).isDirectory() && !isNestedCheckout(join(FIXTURES, n)));\n}\n\n// Strict allowlist: a state must be one of the real fixture subdirectories.\n// Membership in listStates() (basenames only) inherently rejects both unknown\n// states and traversal strings ('../x', 'state/../..'), so no join() ever\n// escapes FIXTURES.\nfunction assertKnownState(state) {\n  const states = listStates();\n  if (!states.includes(state)) {\n    throw new Error(`Unknown fixture state: ${state} (have: ${states.join(', ') || 'none'})`);\n  }\n}\n\nexport function seedFixture(targetDir, { state = DEFAULT_STATE } = {}) {\n  if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) throw new Error(`targetDir is not an existing directory: ${targetDir}`);\n  assertKnownState(state);\n  const src = join(FIXTURES, state);\n  // expected.json is harness metadata, not user data — never seed it.\n  const files = walk(src).filter((f) => f !== 'expected.json');\n  const manifest = {};\n  for (const f of files) {\n    const dest = join(targetDir, f);\n    mkdirSync(dirname(dest), { recursive: true });\n    cpSync(join(src, f), dest);\n    manifest[f] = createHash('sha256').update(readFileSync(dest)).digest('hex');\n  }\n  return { state, files, manifest };\n}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/seed-fixture.mjs#L44-L80","documentation":"assertKnownState() in seed-fixture.mjs throws this when a requested fixture state is not among the directories currently under the FIXTURES root (as returned by listStates(), basenames only). The guard doubles as a traversal defense: unknown names and path-like strings ('../x', 'state/../..') can never pass the membership check, so join(FIXTURES, state) can never escape the fixtures directory.","triggerScenarios":"seedFixture(targetDir, { state: 'no-such-state' }) or any state string that is not an exact basename of a directory in fixtures/ — including typos, stale state names after a fixture rename, or path-containing strings.","commonSituations":"Typo in the state option; test suite referencing a fixture state that was renamed/removed; passing a path fragment or env-supplied state value that was never validated upstream; running in a checkout where the fixtures directory is empty (message then shows 'have: none').","solutions":["List the valid states (the error message includes them, or call listStates() yourself) and use one exactly.","Fix the typo in the state option or test constant.","If the state was renamed, update the caller to the new fixture directory name.","If the message says 'have: none', the fixtures directory is missing or empty — restore it from the repo."],"exampleFix":"// before\nawait seedFixture(dir, { state: 'aplied' }); // typo\n// after\nawait seedFixture(dir, { state: 'applied' }); // must match a fixtures/<state> directory exactly","handlingStrategy":"type-guard","validationCode":"import { listStates } from './seed-fixture.mjs';\nif (!listStates().includes(state)) {\n  throw new Error(`Invalid state '${state}'; valid: ${listStates().join(', ')}`);\n}","typeGuard":"const isKnownState = (state, states = listStates()) => states.includes(state);","tryCatchPattern":"try {\n  await seedFixture(dir, { state });\n} catch (err) {\n  if (err.message.startsWith('Unknown fixture state')) {\n    console.error(`${err.message} — check for typos or a renamed fixture directory`);\n  } else throw err;\n}","preventionTips":["Derive state names from listStates() instead of hardcoding strings in tests.","Treat state as a closed enum: define it as a constant list kept in sync with the fixtures directory.","Validate any user/env-supplied state value before passing it to seedFixture.","Never embed path separators in a state name — exact basenames only."],"tags":["fixtures","invalid-argument","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}