@truenas/api-client 6.0.2 → 7.0.0
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/dist/{chunk-PQIVBXN3.cjs → chunk-QJRF5DK4.cjs} +6 -11
- package/dist/chunk-QJRF5DK4.cjs.map +1 -0
- package/dist/{chunk-7QVRLQIZ.js → chunk-SAJ5HZHK.js} +7 -12
- package/dist/chunk-SAJ5HZHK.js.map +1 -0
- package/dist/index.cjs +36 -36
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/testing/index.cjs +10 -10
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +2 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/{truenas-auth-mechanism.enum-DikfjVxa.d.cts → truenas-auth-mechanism.enum-CHo_zr2p.d.cts} +1479 -1267
- package/dist/{truenas-auth-mechanism.enum-DikfjVxa.d.ts → truenas-auth-mechanism.enum-CHo_zr2p.d.ts} +1479 -1267
- package/package.json +1 -1
- package/dist/chunk-7QVRLQIZ.js.map +0 -1
- package/dist/chunk-PQIVBXN3.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/unmocked-call-error.ts","../../src/testing/fake-connection.ts","../../src/testing/fake-authenticator.ts","../../src/testing/present.ts","../../src/testing/fake-api-error.ts","../../src/testing/fake-job.ts","../../src/testing/mock-answers.ts","../../src/testing/create-fake-client.ts","../../src/testing/with-spies.ts","../../src/testing/fake-api-version.ts","../../src/testing/fake-auth-response.ts"],"names":["TrueNasConnection","noopLogger","Subject","distinctUntilChanged","takeUntil","filter","map","Subscription","TrueNasAuthenticator","SUPPORTED_API_VERSIONS","parseApiVersion","clientClassFor"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Thrown when a strict fake connection is handed a frame nothing will answer.\n *\n * Named rather than a bare `Error` so a consumer's global guard can tell \"the\n * spec forgot a mock\" from a failure the code under test produced.\n *\n * It cannot travel as an error *frame* — `dispatch` reduces any frame's error\n * to `new Error(getApiErrorMessage(…))` — so it is thrown from `send`. A\n * `TrueNasApi` verb dispatches inside a `defer` and so fails its observable;\n * the authenticator sends from its own body and throws at the call site, which\n * in practice is `logout` and `newApiKey` — the two `FakeAuthenticator` does\n * not auto-reply.\n */\nexport class UnmockedCallError extends Error {\n constructor(\n readonly method: string,\n readonly params: unknown\n ) {\n super(\n `Unmocked call: nothing is scripted to answer '${method}'` +\n `${params === undefined ? '' : ` with ${JSON.stringify(params)}`}. ` +\n 'Script it with mock.call / mock.query / mock.job, answer the frame ' +\n 'with connection.reply, or build the client without { strict: true }.'\n );\n this.name = 'UnmockedCallError';\n }\n}\n","import { Subject, Subscription, distinctUntilChanged, filter, map, takeUntil } from 'rxjs';\nimport { TrueNasConnection } from '@/connection/truenas-connection';\nimport { noopLogger } from '@/logger';\nimport type { TrueNasErrorFrame } from '@/types/api-error.type';\nimport type { TrueNasMessage } from '@/types/truenas-message.type';\nimport { UnmockedCallError } from './unmocked-call-error';\n\n/** How a fake connection is set up. Every field has a usable default. */\nexport interface FakeConnectionOptions {\n /** Reported as the appliance's uuid; only ever used for log correlation. */\n uuid?: string;\n /** The path the real connection would have opened a socket on. */\n websocketPath?: string;\n /**\n * Whether the connection starts in the opened state. Defaults to `true`, as\n * it does through `createFakeClient`: a closed connection queues sends\n * instead of writing them, which is rarely what a spec building one directly\n * is after.\n */\n opened?: boolean;\n /**\n * Whether a frame nothing is scripted to answer fails instead of hanging.\n * Defaults to `false`.\n *\n * Off by default because the two ways to answer cannot be told apart at\n * `send` time. A spec that scripts everything with `mock` has registered its\n * answers before the frame goes out; a spec that drives frames by hand calls\n * `reply` *after* it — so refusing an unregistered method would break every\n * hand-driven spec, 24 of them in this repo's own suite. Strictness is the\n * spec's claim that it scripted everything, which only the spec can make.\n */\n strict?: boolean;\n}\n\n/**\n * Frames the client sends for itself, which no spec should have to script.\n *\n * `core.subscribe` is the only one reachable: for the job stream once\n * authenticated, and again per `events()` name. If another method starts\n * arriving through `send`, a strict spec fails naming it.\n *\n * The other two are absent for different reasons. `core.unsubscribe` is never\n * sent, so it would fail loudly if it were. `core.ping` would not — it goes\n * out through `ws.next(…)` rather than `send`, so it cannot reach this check,\n * and a fake connection never yields a socket for it to fire on.\n */\nconst CLIENT_HOUSEKEEPING = new Set(['core.subscribe']);\n\n/**\n * A `TrueNasConnection` that never opens a socket, and that a test drives by\n * hand.\n *\n * A real subclass, so the real `TrueNasApi` and clients run on it without an\n * `as unknown as` cast. `send` matches the real one: it queues while closed,\n * writes on open, and drops frames whose caller unsubscribed. The rest is\n * inherited and inert, except the base 20-second ping timer, which never sends\n * but lives until `close()`.\n */\nexport class FakeConnection extends TrueNasConnection {\n private readonly incoming = new Subject<TrueNasMessage>();\n\n private readonly frames: TrueNasMessage[] = [];\n\n /** Frames handed to `send` while closed, waiting for the socket. */\n private readonly queued: TrueNasMessage[] = [];\n\n private readonly autoReplies = new Map<string, (frame: TrueNasMessage) => void>();\n\n /** Set by `close()`. Latched, because the real connection cannot come back. */\n private terminated = false;\n\n /** See {@link FakeConnectionOptions.strict}. Assigned in the constructor. */\n private readonly strict: boolean;\n\n /**\n * The canonical streams, re-pointed at what this class actually drives.\n *\n * The base derives all three from `connection$`, which on a disabled\n * connection reports one state and then nothing: `{ state: 'closed' }`. So\n * they describe the connection that never connects, not the one the test is\n * driving — `messages$` stayed silent while `receive` delivered, `opened$`\n * stayed `[false]` through every simulated open, and `closed$` reported a\n * connection that had never been up. `messages()` is a shim over `messages$`;\n * leaving them disagreeing puts the divergence in the members a consumer is\n * most likely to reach for.\n */\n override messages$ = this.incoming.asObservable();\n\n override opened$ = this.opened.pipe(\n distinctUntilChanged(),\n takeUntil(this.closeConnection)\n );\n\n override closed$ = this.opened.pipe(\n distinctUntilChanged(),\n filter(isOpen => !isOpen),\n map(() => undefined),\n takeUntil(this.closeConnection)\n );\n\n constructor(options: FakeConnectionOptions = {}) {\n super(\n // Disabled: `connection$` derives from this, so no socket is ever built\n // and none of the retry machinery runs.\n false,\n [],\n options.uuid ?? 'fake-uuid',\n options.websocketPath ?? '/api/current',\n undefined,\n 0,\n 0,\n noopLogger\n );\n\n this.strict = options.strict ?? false;\n\n if (options.opened ?? true) this.simulateOpen();\n }\n\n /** Every frame handed to `send`, in order. */\n get sent(): readonly TrueNasMessage[] {\n return this.frames;\n }\n\n /**\n * Records the frame, or queues it until the connection opens.\n *\n * The real `send` queues on `ws$` until a socket exists and returns the\n * subscription holding that queue open; a caller who gives up unsubscribes\n * it and the frame never goes out. Both halves are reproduced, because\n * \"records everything regardless of `opened`\" is precisely the divergence\n * the hand-rolled double had — it forwarded straight through — and moving it\n * in here would have been the same bug in a better-looking place.\n */\n override send(message: TrueNasMessage): Subscription {\n // Before the terminated and queued paths, so a strict spec is told what it\n // failed to script whatever state the connection is in. A frame that is\n // going to hang forever hangs the same way closed as it does open.\n if (\n this.strict &&\n message.method !== undefined &&\n !this.autoReplies.has(message.method) &&\n !CLIENT_HOUSEKEEPING.has(message.method)\n ) {\n throw new UnmockedCallError(message.method, message.params);\n }\n\n if (this.terminated) {\n const dropped = new Subscription();\n dropped.unsubscribe();\n return dropped;\n }\n\n if (!this.opened.getValue()) {\n this.queued.push(message);\n return new Subscription(() => {\n const at = this.queued.indexOf(message);\n if (at >= 0) this.queued.splice(at, 1);\n });\n }\n\n this.write(message);\n\n // The frame has gone out, so there is nothing left for a caller to\n // abandon; an already-closed subscription says exactly that.\n const done = new Subscription();\n done.unsubscribe();\n return done;\n }\n\n /**\n * Answer any frame for `method` as soon as it is written.\n *\n * The narrow ancestor of `mockCall`: enough to let a collaborator that talks\n * to the appliance — the authenticator, for one — run its real code against\n * a scripted answer rather than be replaced by a fake of itself.\n *\n * One answer per method, last registration wins. The one it replaced is\n * returned so a caller that needs to share the method can chain to it rather\n * than take it over silently.\n */\n autoReply(\n method: string,\n answer: (frame: TrueNasMessage) => void\n ): ((frame: TrueNasMessage) => void) | undefined {\n const replaced = this.autoReplies.get(method);\n this.autoReplies.set(method, answer);\n return replaced;\n }\n\n private write(message: TrueNasMessage): void {\n this.frames.push(message);\n\n const answer = this.autoReplies.get(String(message.method));\n if (!answer) return;\n\n // On a microtask, not inline. A real reply crosses a socket, so it cannot\n // land before the caller has subscribed — and some senders rely on that:\n // `TrueNasAuthenticator` sends its frame when the login method is *called*\n // and returns the observable for the caller to subscribe afterwards, so an\n // inline answer arrives with nobody listening and the login never settles.\n queueMicrotask(() => answer(message));\n }\n\n /** Deliver a message as if middleware had sent it. */\n receive(message: TrueNasMessage): void {\n this.incoming.next(message);\n }\n\n /**\n * Answer the most recent frame carrying `method`, by its id.\n *\n * Ids are what correlate a reply to a request, and a test that had to read\n * them itself would be asserting on the id generator. Most recent rather\n * than first: a spec that calls the same method twice is asking about the\n * second one.\n */\n reply(method: string, result: unknown): void {\n this.receive({ jsonrpc: '2.0', id: this.idOf(method), result });\n }\n\n /**\n * As {@link reply}, with a JSON-RPC error instead of a result.\n *\n * Typed as the frame `/api/<version>` sends and nothing else: `code` and\n * `message` outside, the TrueNAS payload under `data`. The legacy\n * `/websocket` shape — those payload fields at the top level — is not\n * accepted, so a spec cannot script an error this client can never receive.\n * It was accepted until now only because the parameter was the loose\n * `ApiError` union, which admits both.\n *\n * The cast is gone with it: the frame's own type says this is what an error\n * looks like.\n */\n replyError(method: string, error: TrueNasErrorFrame): void {\n this.receive({ jsonrpc: '2.0', id: this.idOf(method), error });\n }\n\n /**\n * Raise `opened` and flush whatever was queued, as the socket opening would.\n *\n * The queue drains after `opened` goes true, which is the order the real\n * connection produces: `send` is waiting on `ws$`, and `ws$` emits because a\n * socket arrived.\n */\n simulateOpen(): void {\n // The real `opened` reaches subscribers through `distinctUntilChanged`, so\n // it never emits `true` twice running. Emitting it here anyway made the\n // client re-run everything keyed on the transition — a second\n // `auth.login_ex` the real one never sends.\n if (this.terminated || this.opened.getValue()) return;\n\n this.opened.next(true);\n const pending = this.queued.splice(0, this.queued.length);\n for (const message of pending) this.write(message);\n }\n\n /**\n * Lower `opened` and fire `closed`, as a socket closing would.\n *\n * Named for what it simulates rather than overriding `close()`, which on the\n * real connection means \"stop, and do not retry\" — a different verb that\n * callers already use for teardown.\n */\n simulateClose(): void {\n if (this.terminated || !this.opened.getValue()) return;\n\n this.opened.next(false);\n this.closed.next();\n }\n\n /**\n * Terminal, as on the real connection: the streams end and nothing more is\n * written.\n *\n * `simulateClose` is the socket dropping — recoverable, and the client will\n * try again. This is the caller giving up on the connection for good, so the\n * message stream completes and `send` has nowhere to put a frame.\n */\n override close(): void {\n if (this.terminated) return;\n\n // Not `simulateClose()`. The real `close()` ends `connection$` through\n // `takeUntil`, which completes its subscribers without emitting: `opened`\n // keeps its last value and `closed` never fires. Routing through the\n // socket-drop path instead made `close()` log the client out, because\n // `TrueNasAuthenticator` lowers `authenticated$` on `closed` — a\n // difference from the real connection introduced by the fix for the\n // previous round's finding that `close()` did nothing at all.\n this.terminated = true;\n this.incoming.complete();\n super.close();\n }\n\n private idOf(method: string): string {\n const frame = [...this.frames].reverse().find(m => m.method === method);\n if (!frame) {\n throw new Error(\n `No frame for '${method}' has been sent; sent so far: ` +\n `${this.frames.map(m => String(m.method)).join(', ') || '(none)'}`\n );\n }\n return frame.id ?? '';\n }\n}\n","import type { Observable } from 'rxjs';\nimport { TrueNasAuthenticator } from '@/auth/truenas-authenticator';\nimport { TrueNasAuthMechanism } from '@/enums/truenas-auth-mechanism.enum';\nimport type { ApiVersion } from '@/types/api-version.type';\nimport { AuthResponseType, type AuthResponse } from '@/types/auth.type';\nimport type { FakeConnection } from './fake-connection';\n\n/** One recorded login attempt. */\nexport interface RecordedLogin {\n mechanism: TrueNasAuthMechanism;\n /** The username, token or key the caller passed, whichever the mechanism takes. */\n credential: string;\n}\n\n/**\n * A `TrueNasAuthenticator` that records its logins and answers them without a\n * socket.\n *\n * Every override records the attempt and then calls the real method, so the\n * real code sends the frame, stores credentials and interprets the response.\n * `succeedNextLogin` / `failNextLogin` only script the connection's reply — a\n * fake that decided success itself would disagree with the real client on\n * what an answer means.\n */\nexport class FakeAuthenticator extends TrueNasAuthenticator {\n private readonly attempts: RecordedLogin[] = [];\n\n /** Armed by the scripting methods; consumed by the next login that is made. */\n private armed: AuthResponse | undefined;\n\n /**\n * Answers waiting for their frames, oldest first.\n *\n * Bound when the login is *made*, not when the reply goes out. Reading an\n * armed answer at reply time meant a second login armed in the meantime\n * answered the first — two logins in flight is exactly what testing\n * supersession looks like, so scripting both is the ordinary case, and it\n * produced the same wrong verdict this class was rewritten to stop\n * producing.\n */\n private readonly pending: AuthResponse[] = [];\n\n constructor(\n private readonly fakeConnection: FakeConnection,\n version?: ApiVersion\n ) {\n super(fakeConnection, version);\n\n // Unscripted logins succeed. The common unit test does not care about\n // authenticating and should not have to arrange it; the ones that do care\n // script the next attempt.\n //\n // This occupies the connection's `auth.login_ex` answer. A spec that\n // registers its own for that method takes login scripting with it.\n this.fakeConnection.autoReply('auth.login_ex', frame => {\n this.fakeConnection.receive({\n jsonrpc: '2.0',\n id: frame.id,\n result: this.pending.shift() ?? this.success(),\n });\n });\n }\n\n /**\n * Every login attempt, in order.\n *\n * Including the ones the base class makes itself: the auto-relogin runs\n * through these same methods, and nothing here distinguishes it from a\n * caller's login. A spec counting logins across a reconnect should expect\n * that one.\n */\n get logins(): readonly RecordedLogin[] {\n return this.attempts;\n }\n\n /** The next login is answered with this response. Defaults to a success. */\n succeedNextLogin(response: Partial<AuthResponse> = {}): void {\n this.armed = { response_type: AuthResponseType.Success, ...response };\n }\n\n /**\n * The next login is answered with a rejection.\n *\n * A `response_type` rather than an `Error`, so what the caller sees is\n * whatever the real authenticator makes of it — the `AuthError` codes\n * included, which a hand-thrown `Error` could not produce.\n */\n failNextLogin(responseType: AuthResponseType = AuthResponseType.AuthErr): void {\n this.armed = { response_type: responseType };\n }\n\n /**\n * Records the attempt and binds its answer, in that order, before the real\n * method puts a frame on the wire.\n */\n private record(mechanism: TrueNasAuthMechanism, credential: string): void {\n this.attempts.push({ mechanism, credential });\n this.pending.push(this.armed ?? this.success());\n this.armed = undefined;\n }\n\n private success(): AuthResponse {\n return { response_type: AuthResponseType.Success };\n }\n\n override loginWithUserPass(username: string, password: string): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.Password, username);\n return super.loginWithUserPass(username, password);\n }\n\n override loginWithToken(token: string): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.Token, token);\n return super.loginWithToken(token);\n }\n\n override loginWithApiKey(credentials: { username: string; key: string }): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.ApiKey, credentials.username);\n return super.loginWithApiKey(credentials);\n }\n\n override loginWithOtp(code: string): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.Otp, code);\n return super.loginWithOtp(code);\n }\n}\n","/**\n * Fields whose value is literally `undefined` are dropped rather than applied.\n *\n * Every builder here takes a `Partial<…>`, so `{ state: done ? Success :\n * undefined }` compiles — and spreading that over a default puts `undefined`\n * where a required field is declared. The object then fails in whatever reads\n * it, one layer away from the fixture that made it. Dropping them makes an\n * absent field mean \"unchanged\", which is what a partial means everywhere else\n * here.\n */\nexport function present<T extends object>(value: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(value).filter(([, v]) => v !== undefined)\n ) as Partial<T>;\n}\n","import type {\n TrueNasErrorData,\n TrueNasErrorFrame,\n} from '@/types/api-error.type';\nimport { present } from './present';\n\n/**\n * Flat overrides for a nested frame: say what varies, get the real shape.\n *\n * Picked field by field rather than `Partial<TrueNasErrorData>`, which would\n * inherit that type's `[key: string]: unknown` — legitimate on the payload,\n * since middleware adds `py_exception` there, and fatal on an overrides bag,\n * because it turns off excess-property checking. `fakeApiError({ resaon: … })`\n * typechecked, put `resaon` in the payload and left `reason` at its default.\n */\nexport interface FakeApiErrorOverrides\n extends Partial<\n Pick<TrueNasErrorData, 'error' | 'errname' | 'reason' | 'extra' | 'trace'>\n > {\n /** JSON-RPC code. Defaults to `-32001`, middleware's \"method call error\". */\n code?: number;\n /** JSON-RPC message. Defaults to `'Method call error'`, which is what `rpc.py` sends. */\n message?: string;\n}\n\n/** A reason that is a bare `repr()` — an exception raised with no arguments. */\nconst BARE_REPR = /^([A-Za-z_][A-Za-z0-9_]*)\\(\\)$/;\n\n/**\n * Characters CPython's `str.isprintable()` calls unprintable: categories Cc,\n * Cf, Cs, Co, Cn, Zl, Zp and Zs, with U+0020 the one exception. Printable\n * non-ASCII — `é`, `日`, `😀` — is left alone, as `repr()` leaves it.\n *\n * `Cn` is the one category this cannot get exactly right. \"Unassigned\" is a\n * property of a code point *in a Unicode version*, and this class reads the\n * engine's table while the appliance's `repr()` reads its Python's. They agree\n * on the code points unassigned in both, which is almost all of them, and\n * disagree either way on the rest; neither side is reliably newer. Everything\n * below U+0100 is exact, and so is any reason made of ordinary prose.\n */\nconst UNPRINTABLE = /[\\p{Cc}\\p{Cf}\\p{Cs}\\p{Co}\\p{Cn}\\p{Zl}\\p{Zp}\\p{Zs}]/u;\n\n/** One code point, escaped the way `repr()` escapes it. */\nfunction escapeCodePoint(character: string): string {\n if (character === '\\n') return '\\\\n';\n if (character === '\\r') return '\\\\r';\n if (character === '\\t') return '\\\\t';\n\n // `\\b`, `\\f` and `\\v` are not among them: `repr('\\x08')` is `'\\x08'`.\n //\n // Non-null rather than `?? 0`: `character` comes from `for…of` over a\n // string, which never yields an empty one, and a fallback here would\n // silently emit `\\x00` for a character it could not read.\n const code = character.codePointAt(0)!;\n if (code < 0x100) return `\\\\x${code.toString(16).padStart(2, '0')}`;\n if (code < 0x10000) return `\\\\u${code.toString(16).padStart(4, '0')}`;\n return `\\\\U${code.toString(16).padStart(8, '0')}`;\n}\n\n/**\n * A Python `repr()` of a string, quoting and escaping the way CPython does —\n * within the one limit {@link UNPRINTABLE} describes.\n *\n * `repr` picks `\"` when the string holds a `'` and no `\"`, escapes the\n * backslash and the chosen quote, and escapes every code point\n * `str.isprintable()` rejects: `\\xNN` below U+0100, `\\uNNNN` below U+10000,\n * `\\UNNNNNNNN` above, with `\\n`, `\\r` and `\\t` the short forms. Ordinary\n * middleware messages need all of it — `{x!r}` f-strings carry single quotes,\n * and `adapt_exception` interpolates raw stderr after a newline.\n *\n * Checked against `python3`, not against expectations written here; see\n * `fake-api-error.spec.ts` and `python-repr.spec.ts`. Exported for those alone.\n */\nexport function pythonRepr(value: string): string {\n const quote = value.includes(\"'\") && !value.includes('\"') ? '\"' : \"'\";\n\n let escaped = '';\n for (const character of value) {\n if (character === '\\\\') escaped += '\\\\\\\\';\n else if (character === quote) escaped += `\\\\${quote}`;\n else if (character === ' ' || !UNPRINTABLE.test(character)) escaped += character;\n else escaped += escapeCodePoint(character);\n }\n\n return `${quote}${escaped}${quote}`;\n}\n\n/**\n * A trace whose `class` and `repr` agree with the reason, so those two are a\n * pair an appliance could send. `formatted` is outside that: middleware builds\n * it from `traceback.format_exception`, whose frames a fixture cannot produce.\n *\n * It is an object rather than `null` because both arms sending `-32001` pass\n * `sys.exc_info()`, always truthy inside an `except`.\n *\n * The rule follows the generic arm's `str(error) or repr(error)`: a bare repr\n * means an argument-free exception, so the class is its name; anything else is\n * `ValueError` with the call written out. `CallError` is never argument-free\n * and so is deliberately not used.\n */\nfunction syntheticTrace(reason: string): NonNullable<TrueNasErrorData['trace']> {\n const bare = BARE_REPR.exec(reason);\n const cls = bare ? bare[1] : 'ValueError';\n const repr = bare ? reason : `ValueError(${pythonRepr(reason)})`;\n\n return {\n class: cls,\n formatted: `Traceback (most recent call last):\\n <synthetic>\\n${repr}\\n`,\n repr,\n };\n}\n\n/**\n * The error a method call fails with on `/api/<version>`.\n *\n * The versioned endpoint wraps the TrueNAS payload in a JSON-RPC error —\n * `{code: -32001, message: 'Method call error', data: {…}}` — and that nesting\n * is the part worth a builder. The flat `{error, errname, extra, reason}` is\n * `/websocket`'s, a route this client never opens; `getApiErrorMessage` reads\n * either, so only a consumer branching on `error.code` or `error.data.errname`\n * finds out. Verified at middleware `4303dc8`, `ws_handler/rpc.py:81-124`.\n *\n * `error` and `errname` are the caller's to keep consistent; middleware\n * derives one from the other. `trace` defaults to one whose `class` and `repr`\n * agree with `reason`; pass `null` only for the job-event payload.\n */\nexport function fakeApiError(overrides: FakeApiErrorOverrides = {}): TrueNasErrorFrame {\n const { code, message, ...data } = overrides;\n const reason = data.reason ?? 'Invalid argument';\n\n return {\n code: code ?? -32001,\n message: message ?? 'Method call error',\n data: {\n error: 22,\n errname: 'EINVAL',\n reason,\n extra: null,\n trace: syntheticTrace(reason),\n ...present(data),\n } satisfies TrueNasErrorData,\n };\n}\n","import { JobState, type Job, type JobProgress } from '@/types/job.type';\nimport { present } from './present';\n\n/**\n * Overrides for {@link fakeJob}. `progress` is partial one level down, so a\n * fixture naming a percent need not name the description beside it. Named so\n * a consumer can write a helper that takes one, as the other builders are.\n */\nexport interface FakeJobOverrides<R = unknown>\n extends Partial<Omit<Job<R>, 'progress'>> {\n progress?: Partial<JobProgress>;\n}\n\n/**\n * A complete `Job`, so a scripted one is the shape a caller actually reads —\n * a partial fixture cast into place leaves `progress` undefined and breaks\n * `job.progress.percent`.\n *\n * Defaults are what middleware sends for a job not yet started, including\n * `result: null`. `satisfies` (not a cast) makes a regeneration that adds a\n * required field fail here.\n */\nexport function fakeJob<R = unknown>(\n overrides: FakeJobOverrides<R> = {}\n): Job<R> {\n // `JobProgress.description` is `string`, not `string | null`: the generated\n // shape allows null and `job.type.ts` narrows it, because the server sends a\n // string for a job that has started.\n const progress: JobProgress = {\n percent: 0,\n description: '',\n extra: null,\n ...present(overrides.progress ?? {}),\n };\n\n return {\n id: 1,\n method: 'test.job',\n arguments: [],\n transient: false,\n description: null,\n abortable: false,\n logs_path: null,\n logs_excerpt: null,\n result: null,\n result_encoding_error: null,\n error: null,\n exception: null,\n exc_info: null,\n state: JobState.Running,\n time_started: null,\n time_finished: null,\n credentials: null,\n ...present(overrides),\n progress,\n } satisfies Job<R>;\n}\n","import type { FakeConnection } from './fake-connection';\nimport type {\n ApiDirectoryShape,\n CallMethod,\n CallParams,\n CallResponse,\n EventName,\n EventUnion,\n JobMethod,\n JobResult,\n} from '@/types/api-directory.type';\nimport type { Job } from '@/types/job.type';\nimport { fakeApiError } from './fake-api-error';\nimport { fakeJob, type FakeJobOverrides } from './fake-job';\nimport type { QueryEntity, QueryMethod } from '@/types/query.type';\nimport type { TrueNasMessage } from '@/types/truenas-message.type';\n\n/**\n * Scripted answers, typed by the directory the client is typed against.\n *\n * Every one of these registers an *answer to a frame*, not a replacement for a\n * verb. The client's own `dispatch`, job correlation and subscription\n * bookkeeping run exactly as they do against an appliance; what changes is only\n * what comes back. That is what makes a spec written against this a test of the\n * client rather than a test of the double.\n */\nexport interface MockAnswers<D extends ApiDirectoryShape> {\n /**\n * Answer `method` with `response`, or with whatever the function makes of\n * the params it was called with.\n *\n * Query methods are excluded, and the exclusion is the point rather than a\n * restriction: they live in the call directory, so `mock.call('user.query',\n * 3)` is well-typed — a query method's response is the five-way union the\n * server may return, and a number is one arm — and then answers all three\n * query verbs from that one value. `mock.query` is where a collection is\n * scripted, from rows that feed the three of them consistently.\n */\n call<M extends Exclude<CallMethod<D>, QueryMethod<D['call']> & string>>(\n method: M,\n response: CallResponse<D, M> | ((params: CallParams<D, M>) => CallResponse<D, M>)\n ): void;\n\n /**\n * Answer a query method from one set of rows.\n *\n * `filters`, `select`, `order_by`, `limit` and `offset` are not applied: the\n * rows given are the rows answered. The frame still carries whatever the\n * caller sent, so a spec asserting on `sent` sees the real request — what is\n * not modelled is middleware's evaluation of it.\n *\n * Feeds all three verbs, because on the wire they are one method\n * distinguished by its options: `queryOne` sends `get: true` and gets the\n * first row, `queryCount` sends `count: true` and gets the row count, and\n * `query` gets the rows. Scripting them separately would let a spec disagree\n * with itself about what the collection holds.\n */\n query<M extends QueryMethod<D['call']> & string>(\n method: M,\n rows: QueryEntity<D['call'], M>[]\n ): void;\n\n /**\n * Answer a job method with the updates it should report, in order. Nothing\n * is synthesised: a sequence with no terminal state never completes.\n *\n * Each update is completed by {@link fakeJob} from its defaults, not from the\n * previous update — progress does not carry and success is not forced to\n * 100. `arguments` is not filled in from the starting call either, since a\n * job reached through `trackJob` has no such call.\n *\n * Returns the job's id (the first update may set it), for `trackJob`.\n */\n job<M extends JobMethod<D>>(\n method: M,\n updates: JobUpdate<JobResult<D, M>> | JobUpdate<JobResult<D, M>>[]\n ): number;\n\n /** Push a change to every subscriber of `events(event)`. */\n emit<E extends EventName<D>>(event: E, change: EventUnion<D, E>): void;\n}\n\n/**\n * One scripted job update. An alias rather than a second statement of the\n * shape: `job` completes each update through {@link fakeJob}, so the two\n * cannot usefully differ.\n */\nexport type JobUpdate<R> = FakeJobOverrides<R>;\n\n/**\n * The id a `core.get_jobs` read is asking about.\n *\n * `trackJob` sends `[[['id', '=', jobId]]]`. Anything else — an unfiltered\n * read, or a filter on another field — is not a question this module can\n * answer from its registry, so it says so rather than guessing at\n * `params[0][0][2]`.\n */\nfunction readJobId(read: TrueNasMessage): number | undefined {\n const [filters] = (read.params ?? []) as [unknown];\n if (!Array.isArray(filters) || filters.length !== 1) return undefined;\n\n const [clause] = filters as [unknown];\n if (!Array.isArray(clause) || clause.length !== 3) return undefined;\n\n const [field, operator, value] = clause as [unknown, unknown, unknown];\n if (field !== 'id' || operator !== '=' || typeof value !== 'number') return undefined;\n\n return value;\n}\n\n/** The options object a query verb sends as its second positional argument. */\ninterface QueryOptionsFrame {\n get?: boolean;\n count?: boolean;\n}\n\nexport function createMockAnswers<D extends ApiDirectoryShape>(\n connection: FakeConnection\n): MockAnswers<D> {\n /**\n * Ids for jobs a spec did not number itself.\n *\n * Per client, like every other registry here. Module-global, the id\n * `mock.job` returns depended on how many jobs every *other* client in the\n * process had registered first — so it moved as files were added, and a spec\n * asserting on the distance between two allocations was reading a counter\n * anything could advance.\n */\n let nextJobId = 1;\n\n const answer = (frame: TrueNasMessage, result: unknown): void => {\n connection.receive({ jsonrpc: '2.0', id: frame.id, result } as TrueNasMessage);\n };\n\n /** Scripted jobs by id: the whole walk, oldest first. */\n const sequences = new Map<number, Job[]>();\n\n /** How far along its walk each scripted job has been reported. */\n const position = new Map<number, number>();\n\n /** Jobs whose remaining updates are already on their way out. */\n const walking = new Set<number>();\n\n /** Ids a start has already run, so a later start does not reuse one. */\n const started = new Set<number>();\n\n /** The next id nothing else holds. */\n const allocateId = (): number => {\n while (sequences.has(nextJobId)) nextJobId++;\n return nextJobId++;\n };\n\n /** Methods a job is already scripted for. */\n const scriptedMethods = new Set<string>();\n\n /** The options a `core.get_jobs` read carried, if any. */\n const readOptions = (read: TrueNasMessage): QueryOptionsFrame | undefined => {\n const [, options] = (read.params ?? []) as [unknown, QueryOptionsFrame?];\n return options;\n };\n\n /**\n * Whether this read is the one a tracker opens with.\n *\n * `trackJob` sends only the filters; every query verb also sends an options\n * object (`query` sends `{}`). Checking the options for `get`/`count` instead\n * would mistake a plain `api.query` for a tracker and release the walk to\n * nobody. Unrecognised reads leave the walk alone, the harmless direction.\n */\n const isTracking = (read: TrueNasMessage): boolean =>\n ((read.params ?? []) as unknown[]).length === 1;\n\n /** Answer a `core.get_jobs` read in the shape its options asked for. */\n const answerRead = (read: TrueNasMessage, job: Job | undefined): void => {\n const options = readOptions(read);\n if (options?.count) return answer(read, job ? 1 : 0);\n if (options?.get) {\n // `do_get` raises `MatchNotFound` on an empty result rather than\n // returning nothing, and `queryOne` is typed non-nullable — so a `get`\n // with no job is an error, not `undefined`. The plain read still answers\n // `[]`, which is what lets `trackJob` on an unknown id complete instead\n // of hanging.\n return job ? answer(read, job) : notFound(read);\n }\n answer(read, job ? [job] : []);\n };\n\n /** Installed once, on the first scripted job. */\n let dispatcher: ((frame: TrueNasMessage) => void) | undefined;\n\n /** Whatever answered `core.get_jobs` before the dispatcher took it over. */\n let fallback: ((frame: TrueNasMessage) => void) | undefined;\n\n /**\n * Middleware's exact frame for a `get` that matched nothing — don't invent\n * friendlier text, or specs end up asserting a message only the fake sends.\n *\n * `MatchNotFound` is a bare `IndexError`, so `rpc.py`'s generic arm reports\n * `EINVAL` with its repr as the reason. `/api/<version>` nests that payload\n * under a JSON-RPC error's `data`; only legacy `/websocket` puts it top-level.\n */\n const notFound = (frame: TrueNasMessage): void => {\n errorTo(frame, fakeApiError({ reason: 'MatchNotFound()' }));\n };\n\n const errorTo = (frame: TrueNasMessage, error: NonNullable<TrueNasMessage['error']>): void => {\n connection.receive({ jsonrpc: '2.0', id: frame.id, error } as TrueNasMessage);\n };\n\n /**\n * One `core.get_jobs` answer for every scripted job, resolved by the id in\n * the read's filter — `autoReply` is last-write-wins per method, so one\n * registration per job would answer every read with the last job.\n *\n * Unknown ids fall back to whatever was registered before (so an earlier\n * `mock.query('core.get_jobs', …)` still works), else `[]`, as middleware\n * answers a reaped id.\n */\n const installSnapshotAnswer = (): void => {\n if (dispatcher) return;\n\n const answerSnapshot = (read: TrueNasMessage): void => {\n const id = readJobId(read);\n const sequence = id === undefined ? undefined : sequences.get(id);\n if (!sequence || id === undefined) {\n if (fallback) return fallback(read);\n // Shaped like any other read of this method: answering `[]` to a\n // `queryCount` hands back an array typed `number`, which is the same\n // silent wrong shape the hit path was fixed for.\n return answerRead(read, undefined);\n }\n\n // Where the job has got to, not where it started. A snapshot reports the\n // job's current state — `trackJob`'s own comment says the read exists\n // \"so an already-finished job is still reported\" — so a second reader\n // after the walk has run gets the finished job and completes, rather\n // than being told it is still running and waiting for events that have\n // already been sent.\n const at = position.get(id) ?? 0;\n answerRead(read, sequence[at]);\n\n // The rest of the walk, once the tracker is subscribed. The cursor\n // advances with each event, not ahead, so a concurrent reader isn't told\n // the job finished before the tracker saw it run. Only a tracker's read\n // releases the walk: a `get`/`count` has nobody on `jobEvents`, and\n // replaying into it would leave a later `trackJob` only the final state.\n if (!isTracking(read) || at >= sequence.length - 1 || walking.has(id)) return;\n walking.add(id);\n queueMicrotask(() => {\n for (let next = at + 1; next < sequence.length; next++) {\n position.set(id, next);\n connection.receive(jobEvent(sequence[next]));\n }\n walking.delete(id);\n });\n };\n\n dispatcher = answerSnapshot;\n fallback = connection.autoReply('core.get_jobs', answerSnapshot);\n };\n\n /**\n * Scripted jobs own `core.get_jobs` once there are any.\n *\n * Only `mock.query` can reach it — `core.get_jobs` carries an `entity`, so it\n * is a query method and `mock.call` excludes those.\n *\n * The dispatcher chains to whatever was registered before it, so scripting\n * that method and *then* a job composes. The other order cannot: the later\n * registration takes the method over, and a read for a scripted job is then\n * answered with someone else's row — `job()` completing with a job it never\n * started, silently. Refused loudly instead; `connection.reply` is still\n * there for a spec that wants the frames.\n */\n const refuseJobMethodTakeover = (method: string): void => {\n if (method !== 'core.get_jobs' || sequences.size === 0) return;\n\n throw new Error(\n \"mock.query('core.get_jobs', …) cannot be registered after a \" +\n 'scripted job: jobs answer that method to report their progress, and ' +\n 'taking it over makes them complete with the wrong job. Script it ' +\n 'before the job, or drive the frames with connection.reply.'\n );\n };\n\n const jobEvent = (job: Job, messageId?: string): TrueNasMessage =>\n ({\n jsonrpc: '2.0',\n method: 'collection_update',\n params: {\n collection: 'core.get_jobs',\n msg: 'changed',\n id: job.id,\n fields: messageId ? { ...job, message_ids: [messageId] } : job,\n },\n }) as unknown as TrueNasMessage;\n\n return {\n call(method, response) {\n connection.autoReply(String(method), frame => {\n const params = (frame.params ?? []) as CallParams<D, typeof method>;\n answer(\n frame,\n typeof response === 'function'\n ? (response as (p: typeof params) => unknown)(params)\n : response\n );\n });\n },\n\n query(method, rows) {\n refuseJobMethodTakeover(method);\n connection.autoReply(method, frame => {\n const [, options] = (frame.params ?? []) as [unknown, QueryOptionsFrame?];\n if (options?.count) return answer(frame, rows.length);\n if (options?.get) {\n // `queryOne` is typed non-nullable and the appliance raises rather\n // than sending nothing, so answering `undefined` here would hand a\n // spec a value its own types say cannot exist.\n if (rows.length === 0) {\n // Addressed to this frame, not to `connection.replyError`, which\n // resolves the id by looking up the most recent frame for the\n // method. Two reads issued in the same tick both answer on\n // whichever went second, and `query` and `queryOne` share a method\n // here by design — so the wrong caller gets the error and the\n // right one hangs.\n return notFound(frame);\n }\n return answer(frame, rows[0]);\n }\n answer(frame, rows);\n });\n },\n\n job(method, updates) {\n const given = Array.isArray(updates) ? updates : [updates];\n if (given.length === 0) {\n throw new Error(\n `mock.job('${String(method)}', []) has nothing to answer with. ` +\n 'A job needs at least one update; one that never reaches a ' +\n 'terminal state is how you script a job that hangs.'\n );\n }\n\n // Every refusal first, then the id, then the walk. Allocating before the\n // refusals burned an id on a registration that never happened, and\n // `nextJobId` is how a spec predicts what the next auto-allocated job\n // will be called.\n const chosen = given[0].id;\n if (chosen !== undefined && sequences.has(chosen)) {\n throw new Error(\n `mock.job('${String(method)}', …) was given id ${String(chosen)}, ` +\n 'which another scripted job already holds. Ids are how a spec ' +\n 'reaches a job through trackJob, so two jobs cannot share one.'\n );\n }\n\n const conflicting = given.find(\n update => update.id !== undefined && update.id !== chosen\n );\n if (conflicting) {\n // Named against the first update rather than the allocated id, which\n // does not exist yet: the ids in the message are the ones the fixture\n // actually wrote. An update naming an id when the first did not is a\n // conflict too — it would otherwise be accepted or refused depending\n // on what the auto-allocator happened to be about to hand out.\n const first = chosen === undefined ? 'none' : String(chosen);\n throw new Error(\n `mock.job('${String(method)}', …) was given updates for different ` +\n `ids: ${first} and ${String(conflicting.id)}. They are updates to ` +\n 'one job; script the second separately if it is a second job.'\n );\n }\n\n if (scriptedMethods.has(String(method))) {\n throw new Error(\n `mock.job('${String(method)}', …) is already scripted. Starting the ` +\n 'method again would correlate onto one job id, which the appliance ' +\n 'cannot produce; script one job per method, or reach the other ' +\n 'through trackJob with the id this returned.'\n );\n }\n scriptedMethods.add(String(method));\n\n const id = chosen ?? allocateId();\n\n // Replayed, not simulated: no folding updates together or forcing success\n // to 100. Those are middleware's rules, and modelling them makes claims\n // about the appliance rather than testing the client.\n const sequence = given.map(\n update => fakeJob({ method: String(method), ...update, id }) as Job\n );\n\n sequences.set(id, sequence);\n installSnapshotAnswer();\n\n connection.autoReply(String(method), frame => {\n // Each start is a new job with an untouched walk; reusing a consumed\n // one would silently report only the terminal state. The first start\n // keeps the id `mock.job` returned, unless its walk was already\n // started, reported past (a `trackJob` read alone does that) or is\n // mid-replay.\n const fresh = started.has(id) || position.has(id) || walking.has(id);\n const runId = fresh ? allocateId() : id;\n started.add(runId);\n if (fresh) sequences.set(runId, sequence.map(job => ({ ...job, id: runId })));\n const run = sequences.get(runId) ?? sequence;\n\n // Registered before the id goes out, because delivering the id runs the\n // whole of `job()` synchronously: `callAndGetJobId` emits, `trackJob`\n // subscribes, and its opening `core.get_jobs` read is on the wire\n // before this line would otherwise have been reached.\n //\n // The remaining updates wait for that read. Sending them alongside the\n // id loses every intermediate state, because `trackJob` has not\n // subscribed yet and an update delivered in that turn arrives with\n // nobody listening — which is what the differential test caught,\n // scripted jobs reporting only their terminal state where a\n // hand-driven one reported the whole walk.\n connection.receive(jobEvent(run[0], frame.id));\n });\n\n return id;\n },\n\n emit(event, change) {\n connection.receive({\n jsonrpc: '2.0',\n method: 'collection_update',\n params: { collection: event, ...change },\n } as unknown as TrueNasMessage);\n },\n };\n}\n","import { clientClassFor, type DerivedDirectory } from '@/factory';\nimport type { TrueNasApiClient } from '@/client/truenas-api-client';\nimport { SUPPORTED_API_VERSIONS } from '@/generated';\nimport type { SupportedApiVersion } from '@/generated';\nimport type { ApiDirectoryShape } from '@/types/api-directory.type';\nimport type { OperationMappings } from '@/types/operation-mappings.interface';\nimport { parseApiVersion } from '@/utils/api-version.utils';\nimport { FakeAuthenticator } from './fake-authenticator';\nimport { FakeConnection } from './fake-connection';\nimport { createMockAnswers, type MockAnswers } from './mock-answers';\n\n/**\n * The concrete client classes, as a constructor this module can extend.\n *\n * `clientClassFor` is declared as returning the abstract base, so extending it\n * directly asks TypeScript to implement `createOperations` here — which would\n * mean writing a second copy of the operations the version's own class already\n * has. Every value in that map is one of those concrete classes.\n */\ntype FakeableClientConstructor = new (\n // A mixin base must take a single `any[]` rest parameter; the real arguments\n // are applied at the one construction site below, where they are checked.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: any[]\n) => TrueNasApiClient & { createOperations(): OperationMappings };\n\n/** A real client whose collaborators are the fakes, so a test can drive them. */\nexport type FakeTrueNasClient<D extends ApiDirectoryShape> = TrueNasApiClient<D> & {\n readonly connection: FakeConnection;\n readonly authenticator: FakeAuthenticator;\n /** Scripted answers, typed by this client's directory. */\n readonly mock: MockAnswers<D>;\n};\n\n/** How a fake client is set up. Every field has a usable default. */\nexport interface FakeClientOptions<V extends SupportedApiVersion> {\n /** Selects both the client class and the directory the result is typed against. */\n version?: V;\n /**\n * Whether the client starts authenticated. Defaults to `true`.\n *\n * Not because delivery depends on it — `events()` and the job stream filter\n * the message, not the auth state, so a `receive` arrives either way — but\n * because the client only *sends* its `core.subscribe` frames once\n * `authenticated$` is true. A fake that starts unauthenticated has an empty\n * `sent` where a real one would have registered its subscriptions, and a\n * spec asserting on the wire sees a client that never subscribed.\n */\n authenticated?: boolean;\n /** Whether the connection starts opened. Defaults to `true`. */\n opened?: boolean;\n /**\n * Whether a call nothing is scripted to answer fails with\n * {@link UnmockedCallError} instead of hanging. Defaults to `false`.\n *\n * A spec that scripts every call with `mock` can turn this on and find out\n * which one it missed, by name, instead of watching a promise never settle.\n * A spec that answers frames by hand with `connection.reply` cannot: the\n * answer is registered after the frame goes out, and there is no way to tell\n * that apart from a frame nobody will ever answer. See\n * {@link FakeConnectionOptions.strict}.\n */\n strict?: boolean;\n}\n\n/**\n * Substitutes the fakes into a concrete client class. A mixin over a type\n * parameter so the concrete class comes through as itself (see\n * `FakeableClientConstructor`).\n *\n * The fakes are built in the hooks, not as fields: the base constructor calls\n * the hooks before subclass fields are assigned. `declare` narrows the\n * properties without emitting an assignment that would clobber them.\n */\nfunction withFakeCollaborators<T extends FakeableClientConstructor>(\n Base: T,\n options: { opened?: boolean; strict?: boolean }\n) {\n return class FakeClient extends Base {\n declare readonly connection: FakeConnection;\n\n declare readonly authenticator: FakeAuthenticator;\n\n protected override createConnection(): FakeConnection {\n return new FakeConnection({\n uuid: this.uuid,\n websocketPath: this.version.websocketPath,\n opened: options.opened ?? true,\n strict: options.strict ?? false,\n });\n }\n\n protected override createAuthenticator(): FakeAuthenticator {\n return new FakeAuthenticator(this.connection, this.version);\n }\n };\n}\n\n/**\n * A real `TrueNasApiClient` with no socket or version discovery: only the\n * connection and authenticator are fakes, so the real dispatch runs.\n *\n * ```typescript\n * const client = createFakeClient({ version: 'v27.0.0' });\n * client.mock.call('system.info', { hostname: 'truenas.local' });\n * client.api.call('system.info').subscribe(info => …);\n *\n * // Instead of `mock.call`, a spec can answer the sent frame by hand:\n * client.connection.reply('system.info', { hostname: 'truenas.local' });\n * ```\n */\nexport function createFakeClient<V extends SupportedApiVersion = SupportedApiVersion>(\n options: FakeClientOptions<V> = {}\n): FakeTrueNasClient<DerivedDirectory<V>> {\n // The oldest supported version, taken from the list rather than written out,\n // for the same reason the directory type comes from the factory's own\n // `DerivedDirectory`: one statement of the default, not two.\n const versionString = options.version ?? SUPPORTED_API_VERSIONS[0];\n const version = parseApiVersion(versionString);\n if (!version) {\n throw new Error(`'${versionString}' is not a version this package parses.`);\n }\n\n const Base = clientClassFor(version) as FakeableClientConstructor | undefined;\n if (!Base) {\n throw new Error(\n `No client implementation for '${versionString}'. The real factory ` +\n `would have refused it too.`\n );\n }\n\n const FakeClient = withFakeCollaborators(Base, options);\n\n const client = new FakeClient(\n // Deliberately not `FakeConnection`'s own placeholder: if the two defaults\n // matched, a test asserting the connection was told which appliance it\n // stands for would pass whether or not it was told.\n 'fake-client-uuid',\n ['fake.local'],\n version,\n // Never enabled: the connection this client builds is a fake and opens\n // nothing, and leaving the gate shut keeps that true if it ever stops\n // being one.\n false\n );\n\n if (options.authenticated ?? true) {\n // Raised directly rather than by logging in, so no credentials are stored\n // and the authenticator's auto-relogin does not fire for it. A spec about\n // reconnection should log in through the authenticator instead, which is\n // what writes them.\n client.authenticator.authenticated$.next(true);\n }\n\n const fake = client as unknown as FakeTrueNasClient<DerivedDirectory<V>>;\n\n // Assigned rather than constructed with the client: it needs the connection,\n // which the base constructor builds, and the client's type is only settled\n // once. `mock` is the fake's own member, so widening the readonly here is\n // the definition rather than a mutation of the class's contract.\n (fake as { mock: MockAnswers<DerivedDirectory<V>> }).mock = createMockAnswers(\n client.connection\n );\n\n return fake;\n}\n","/**\n * What this needs of a client, which is less than a client.\n *\n * Written structurally rather than as `FakeTrueNasClient<D>` so the directory\n * type does not have to be named at the call site, and so a spec holding a\n * client narrowed to its own version still passes one.\n */\nexport interface SpyableClient {\n readonly api: object;\n readonly connection: object;\n readonly authenticator: object;\n}\n\n/**\n * A test runner's spy factory: `vi.fn` or `jest.fn`.\n *\n * Taken as an argument rather than imported, because this package does not\n * depend on a runner and must not start.\n *\n * **One requirement the type cannot express: the returned function must invoke\n * the implementation with its own `this`.** `vi.fn` and `jest.fn` do; an arrow\n * does not, so `(impl) => vi.fn((...args) => impl(...args))` type-checks and\n * hands every verb `undefined`. `withSpies` detects that and says so.\n */\nexport type SpyFactory = <A extends unknown[], R>(\n implementation: (...args: A) => R\n) => (...args: A) => R;\n\n/**\n * The verbs of `TrueNasApi` a spec asserts on.\n *\n * Enumerated rather than discovered, and pinned by `with-spies.spec.ts`: a\n * verb added to `TrueNasApi` fails that test until someone classifies it.\n * `generateToken` is on the list because it is a public verb, not because it\n * is special.\n *\n * Exported for that test alone, not from the entry. The test used to restate\n * these strings, which pinned a copy: removing one from here left the suite\n * green while the verb silently stopped being spied.\n */\nexport const API_VERBS = [\n 'call',\n 'callAndGetJobId',\n 'events',\n 'generateToken',\n 'job',\n 'query',\n 'queryCount',\n 'queryOne',\n 'trackJob',\n] as const;\n\n/**\n * `send` is the whole of the connection's outbound surface — every verb above\n * reaches the wire through it — so a spec that wants to assert on frames\n * rather than on verbs has one place to look. The rest of the connection is\n * driven by the fake and already recorded: `connection.sent` holds the frames\n * whether or not a spy is installed.\n */\nexport const CONNECTION_METHODS = ['send'] as const;\n\n/** Every way in, and the way out. */\nexport const AUTHENTICATOR_METHODS = [\n 'loginWithApiKey',\n 'loginWithOtp',\n 'loginWithToken',\n 'loginWithUserPass',\n 'logout',\n 'newApiKey',\n] as const;\n\nfunction spyOnMethods<T extends object>(\n target: T,\n names: readonly string[],\n spy: SpyFactory\n): void {\n for (const name of names) {\n const original = (target as Record<string, unknown>)[name];\n if (typeof original !== 'function') {\n // Not defensive padding: the lists above are pinned against the real\n // classes by a test, so reaching this means the class changed shape\n // under a name that still exists — worth saying out loud rather than\n // silently installing nothing.\n throw new Error(\n `withSpies: '${name}' is not a method on ${target.constructor.name}.`\n );\n }\n\n // Not bound to `target`: both runners invoke the implementation with the\n // `this` of the call, and binding would make a *detached* verb work under\n // spies when it throws without them — a spied client more permissive than\n // an unspied one is the divergence this helper exists to observe.\n //\n // The wrapper turns the two ways of losing `this` into a sentence, instead\n // of `Cannot read properties of undefined (reading 'dispatch')` out of a\n // bundled chunk. It calls the method either way: refusing made the spied\n // client *stricter* for `callAndGetJobId`, whose body sits inside a\n // `defer` and tolerates a detached call.\n const method = original as (this: unknown, ...args: unknown[]) => unknown;\n const forwarded = function (this: unknown, ...args: unknown[]): unknown {\n if (this !== undefined) return method.apply(this, args);\n\n try {\n return method.apply(this, args);\n } catch (cause) {\n throw new Error(\n `withSpies: ${name} was called without its object. Either the verb ` +\n 'was detached from the client, or the spy factory does not forward ' +\n '`this` to the implementation it was given. An arrow function does ' +\n 'not; `vi.fn` and `jest.fn` do.',\n { cause }\n );\n }\n };\n\n // Both runners copy the implementation's `name` and `length` onto the\n // mock, so without these every spied method would report `forwarded` — and\n // the moment `expect(client.api.call).toHaveBeenCalledWith(…)` matters is\n // the moment it fails and prints that name.\n Object.defineProperty(forwarded, 'name', { value: name, configurable: true });\n Object.defineProperty(forwarded, 'length', {\n value: method.length,\n configurable: true,\n });\n\n (target as Record<string, unknown>)[name] = spy(forwarded);\n }\n}\n\n/**\n * Wrap a fake client's verbs in the caller's spies, in place.\n *\n * ```typescript\n * const client = withSpies(createFakeClient({ version: 'v27.0.0' }), vi.fn);\n * expect(client.api.call).toHaveBeenCalledWith('system.info');\n * ```\n *\n * Behaviour is preserved: each spy wraps the real method, which the runner\n * invokes with the `this` of the call, so a spied call still dispatches and\n * still answers from whatever `mock` scripted. Nothing is bound — see\n * `spyOnMethods`. The client is mutated and returned rather than proxied,\n * because a proxy would not be the object its own collaborators hold.\n */\nexport function withSpies<C extends SpyableClient>(client: C, spy: SpyFactory): C {\n spyOnMethods(client.api, API_VERBS, spy);\n spyOnMethods(client.connection, CONNECTION_METHODS, spy);\n spyOnMethods(client.authenticator, AUTHENTICATOR_METHODS, spy);\n\n return client;\n}\n","import { SUPPORTED_API_VERSIONS } from '@/generated';\nimport type { SupportedApiVersion } from '@/generated';\nimport type { ApiVersion } from '@/types/api-version.type';\nimport { parseApiVersion } from '@/utils/api-version.utils';\nimport { present } from './present';\n\n/**\n * Overrides for {@link fakeApiVersion}: the fields the parser would have\n * derived, for the versions it will not produce.\n */\nexport type FakeApiVersionOverrides = Partial<ApiVersion>;\n\n/**\n * A parsed `ApiVersion`, built by the parser the client itself uses.\n *\n * Going through `parseApiVersion` is the point: the year/minor/patch split and\n * the websocket path are its rules, and writing them out here would be a\n * second statement free to drift. It returns `ApiVersion | null`, so this\n * throws on a string that does not parse rather than handing back a `null` a\n * spec then carries a non-null assertion for.\n *\n * `overrides` is for versions the parser will not produce — a compatibility\n * spec may want a year this package does not support — and is visible at the\n * call site rather than smuggled in through a string.\n */\nexport function fakeApiVersion(\n version: SupportedApiVersion | (string & {}) = SUPPORTED_API_VERSIONS[0],\n overrides: FakeApiVersionOverrides = {}\n): ApiVersion {\n const parsed = parseApiVersion(version);\n if (!parsed) {\n throw new Error(\n `fakeApiVersion('${version}'): not a version this package parses. ` +\n 'Expected vYY.MINOR.PATCH — v25.10.0, v27.0.0. For a version the ' +\n 'parser rejects, pass one it accepts and override its fields.'\n );\n }\n\n return { ...parsed, ...present(overrides) };\n}\n","import { AuthResponseType, type AuthResponse } from '@/types/auth.type';\nimport { UserRole } from '@/enums/user-role.enum';\nimport { present } from './present';\n\ntype UserInfo = NonNullable<AuthResponse['user_info']>;\n\n/**\n * Overrides for {@link fakeAuthResponse}.\n *\n * `user_info` is partial one level down, like `fakeJob`'s `progress`: a spec\n * that wants a different username should not have to restate twenty-odd fields\n * around it. The structures below it — `privilege`, `group`, `attributes` —\n * are replaced whole when given, because a partial `privilege` is a shape that\n * cannot reach a consumer and merging it would invent one.\n */\nexport interface FakeAuthResponseOverrides\n extends Partial<Omit<AuthResponse, 'user_info'>> {\n user_info?: Partial<UserInfo>;\n}\n\n/**\n * What each arm of the union carries beyond `response_type`.\n *\n * A `Record` keyed by the enum, so a member added to `AuthResponseType` is a\n * compile error here rather than an empty arm. That matters now: the enum is\n * two arms short of middleware's union — `AuthLoginExResult.result` at\n * `4303dc8:…/api/v27_0_0/auth.py:335-338` has seven, and `AuthRespScram`\n * (`:245-262`) requires fields of its own.\n *\n * Each entry is a *function*: as plain objects in module scope every\n * `REDIRECT` response shared one `urls` array, so a spec mutating one changed\n * every later response in that file.\n *\n * Exported for `fake-auth-response.spec.ts` alone, not from the entry.\n */\nexport const ARMS: Record<AuthResponseType, () => Partial<AuthResponse>> = {\n [AuthResponseType.Success]: () => ({\n authenticator: 'LEVEL_1',\n reconnect_token: null,\n }),\n [AuthResponseType.OtpRequired]: () => ({ username: 'root' }),\n [AuthResponseType.Redirect]: () => ({ urls: ['https://truenas.local/sso'] }),\n [AuthResponseType.AuthErr]: () => ({}),\n [AuthResponseType.Expired]: () => ({}),\n};\n\n/**\n * A complete `AuthResponse`, so a scripted login is the shape a caller reads.\n *\n * The envelope follows `response_type`: `auth.login_ex` is a discriminated\n * union and each arm carries only its own fields, so `AUTH_ERR` comes back as\n * `{ response_type }` rather than a success wearing a failure's label.\n *\n * | `response_type` | fields |\n * |---|---|\n * | `SUCCESS` | `user_info`, `authenticator`, `reconnect_token` |\n * | `OTP_REQUIRED` | `username` |\n * | `REDIRECT` | `urls` |\n * | `AUTH_ERR`, `EXPIRED` | none |\n */\nexport function fakeAuthResponse(\n overrides: FakeAuthResponseOverrides = {}\n): AuthResponse {\n // The defaults are a successful password login by a full admin. `user_info`\n // has twenty-two required fields, which every spec here used to supply three\n // of and cast the rest away — the cast a consumer then copies.\n //\n // Any field passed explicitly is kept whatever the type: the builder\n // declines to add, it does not take away. `max_session_age` and\n // `max_inactivity` are settable and never defaulted, being on no arm of\n // middleware's union at any version. `satisfies` below checks the literal,\n // but only its *required* fields — `ARMS` is the guard that bites.\n const { user_info: userInfoOverrides, ...rest } = overrides;\n const responseType = rest.response_type ?? AuthResponseType.Success;\n const succeeded = responseType === AuthResponseType.Success;\n\n const userInfo: UserInfo | undefined =\n succeeded || userInfoOverrides\n ? ({\n username: 'root',\n fullname: 'root',\n builtin: true,\n email: null,\n groups: [0],\n privilege: { roles: { $set: [UserRole.FullAdmin] } },\n two_factor_auth_configured: false,\n immutable: true,\n sid: 'S-1-5-21-0-0-0-1000',\n id: 1,\n uid: 0,\n gid: 0,\n shell: '/usr/bin/bash',\n home: '/root',\n locked: false,\n sudo: true,\n sudo_nopasswd: true,\n sudo_commands: [],\n smb: false,\n group: {\n id: 1,\n bsdgrp_builtin: true,\n bsdgrp_gid: 0,\n bsdgrp_group: 'root',\n bsdgrp_sudo: true,\n bsdgrp_sudo_nopasswd: true,\n bsdgrp_sudo_commands: [],\n bsdgrp_smb: false,\n bsdgrp_users: [],\n },\n sshpubkey: null,\n attributes: { preferences: { language: 'en', lifetime: 300 } },\n ...present(userInfoOverrides ?? {}),\n } satisfies UserInfo)\n : undefined;\n\n return {\n response_type: responseType,\n ...ARMS[responseType](),\n ...present(rest),\n ...(userInfo ? { user_info: userInfo } : {}),\n } satisfies AuthResponse;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/unmocked-call-error.ts","../../src/testing/fake-connection.ts","../../src/testing/fake-authenticator.ts","../../src/testing/present.ts","../../src/testing/fake-api-error.ts","../../src/testing/fake-job.ts","../../src/testing/mock-answers.ts","../../src/testing/create-fake-client.ts","../../src/testing/with-spies.ts","../../src/testing/fake-api-version.ts","../../src/testing/fake-auth-response.ts"],"names":["TrueNasConnection","noopLogger","Subject","distinctUntilChanged","takeUntil","filter","map","Subscription","TrueNasAuthenticator","SUPPORTED_API_VERSIONS","parseApiVersion","clientClassFor"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Thrown when a strict fake connection is handed a frame nothing will answer.\n *\n * Named rather than a bare `Error` so a consumer's global guard can tell \"the\n * spec forgot a mock\" from a failure the code under test produced.\n *\n * It cannot travel as an error *frame* — `dispatch` reduces any frame's error\n * to `new Error(getApiErrorMessage(…))` — so it is thrown from `send`. A\n * `TrueNasApi` verb dispatches inside a `defer` and so fails its observable;\n * the authenticator sends from its own body and throws at the call site, which\n * in practice is `logout` and `newApiKey` — the two `FakeAuthenticator` does\n * not auto-reply.\n */\nexport class UnmockedCallError extends Error {\n constructor(\n readonly method: string,\n readonly params: unknown\n ) {\n super(\n `Unmocked call: nothing is scripted to answer '${method}'` +\n `${params === undefined ? '' : ` with ${JSON.stringify(params)}`}. ` +\n 'Script it with mock.call / mock.query / mock.job, answer the frame ' +\n 'with connection.reply, or build the client without { strict: true }.'\n );\n this.name = 'UnmockedCallError';\n }\n}\n","import { Subject, Subscription, distinctUntilChanged, filter, map, takeUntil } from 'rxjs';\nimport { TrueNasConnection } from '@/connection/truenas-connection';\nimport { noopLogger } from '@/logger';\nimport type { TrueNasErrorFrame } from '@/types/api-error.type';\nimport type { TrueNasMessage } from '@/types/truenas-message.type';\nimport { UnmockedCallError } from './unmocked-call-error';\n\n/** How a fake connection is set up. Every field has a usable default. */\nexport interface FakeConnectionOptions {\n /** Reported as the appliance's uuid; only ever used for log correlation. */\n uuid?: string;\n /** The path the real connection would have opened a socket on. */\n websocketPath?: string;\n /**\n * Whether the connection starts in the opened state. Defaults to `true`, as\n * it does through `createFakeClient`: a closed connection queues sends\n * instead of writing them, which is rarely what a spec building one directly\n * is after.\n */\n opened?: boolean;\n /**\n * Whether a frame nothing is scripted to answer fails instead of hanging.\n * Defaults to `false`.\n *\n * Off by default because the two ways to answer cannot be told apart at\n * `send` time. A spec that scripts everything with `mock` has registered its\n * answers before the frame goes out; a spec that drives frames by hand calls\n * `reply` *after* it — so refusing an unregistered method would break every\n * hand-driven spec, 24 of them in this repo's own suite. Strictness is the\n * spec's claim that it scripted everything, which only the spec can make.\n */\n strict?: boolean;\n}\n\n/**\n * Frames the client sends for itself, which no spec should have to script.\n *\n * `core.subscribe` is the only one reachable: for the job stream once\n * authenticated, and again per `events()` name. If another method starts\n * arriving through `send`, a strict spec fails naming it.\n *\n * The other two are absent for different reasons. `core.unsubscribe` is never\n * sent, so it would fail loudly if it were. `core.ping` would not — it goes\n * out through `ws.next(…)` rather than `send`, so it cannot reach this check,\n * and a fake connection never yields a socket for it to fire on.\n */\nconst CLIENT_HOUSEKEEPING = new Set(['core.subscribe']);\n\n/**\n * A `TrueNasConnection` that never opens a socket, and that a test drives by\n * hand.\n *\n * A real subclass, so the real `TrueNasApi` and clients run on it without an\n * `as unknown as` cast. `send` matches the real one: it queues while closed,\n * writes on open, and drops frames whose caller unsubscribed. The rest is\n * inherited and inert: the base ping timer is derived from the socket stream,\n * and a fake never yields a socket, so no timer is ever started.\n */\nexport class FakeConnection extends TrueNasConnection {\n private readonly incoming = new Subject<TrueNasMessage>();\n\n private readonly frames: TrueNasMessage[] = [];\n\n /** Frames handed to `send` while closed, waiting for the socket. */\n private readonly queued: TrueNasMessage[] = [];\n\n private readonly autoReplies = new Map<string, (frame: TrueNasMessage) => void>();\n\n /** Set by `close()`. Latched, because the real connection cannot come back. */\n private terminated = false;\n\n /** See {@link FakeConnectionOptions.strict}. Assigned in the constructor. */\n private readonly strict: boolean;\n\n /**\n * The canonical streams, re-pointed at what this class actually drives.\n *\n * The base derives all three from `connection$`, which on a disabled\n * connection reports one state and then nothing: `{ state: 'closed' }`. So\n * they describe the connection that never connects, not the one the test is\n * driving — `messages$` stayed silent while `receive` delivered, `opened$`\n * stayed `[false]` through every simulated open, and `closed$` reported a\n * connection that had never been up. `messages()` is a shim over `messages$`;\n * leaving them disagreeing puts the divergence in the members a consumer is\n * most likely to reach for.\n */\n override messages$ = this.incoming.asObservable();\n\n override opened$ = this.opened.pipe(\n distinctUntilChanged(),\n takeUntil(this.closeConnection)\n );\n\n override closed$ = this.opened.pipe(\n distinctUntilChanged(),\n filter(isOpen => !isOpen),\n map(() => undefined),\n takeUntil(this.closeConnection)\n );\n\n constructor(options: FakeConnectionOptions = {}) {\n super(\n // Disabled: `connection$` derives from this, so no socket is ever built\n // and none of the retry machinery runs.\n false,\n [],\n options.uuid ?? 'fake-uuid',\n options.websocketPath ?? '/api/current',\n undefined,\n 0,\n 0,\n noopLogger\n );\n\n this.strict = options.strict ?? false;\n\n if (options.opened ?? true) this.simulateOpen();\n }\n\n /** Every frame handed to `send`, in order. */\n get sent(): readonly TrueNasMessage[] {\n return this.frames;\n }\n\n /**\n * Records the frame, or queues it until the connection opens.\n *\n * The real `send` queues on `ws$` until a socket exists and returns the\n * subscription holding that queue open; a caller who gives up unsubscribes\n * it and the frame never goes out. Both halves are reproduced, because\n * \"records everything regardless of `opened`\" is precisely the divergence\n * the hand-rolled double had — it forwarded straight through — and moving it\n * in here would have been the same bug in a better-looking place.\n */\n override send(message: TrueNasMessage): Subscription {\n // Before the terminated and queued paths, so a strict spec is told what it\n // failed to script whatever state the connection is in. A frame that is\n // going to hang forever hangs the same way closed as it does open.\n if (\n this.strict &&\n message.method !== undefined &&\n !this.autoReplies.has(message.method) &&\n !CLIENT_HOUSEKEEPING.has(message.method)\n ) {\n throw new UnmockedCallError(message.method, message.params);\n }\n\n if (this.terminated) {\n const dropped = new Subscription();\n dropped.unsubscribe();\n return dropped;\n }\n\n if (!this.opened.getValue()) {\n this.queued.push(message);\n return new Subscription(() => {\n const at = this.queued.indexOf(message);\n if (at >= 0) this.queued.splice(at, 1);\n });\n }\n\n this.write(message);\n\n // The frame has gone out, so there is nothing left for a caller to\n // abandon; an already-closed subscription says exactly that.\n const done = new Subscription();\n done.unsubscribe();\n return done;\n }\n\n /**\n * Answer any frame for `method` as soon as it is written.\n *\n * The narrow ancestor of `mockCall`: enough to let a collaborator that talks\n * to the appliance — the authenticator, for one — run its real code against\n * a scripted answer rather than be replaced by a fake of itself.\n *\n * One answer per method, last registration wins. The one it replaced is\n * returned so a caller that needs to share the method can chain to it rather\n * than take it over silently.\n */\n autoReply(\n method: string,\n answer: (frame: TrueNasMessage) => void\n ): ((frame: TrueNasMessage) => void) | undefined {\n const replaced = this.autoReplies.get(method);\n this.autoReplies.set(method, answer);\n return replaced;\n }\n\n private write(message: TrueNasMessage): void {\n this.frames.push(message);\n\n const answer = this.autoReplies.get(String(message.method));\n if (!answer) return;\n\n // On a microtask, not inline. A real reply crosses a socket, so it cannot\n // land before the caller has subscribed — and some senders rely on that:\n // `TrueNasAuthenticator` sends its frame when the login method is *called*\n // and returns the observable for the caller to subscribe afterwards, so an\n // inline answer arrives with nobody listening and the login never settles.\n queueMicrotask(() => answer(message));\n }\n\n /** Deliver a message as if middleware had sent it. */\n receive(message: TrueNasMessage): void {\n this.incoming.next(message);\n }\n\n /**\n * Answer the most recent frame carrying `method`, by its id.\n *\n * Ids are what correlate a reply to a request, and a test that had to read\n * them itself would be asserting on the id generator. Most recent rather\n * than first: a spec that calls the same method twice is asking about the\n * second one.\n */\n reply(method: string, result: unknown): void {\n this.receive({ jsonrpc: '2.0', id: this.idOf(method), result });\n }\n\n /**\n * As {@link reply}, with a JSON-RPC error instead of a result.\n *\n * Typed as the frame `/api/<version>` sends and nothing else: `code` and\n * `message` outside, the TrueNAS payload under `data`. The legacy\n * `/websocket` shape — those payload fields at the top level — is not\n * accepted, so a spec cannot script an error this client can never receive.\n * It was accepted until now only because the parameter was the loose\n * `ApiError` union, which admits both.\n *\n * The cast is gone with it: the frame's own type says this is what an error\n * looks like.\n */\n replyError(method: string, error: TrueNasErrorFrame): void {\n this.receive({ jsonrpc: '2.0', id: this.idOf(method), error });\n }\n\n /**\n * Raise `opened` and flush whatever was queued, as the socket opening would.\n *\n * The queue drains after `opened` goes true, which is the order the real\n * connection produces: `send` is waiting on `ws$`, and `ws$` emits because a\n * socket arrived.\n */\n simulateOpen(): void {\n // The real `opened` reaches subscribers through `distinctUntilChanged`, so\n // it never emits `true` twice running. Emitting it here anyway made the\n // client re-run everything keyed on the transition — a second\n // `auth.login_ex` the real one never sends.\n if (this.terminated || this.opened.getValue()) return;\n\n this.opened.next(true);\n const pending = this.queued.splice(0, this.queued.length);\n for (const message of pending) this.write(message);\n }\n\n /**\n * Lower `opened` and fire `closed`, as a socket closing would.\n *\n * Named for what it simulates rather than overriding `close()`, which on the\n * real connection means \"stop, and do not retry\" — a different verb that\n * callers already use for teardown.\n */\n simulateClose(): void {\n if (this.terminated || !this.opened.getValue()) return;\n\n this.opened.next(false);\n this.closed.next();\n }\n\n /**\n * Terminal, as on the real connection: the streams end and nothing more is\n * written.\n *\n * `simulateClose` is the socket dropping — recoverable, and the client will\n * try again. This is the caller giving up on the connection for good, so the\n * message stream completes and `send` has nowhere to put a frame.\n */\n override close(): void {\n if (this.terminated) return;\n\n // Not `simulateClose()`. The real `close()` ends `connection$` through\n // `takeUntil`, which completes its subscribers without emitting: `opened`\n // keeps its last value and `closed` never fires. Routing through the\n // socket-drop path instead made `close()` log the client out, because\n // `TrueNasAuthenticator` lowers `authenticated$` on `closed` — a\n // difference from the real connection introduced by the fix for the\n // previous round's finding that `close()` did nothing at all.\n this.terminated = true;\n this.incoming.complete();\n super.close();\n }\n\n private idOf(method: string): string {\n const frame = [...this.frames].reverse().find(m => m.method === method);\n if (!frame) {\n throw new Error(\n `No frame for '${method}' has been sent; sent so far: ` +\n `${this.frames.map(m => String(m.method)).join(', ') || '(none)'}`\n );\n }\n return frame.id ?? '';\n }\n}\n","import type { Observable } from 'rxjs';\nimport { TrueNasAuthenticator } from '@/auth/truenas-authenticator';\nimport { TrueNasAuthMechanism } from '@/enums/truenas-auth-mechanism.enum';\nimport type { ApiVersion } from '@/types/api-version.type';\nimport { AuthResponseType, type AuthResponse } from '@/types/auth.type';\nimport type { FakeConnection } from './fake-connection';\n\n/** One recorded login attempt. */\nexport interface RecordedLogin {\n mechanism: TrueNasAuthMechanism;\n /** The username, token or key the caller passed, whichever the mechanism takes. */\n credential: string;\n}\n\n/**\n * A `TrueNasAuthenticator` that records its logins and answers them without a\n * socket.\n *\n * Every override records the attempt and then calls the real method, so the\n * real code sends the frame, stores credentials and interprets the response.\n * `succeedNextLogin` / `failNextLogin` only script the connection's reply — a\n * fake that decided success itself would disagree with the real client on\n * what an answer means.\n */\nexport class FakeAuthenticator extends TrueNasAuthenticator {\n private readonly attempts: RecordedLogin[] = [];\n\n /** Armed by the scripting methods; consumed by the next login that is made. */\n private armed: AuthResponse | undefined;\n\n /**\n * Answers waiting for their frames, oldest first.\n *\n * Bound when the login is *made*, not when the reply goes out. Reading an\n * armed answer at reply time meant a second login armed in the meantime\n * answered the first — two logins in flight is exactly what testing\n * supersession looks like, so scripting both is the ordinary case, and it\n * produced the same wrong verdict this class was rewritten to stop\n * producing.\n */\n private readonly pending: AuthResponse[] = [];\n\n constructor(\n private readonly fakeConnection: FakeConnection,\n version?: ApiVersion\n ) {\n super(fakeConnection, version);\n\n // Unscripted logins succeed. The common unit test does not care about\n // authenticating and should not have to arrange it; the ones that do care\n // script the next attempt.\n //\n // This occupies the connection's `auth.login_ex` answer. A spec that\n // registers its own for that method takes login scripting with it.\n this.fakeConnection.autoReply('auth.login_ex', frame => {\n this.fakeConnection.receive({\n jsonrpc: '2.0',\n id: frame.id,\n result: this.pending.shift() ?? this.success(),\n });\n });\n }\n\n /**\n * Every login attempt, in order.\n *\n * Including the ones the base class makes itself: the auto-relogin runs\n * through these same methods, and nothing here distinguishes it from a\n * caller's login. A spec counting logins across a reconnect should expect\n * that one.\n */\n get logins(): readonly RecordedLogin[] {\n return this.attempts;\n }\n\n /** The next login is answered with this response. Defaults to a success. */\n succeedNextLogin(response: Partial<AuthResponse> = {}): void {\n this.armed = { response_type: AuthResponseType.Success, ...response };\n }\n\n /**\n * The next login is answered with a rejection.\n *\n * A `response_type` rather than an `Error`, so what the caller sees is\n * whatever the real authenticator makes of it — the `AuthError` codes\n * included, which a hand-thrown `Error` could not produce.\n */\n failNextLogin(responseType: AuthResponseType = AuthResponseType.AuthErr): void {\n this.armed = { response_type: responseType };\n }\n\n /**\n * Records the attempt and binds its answer, in that order, before the real\n * method puts a frame on the wire.\n */\n private record(mechanism: TrueNasAuthMechanism, credential: string): void {\n this.attempts.push({ mechanism, credential });\n this.pending.push(this.armed ?? this.success());\n this.armed = undefined;\n }\n\n private success(): AuthResponse {\n return { response_type: AuthResponseType.Success };\n }\n\n override loginWithUserPass(username: string, password: string): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.Password, username);\n return super.loginWithUserPass(username, password);\n }\n\n override loginWithToken(token: string): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.Token, token);\n return super.loginWithToken(token);\n }\n\n override loginWithApiKey(credentials: { username: string; key: string }): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.ApiKey, credentials.username);\n return super.loginWithApiKey(credentials);\n }\n\n override loginWithOtp(code: string): Observable<AuthResponse> {\n this.record(TrueNasAuthMechanism.Otp, code);\n return super.loginWithOtp(code);\n }\n}\n","/**\n * Fields whose value is literally `undefined` are dropped rather than applied.\n *\n * Every builder here takes a `Partial<…>`, so `{ state: done ? Success :\n * undefined }` compiles — and spreading that over a default puts `undefined`\n * where a required field is declared. The object then fails in whatever reads\n * it, one layer away from the fixture that made it. Dropping them makes an\n * absent field mean \"unchanged\", which is what a partial means everywhere else\n * here.\n */\nexport function present<T extends object>(value: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(value).filter(([, v]) => v !== undefined)\n ) as Partial<T>;\n}\n","import type {\n TrueNasErrorData,\n TrueNasErrorFrame,\n} from '@/types/api-error.type';\nimport { present } from './present';\n\n/**\n * Flat overrides for a nested frame: say what varies, get the real shape.\n *\n * Picked field by field rather than `Partial<TrueNasErrorData>`, which would\n * inherit that type's `[key: string]: unknown` — legitimate on the payload,\n * since middleware adds `py_exception` there, and fatal on an overrides bag,\n * because it turns off excess-property checking. `fakeApiError({ resaon: … })`\n * typechecked, put `resaon` in the payload and left `reason` at its default.\n */\nexport interface FakeApiErrorOverrides\n extends Partial<\n Pick<TrueNasErrorData, 'error' | 'errname' | 'reason' | 'extra' | 'trace'>\n > {\n /** JSON-RPC code. Defaults to `-32001`, middleware's \"method call error\". */\n code?: number;\n /** JSON-RPC message. Defaults to `'Method call error'`, which is what `rpc.py` sends. */\n message?: string;\n}\n\n/** A reason that is a bare `repr()` — an exception raised with no arguments. */\nconst BARE_REPR = /^([A-Za-z_][A-Za-z0-9_]*)\\(\\)$/;\n\n/**\n * Characters CPython's `str.isprintable()` calls unprintable: categories Cc,\n * Cf, Cs, Co, Cn, Zl, Zp and Zs, with U+0020 the one exception. Printable\n * non-ASCII — `é`, `日`, `😀` — is left alone, as `repr()` leaves it.\n *\n * `Cn` is the one category this cannot get exactly right. \"Unassigned\" is a\n * property of a code point *in a Unicode version*, and this class reads the\n * engine's table while the appliance's `repr()` reads its Python's. They agree\n * on the code points unassigned in both, which is almost all of them, and\n * disagree either way on the rest; neither side is reliably newer. Everything\n * below U+0100 is exact, and so is any reason made of ordinary prose.\n */\nconst UNPRINTABLE = /[\\p{Cc}\\p{Cf}\\p{Cs}\\p{Co}\\p{Cn}\\p{Zl}\\p{Zp}\\p{Zs}]/u;\n\n/** One code point, escaped the way `repr()` escapes it. */\nfunction escapeCodePoint(character: string): string {\n if (character === '\\n') return '\\\\n';\n if (character === '\\r') return '\\\\r';\n if (character === '\\t') return '\\\\t';\n\n // `\\b`, `\\f` and `\\v` are not among them: `repr('\\x08')` is `'\\x08'`.\n //\n // Non-null rather than `?? 0`: `character` comes from `for…of` over a\n // string, which never yields an empty one, and a fallback here would\n // silently emit `\\x00` for a character it could not read.\n const code = character.codePointAt(0)!;\n if (code < 0x100) return `\\\\x${code.toString(16).padStart(2, '0')}`;\n if (code < 0x10000) return `\\\\u${code.toString(16).padStart(4, '0')}`;\n return `\\\\U${code.toString(16).padStart(8, '0')}`;\n}\n\n/**\n * A Python `repr()` of a string, quoting and escaping the way CPython does —\n * within the one limit {@link UNPRINTABLE} describes.\n *\n * `repr` picks `\"` when the string holds a `'` and no `\"`, escapes the\n * backslash and the chosen quote, and escapes every code point\n * `str.isprintable()` rejects: `\\xNN` below U+0100, `\\uNNNN` below U+10000,\n * `\\UNNNNNNNN` above, with `\\n`, `\\r` and `\\t` the short forms. Ordinary\n * middleware messages need all of it — `{x!r}` f-strings carry single quotes,\n * and `adapt_exception` interpolates raw stderr after a newline.\n *\n * Checked against `python3`, not against expectations written here; see\n * `fake-api-error.spec.ts` and `python-repr.spec.ts`. Exported for those alone.\n */\nexport function pythonRepr(value: string): string {\n const quote = value.includes(\"'\") && !value.includes('\"') ? '\"' : \"'\";\n\n let escaped = '';\n for (const character of value) {\n if (character === '\\\\') escaped += '\\\\\\\\';\n else if (character === quote) escaped += `\\\\${quote}`;\n else if (character === ' ' || !UNPRINTABLE.test(character)) escaped += character;\n else escaped += escapeCodePoint(character);\n }\n\n return `${quote}${escaped}${quote}`;\n}\n\n/**\n * A trace whose `class` and `repr` agree with the reason, so those two are a\n * pair an appliance could send. `formatted` is outside that: middleware builds\n * it from `traceback.format_exception`, whose frames a fixture cannot produce.\n *\n * It is an object rather than `null` because both arms sending `-32001` pass\n * `sys.exc_info()`, always truthy inside an `except`.\n *\n * The rule follows the generic arm's `str(error) or repr(error)`: a bare repr\n * means an argument-free exception, so the class is its name; anything else is\n * `ValueError` with the call written out. `CallError` is never argument-free\n * and so is deliberately not used.\n */\nfunction syntheticTrace(reason: string): NonNullable<TrueNasErrorData['trace']> {\n const bare = BARE_REPR.exec(reason);\n const cls = bare ? bare[1] : 'ValueError';\n const repr = bare ? reason : `ValueError(${pythonRepr(reason)})`;\n\n return {\n class: cls,\n formatted: `Traceback (most recent call last):\\n <synthetic>\\n${repr}\\n`,\n repr,\n };\n}\n\n/**\n * The error a method call fails with on `/api/<version>`.\n *\n * The versioned endpoint wraps the TrueNAS payload in a JSON-RPC error —\n * `{code: -32001, message: 'Method call error', data: {…}}` — and that nesting\n * is the part worth a builder. The flat `{error, errname, extra, reason}` is\n * `/websocket`'s, a route this client never opens; `getApiErrorMessage` reads\n * either, so only a consumer branching on `error.code` or `error.data.errname`\n * finds out. Verified at middleware `4303dc8`, `ws_handler/rpc.py:81-124`.\n *\n * `error` and `errname` are the caller's to keep consistent; middleware\n * derives one from the other. `trace` defaults to one whose `class` and `repr`\n * agree with `reason`; pass `null` only for the job-event payload.\n */\nexport function fakeApiError(overrides: FakeApiErrorOverrides = {}): TrueNasErrorFrame {\n const { code, message, ...data } = overrides;\n const reason = data.reason ?? 'Invalid argument';\n\n return {\n code: code ?? -32001,\n message: message ?? 'Method call error',\n data: {\n error: 22,\n errname: 'EINVAL',\n reason,\n extra: null,\n trace: syntheticTrace(reason),\n ...present(data),\n } satisfies TrueNasErrorData,\n };\n}\n","import { JobState, type Job, type JobProgress } from '@/types/job.type';\nimport { present } from './present';\n\n/**\n * Overrides for {@link fakeJob}. `progress` is partial one level down, so a\n * fixture naming a percent need not name the description beside it. Named so\n * a consumer can write a helper that takes one, as the other builders are.\n */\nexport interface FakeJobOverrides<R = unknown>\n extends Partial<Omit<Job<R>, 'progress'>> {\n progress?: Partial<JobProgress>;\n}\n\n/**\n * A complete `Job`, so a scripted one is the shape a caller actually reads —\n * a partial fixture cast into place leaves `progress` undefined and breaks\n * `job.progress.percent`.\n *\n * Defaults are what middleware sends for a job not yet started, including\n * `result: null`. `satisfies` (not a cast) makes a regeneration that adds a\n * required field fail here.\n */\nexport function fakeJob<R = unknown>(\n overrides: FakeJobOverrides<R> = {}\n): Job<R> {\n // `JobProgress.description` is `string`, not `string | null`: the generated\n // shape allows null and `job.type.ts` narrows it, because the server sends a\n // string for a job that has started.\n const progress: JobProgress = {\n percent: 0,\n description: '',\n extra: null,\n ...present(overrides.progress ?? {}),\n };\n\n return {\n id: 1,\n method: 'test.job',\n arguments: [],\n transient: false,\n description: null,\n abortable: false,\n logs_path: null,\n logs_excerpt: null,\n result: null,\n result_encoding_error: null,\n error: null,\n exception: null,\n exc_info: null,\n state: JobState.Running,\n time_started: null,\n time_finished: null,\n credentials: null,\n ...present(overrides),\n progress,\n } satisfies Job<R>;\n}\n","import type { FakeConnection } from './fake-connection';\nimport type {\n ApiDirectoryShape,\n CallMethod,\n CallParams,\n CallResponse,\n EventName,\n EventUnion,\n JobMethod,\n JobResult,\n} from '@/types/api-directory.type';\nimport type { Job } from '@/types/job.type';\nimport { fakeApiError } from './fake-api-error';\nimport { fakeJob, type FakeJobOverrides } from './fake-job';\nimport type { QueryEntity, QueryMethod } from '@/types/query.type';\nimport type { TrueNasMessage } from '@/types/truenas-message.type';\n\n/**\n * Scripted answers, typed by the directory the client is typed against.\n *\n * Every one of these registers an *answer to a frame*, not a replacement for a\n * verb. The client's own `dispatch`, job correlation and subscription\n * bookkeeping run exactly as they do against an appliance; what changes is only\n * what comes back. That is what makes a spec written against this a test of the\n * client rather than a test of the double.\n */\nexport interface MockAnswers<D extends ApiDirectoryShape> {\n /**\n * Answer `method` with `response`, or with whatever the function makes of\n * the params it was called with.\n *\n * Query methods are excluded, and the exclusion is the point rather than a\n * restriction: they live in the call directory, so `mock.call('user.query',\n * 3)` is well-typed — a query method's response is the five-way union the\n * server may return, and a number is one arm — and then answers all three\n * query verbs from that one value. `mock.query` is where a collection is\n * scripted, from rows that feed the three of them consistently.\n */\n call<M extends Exclude<CallMethod<D>, QueryMethod<D['call']> & string>>(\n method: M,\n response: CallResponse<D, M> | ((params: CallParams<D, M>) => CallResponse<D, M>)\n ): void;\n\n /**\n * Answer a query method from one set of rows.\n *\n * `filters`, `select`, `order_by`, `limit` and `offset` are not applied: the\n * rows given are the rows answered. The frame still carries whatever the\n * caller sent, so a spec asserting on `sent` sees the real request — what is\n * not modelled is middleware's evaluation of it.\n *\n * Feeds all three verbs, because on the wire they are one method\n * distinguished by its options: `queryOne` sends `get: true` and gets the\n * first row, `queryCount` sends `count: true` and gets the row count, and\n * `query` gets the rows. Scripting them separately would let a spec disagree\n * with itself about what the collection holds.\n */\n query<M extends QueryMethod<D['call']> & string>(\n method: M,\n rows: QueryEntity<D['call'], M>[]\n ): void;\n\n /**\n * Answer a job method with the updates it should report, in order. Nothing\n * is synthesised: a sequence with no terminal state never completes.\n *\n * Each update is completed by {@link fakeJob} from its defaults, not from the\n * previous update — progress does not carry and success is not forced to\n * 100. `arguments` is not filled in from the starting call either, since a\n * job reached through `trackJob` has no such call.\n *\n * Returns the job's id (the first update may set it), for `trackJob`.\n */\n job<M extends JobMethod<D>>(\n method: M,\n updates: JobUpdate<JobResult<D, M>> | JobUpdate<JobResult<D, M>>[]\n ): number;\n\n /** Push a change to every subscriber of `events(event)`. */\n emit<E extends EventName<D>>(event: E, change: EventUnion<D, E>): void;\n}\n\n/**\n * One scripted job update. An alias rather than a second statement of the\n * shape: `job` completes each update through {@link fakeJob}, so the two\n * cannot usefully differ.\n */\nexport type JobUpdate<R> = FakeJobOverrides<R>;\n\n/**\n * The id a `core.get_jobs` read is asking about.\n *\n * `trackJob` sends `[[['id', '=', jobId]]]`. Anything else — an unfiltered\n * read, or a filter on another field — is not a question this module can\n * answer from its registry, so it says so rather than guessing at\n * `params[0][0][2]`.\n */\nfunction readJobId(read: TrueNasMessage): number | undefined {\n const [filters] = (read.params ?? []) as [unknown];\n if (!Array.isArray(filters) || filters.length !== 1) return undefined;\n\n const [clause] = filters as [unknown];\n if (!Array.isArray(clause) || clause.length !== 3) return undefined;\n\n const [field, operator, value] = clause as [unknown, unknown, unknown];\n if (field !== 'id' || operator !== '=' || typeof value !== 'number') return undefined;\n\n return value;\n}\n\n/** The options object a query verb sends as its second positional argument. */\ninterface QueryOptionsFrame {\n get?: boolean;\n count?: boolean;\n}\n\nexport function createMockAnswers<D extends ApiDirectoryShape>(\n connection: FakeConnection\n): MockAnswers<D> {\n /**\n * Ids for jobs a spec did not number itself.\n *\n * Per client, like every other registry here. Module-global, the id\n * `mock.job` returns depended on how many jobs every *other* client in the\n * process had registered first — so it moved as files were added, and a spec\n * asserting on the distance between two allocations was reading a counter\n * anything could advance.\n */\n let nextJobId = 1;\n\n const answer = (frame: TrueNasMessage, result: unknown): void => {\n connection.receive({ jsonrpc: '2.0', id: frame.id, result } as TrueNasMessage);\n };\n\n /** Scripted jobs by id: the whole walk, oldest first. */\n const sequences = new Map<number, Job[]>();\n\n /** How far along its walk each scripted job has been reported. */\n const position = new Map<number, number>();\n\n /** Jobs whose remaining updates are already on their way out. */\n const walking = new Set<number>();\n\n /** Ids a start has already run, so a later start does not reuse one. */\n const started = new Set<number>();\n\n /** The next id nothing else holds. */\n const allocateId = (): number => {\n while (sequences.has(nextJobId)) nextJobId++;\n return nextJobId++;\n };\n\n /** Methods a job is already scripted for. */\n const scriptedMethods = new Set<string>();\n\n /** The options a `core.get_jobs` read carried, if any. */\n const readOptions = (read: TrueNasMessage): QueryOptionsFrame | undefined => {\n const [, options] = (read.params ?? []) as [unknown, QueryOptionsFrame?];\n return options;\n };\n\n /**\n * Whether this read is the one a tracker opens with.\n *\n * `trackJob` sends only the filters; every query verb also sends an options\n * object (`query` sends `{}`). Checking the options for `get`/`count` instead\n * would mistake a plain `api.query` for a tracker and release the walk to\n * nobody. Unrecognised reads leave the walk alone, the harmless direction.\n */\n const isTracking = (read: TrueNasMessage): boolean =>\n ((read.params ?? []) as unknown[]).length === 1;\n\n /** Answer a `core.get_jobs` read in the shape its options asked for. */\n const answerRead = (read: TrueNasMessage, job: Job | undefined): void => {\n const options = readOptions(read);\n if (options?.count) return answer(read, job ? 1 : 0);\n if (options?.get) {\n // `do_get` raises `MatchNotFound` on an empty result rather than\n // returning nothing, and `queryOne` is typed non-nullable — so a `get`\n // with no job is an error, not `undefined`. The plain read still answers\n // `[]`, which is what lets `trackJob` on an unknown id complete instead\n // of hanging.\n return job ? answer(read, job) : notFound(read);\n }\n answer(read, job ? [job] : []);\n };\n\n /** Installed once, on the first scripted job. */\n let dispatcher: ((frame: TrueNasMessage) => void) | undefined;\n\n /** Whatever answered `core.get_jobs` before the dispatcher took it over. */\n let fallback: ((frame: TrueNasMessage) => void) | undefined;\n\n /**\n * Middleware's exact frame for a `get` that matched nothing — don't invent\n * friendlier text, or specs end up asserting a message only the fake sends.\n *\n * `MatchNotFound` is a bare `IndexError`, so `rpc.py`'s generic arm reports\n * `EINVAL` with its repr as the reason. `/api/<version>` nests that payload\n * under a JSON-RPC error's `data`; only legacy `/websocket` puts it top-level.\n */\n const notFound = (frame: TrueNasMessage): void => {\n errorTo(frame, fakeApiError({ reason: 'MatchNotFound()' }));\n };\n\n const errorTo = (frame: TrueNasMessage, error: NonNullable<TrueNasMessage['error']>): void => {\n connection.receive({ jsonrpc: '2.0', id: frame.id, error } as TrueNasMessage);\n };\n\n /**\n * One `core.get_jobs` answer for every scripted job, resolved by the id in\n * the read's filter — `autoReply` is last-write-wins per method, so one\n * registration per job would answer every read with the last job.\n *\n * Unknown ids fall back to whatever was registered before (so an earlier\n * `mock.query('core.get_jobs', …)` still works), else `[]`, as middleware\n * answers a reaped id.\n */\n const installSnapshotAnswer = (): void => {\n if (dispatcher) return;\n\n const answerSnapshot = (read: TrueNasMessage): void => {\n const id = readJobId(read);\n const sequence = id === undefined ? undefined : sequences.get(id);\n if (!sequence || id === undefined) {\n if (fallback) return fallback(read);\n // Shaped like any other read of this method: answering `[]` to a\n // `queryCount` hands back an array typed `number`, which is the same\n // silent wrong shape the hit path was fixed for.\n return answerRead(read, undefined);\n }\n\n // Where the job has got to, not where it started. A snapshot reports the\n // job's current state — `trackJob`'s own comment says the read exists\n // \"so an already-finished job is still reported\" — so a second reader\n // after the walk has run gets the finished job and completes, rather\n // than being told it is still running and waiting for events that have\n // already been sent.\n const at = position.get(id) ?? 0;\n answerRead(read, sequence[at]);\n\n // The rest of the walk, once the tracker is subscribed. The cursor\n // advances with each event, not ahead, so a concurrent reader isn't told\n // the job finished before the tracker saw it run. Only a tracker's read\n // releases the walk: a `get`/`count` has nobody on `jobEvents`, and\n // replaying into it would leave a later `trackJob` only the final state.\n if (!isTracking(read) || at >= sequence.length - 1 || walking.has(id)) return;\n walking.add(id);\n queueMicrotask(() => {\n for (let next = at + 1; next < sequence.length; next++) {\n position.set(id, next);\n connection.receive(jobEvent(sequence[next]));\n }\n walking.delete(id);\n });\n };\n\n dispatcher = answerSnapshot;\n fallback = connection.autoReply('core.get_jobs', answerSnapshot);\n };\n\n /**\n * Scripted jobs own `core.get_jobs` once there are any.\n *\n * Only `mock.query` can reach it — `core.get_jobs` carries an `entity`, so it\n * is a query method and `mock.call` excludes those.\n *\n * The dispatcher chains to whatever was registered before it, so scripting\n * that method and *then* a job composes. The other order cannot: the later\n * registration takes the method over, and a read for a scripted job is then\n * answered with someone else's row — `job()` completing with a job it never\n * started, silently. Refused loudly instead; `connection.reply` is still\n * there for a spec that wants the frames.\n */\n const refuseJobMethodTakeover = (method: string): void => {\n if (method !== 'core.get_jobs' || sequences.size === 0) return;\n\n throw new Error(\n \"mock.query('core.get_jobs', …) cannot be registered after a \" +\n 'scripted job: jobs answer that method to report their progress, and ' +\n 'taking it over makes them complete with the wrong job. Script it ' +\n 'before the job, or drive the frames with connection.reply.'\n );\n };\n\n const jobEvent = (job: Job, messageId?: string): TrueNasMessage =>\n ({\n jsonrpc: '2.0',\n method: 'collection_update',\n params: {\n collection: 'core.get_jobs',\n msg: 'changed',\n id: job.id,\n fields: messageId ? { ...job, message_ids: [messageId] } : job,\n },\n }) as unknown as TrueNasMessage;\n\n return {\n call(method, response) {\n connection.autoReply(String(method), frame => {\n const params = (frame.params ?? []) as CallParams<D, typeof method>;\n answer(\n frame,\n typeof response === 'function'\n ? (response as (p: typeof params) => unknown)(params)\n : response\n );\n });\n },\n\n query(method, rows) {\n refuseJobMethodTakeover(method);\n connection.autoReply(method, frame => {\n const [, options] = (frame.params ?? []) as [unknown, QueryOptionsFrame?];\n if (options?.count) return answer(frame, rows.length);\n if (options?.get) {\n // `queryOne` is typed non-nullable and the appliance raises rather\n // than sending nothing, so answering `undefined` here would hand a\n // spec a value its own types say cannot exist.\n if (rows.length === 0) {\n // Addressed to this frame, not to `connection.replyError`, which\n // resolves the id by looking up the most recent frame for the\n // method. Two reads issued in the same tick both answer on\n // whichever went second, and `query` and `queryOne` share a method\n // here by design — so the wrong caller gets the error and the\n // right one hangs.\n return notFound(frame);\n }\n return answer(frame, rows[0]);\n }\n answer(frame, rows);\n });\n },\n\n job(method, updates) {\n const given = Array.isArray(updates) ? updates : [updates];\n if (given.length === 0) {\n throw new Error(\n `mock.job('${String(method)}', []) has nothing to answer with. ` +\n 'A job needs at least one update; one that never reaches a ' +\n 'terminal state is how you script a job that hangs.'\n );\n }\n\n // Every refusal first, then the id, then the walk. Allocating before the\n // refusals burned an id on a registration that never happened, and\n // `nextJobId` is how a spec predicts what the next auto-allocated job\n // will be called.\n const chosen = given[0].id;\n if (chosen !== undefined && sequences.has(chosen)) {\n throw new Error(\n `mock.job('${String(method)}', …) was given id ${String(chosen)}, ` +\n 'which another scripted job already holds. Ids are how a spec ' +\n 'reaches a job through trackJob, so two jobs cannot share one.'\n );\n }\n\n const conflicting = given.find(\n update => update.id !== undefined && update.id !== chosen\n );\n if (conflicting) {\n // Named against the first update rather than the allocated id, which\n // does not exist yet: the ids in the message are the ones the fixture\n // actually wrote. An update naming an id when the first did not is a\n // conflict too — it would otherwise be accepted or refused depending\n // on what the auto-allocator happened to be about to hand out.\n const first = chosen === undefined ? 'none' : String(chosen);\n throw new Error(\n `mock.job('${String(method)}', …) was given updates for different ` +\n `ids: ${first} and ${String(conflicting.id)}. They are updates to ` +\n 'one job; script the second separately if it is a second job.'\n );\n }\n\n if (scriptedMethods.has(String(method))) {\n throw new Error(\n `mock.job('${String(method)}', …) is already scripted. Starting the ` +\n 'method again would correlate onto one job id, which the appliance ' +\n 'cannot produce; script one job per method, or reach the other ' +\n 'through trackJob with the id this returned.'\n );\n }\n scriptedMethods.add(String(method));\n\n const id = chosen ?? allocateId();\n\n // Replayed, not simulated: no folding updates together or forcing success\n // to 100. Those are middleware's rules, and modelling them makes claims\n // about the appliance rather than testing the client.\n const sequence = given.map(\n update => fakeJob({ method: String(method), ...update, id }) as Job\n );\n\n sequences.set(id, sequence);\n installSnapshotAnswer();\n\n connection.autoReply(String(method), frame => {\n // Each start is a new job with an untouched walk; reusing a consumed\n // one would silently report only the terminal state. The first start\n // keeps the id `mock.job` returned, unless its walk was already\n // started, reported past (a `trackJob` read alone does that) or is\n // mid-replay.\n const fresh = started.has(id) || position.has(id) || walking.has(id);\n const runId = fresh ? allocateId() : id;\n started.add(runId);\n if (fresh) sequences.set(runId, sequence.map(job => ({ ...job, id: runId })));\n const run = sequences.get(runId) ?? sequence;\n\n // Registered before the id goes out, because delivering the id runs the\n // whole of `job()` synchronously: `callAndGetJobId` emits, `trackJob`\n // subscribes, and its opening `core.get_jobs` read is on the wire\n // before this line would otherwise have been reached.\n //\n // The remaining updates wait for that read. Sending them alongside the\n // id loses every intermediate state, because `trackJob` has not\n // subscribed yet and an update delivered in that turn arrives with\n // nobody listening — which is what the differential test caught,\n // scripted jobs reporting only their terminal state where a\n // hand-driven one reported the whole walk.\n connection.receive(jobEvent(run[0], frame.id));\n });\n\n return id;\n },\n\n emit(event, change) {\n connection.receive({\n jsonrpc: '2.0',\n method: 'collection_update',\n params: { collection: event, ...change },\n } as unknown as TrueNasMessage);\n },\n };\n}\n","import { clientClassFor, type DerivedDirectory } from '@/factory';\nimport type { TrueNasApiClient } from '@/client/truenas-api-client';\nimport { SUPPORTED_API_VERSIONS } from '@/generated';\nimport type { SupportedApiVersion } from '@/generated';\nimport type { ApiDirectoryShape } from '@/types/api-directory.type';\nimport type { OperationMappings } from '@/types/operation-mappings.interface';\nimport { parseApiVersion } from '@/utils/api-version.utils';\nimport { FakeAuthenticator } from './fake-authenticator';\nimport { FakeConnection } from './fake-connection';\nimport { createMockAnswers, type MockAnswers } from './mock-answers';\n\n/**\n * The concrete client classes, as a constructor this module can extend.\n *\n * `clientClassFor` is declared as returning the abstract base, so extending it\n * directly asks TypeScript to implement `createOperations` here — which would\n * mean writing a second copy of the operations the version's own class already\n * has. Every value in that map is one of those concrete classes.\n */\ntype FakeableClientConstructor = new (\n // A mixin base must take a single `any[]` rest parameter; the real arguments\n // are applied at the one construction site below, where they are checked.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: any[]\n) => TrueNasApiClient & { createOperations(): OperationMappings };\n\n/** A real client whose collaborators are the fakes, so a test can drive them. */\nexport type FakeTrueNasClient<D extends ApiDirectoryShape> = TrueNasApiClient<D> & {\n readonly connection: FakeConnection;\n readonly authenticator: FakeAuthenticator;\n /** Scripted answers, typed by this client's directory. */\n readonly mock: MockAnswers<D>;\n};\n\n/** How a fake client is set up. Every field has a usable default. */\nexport interface FakeClientOptions<V extends SupportedApiVersion> {\n /** Selects both the client class and the directory the result is typed against. */\n version?: V;\n /**\n * Whether the client starts authenticated. Defaults to `true`.\n *\n * Not because delivery depends on it — `events()` and the job stream filter\n * the message, not the auth state, so a `receive` arrives either way — but\n * because the client only *sends* its `core.subscribe` frames once\n * `authenticated$` is true. A fake that starts unauthenticated has an empty\n * `sent` where a real one would have registered its subscriptions, and a\n * spec asserting on the wire sees a client that never subscribed.\n */\n authenticated?: boolean;\n /** Whether the connection starts opened. Defaults to `true`. */\n opened?: boolean;\n /**\n * Whether a call nothing is scripted to answer fails with\n * {@link UnmockedCallError} instead of hanging. Defaults to `false`.\n *\n * A spec that scripts every call with `mock` can turn this on and find out\n * which one it missed, by name, instead of watching a promise never settle.\n * A spec that answers frames by hand with `connection.reply` cannot: the\n * answer is registered after the frame goes out, and there is no way to tell\n * that apart from a frame nobody will ever answer. See\n * {@link FakeConnectionOptions.strict}.\n */\n strict?: boolean;\n}\n\n/**\n * Substitutes the fakes into a concrete client class. A mixin over a type\n * parameter so the concrete class comes through as itself (see\n * `FakeableClientConstructor`).\n *\n * The fakes are built in the hooks, not as fields: the base constructor calls\n * the hooks before subclass fields are assigned. `declare` narrows the\n * properties without emitting an assignment that would clobber them.\n */\nfunction withFakeCollaborators<T extends FakeableClientConstructor>(\n Base: T,\n options: { opened?: boolean; strict?: boolean }\n) {\n return class FakeClient extends Base {\n declare readonly connection: FakeConnection;\n\n declare readonly authenticator: FakeAuthenticator;\n\n protected override createConnection(): FakeConnection {\n return new FakeConnection({\n uuid: this.uuid,\n websocketPath: this.version.websocketPath,\n opened: options.opened ?? true,\n strict: options.strict ?? false,\n });\n }\n\n protected override createAuthenticator(): FakeAuthenticator {\n return new FakeAuthenticator(this.connection, this.version);\n }\n };\n}\n\n/**\n * A real `TrueNasApiClient` with no socket or version discovery: only the\n * connection and authenticator are fakes, so the real dispatch runs.\n *\n * ```typescript\n * const client = createFakeClient({ version: 'v27.0.0' });\n * client.mock.call('system.info', { hostname: 'truenas.local' });\n * client.api.call('system.info').subscribe(info => …);\n *\n * // Instead of `mock.call`, a spec can answer the sent frame by hand:\n * client.connection.reply('system.info', { hostname: 'truenas.local' });\n * ```\n */\nexport function createFakeClient<V extends SupportedApiVersion = SupportedApiVersion>(\n options: FakeClientOptions<V> = {}\n): FakeTrueNasClient<DerivedDirectory<V>> {\n // The oldest supported version, taken from the list rather than written out,\n // for the same reason the directory type comes from the factory's own\n // `DerivedDirectory`: one statement of the default, not two.\n const versionString = options.version ?? SUPPORTED_API_VERSIONS[0];\n const version = parseApiVersion(versionString);\n if (!version) {\n throw new Error(`'${versionString}' is not a version this package parses.`);\n }\n\n const Base = clientClassFor(version) as FakeableClientConstructor | undefined;\n if (!Base) {\n throw new Error(\n `No client implementation for '${versionString}'. The real factory ` +\n `would have refused it too.`\n );\n }\n\n const FakeClient = withFakeCollaborators(Base, options);\n\n const client = new FakeClient(\n // Deliberately not `FakeConnection`'s own placeholder: if the two defaults\n // matched, a test asserting the connection was told which appliance it\n // stands for would pass whether or not it was told.\n 'fake-client-uuid',\n ['fake.local'],\n version,\n // Never enabled: the connection this client builds is a fake and opens\n // nothing, and leaving the gate shut keeps that true if it ever stops\n // being one.\n false\n );\n\n if (options.authenticated ?? true) {\n // Raised directly rather than by logging in, so no credentials are stored\n // and the authenticator's auto-relogin does not fire for it. A spec about\n // reconnection should log in through the authenticator instead, which is\n // what writes them.\n client.authenticator.authenticated$.next(true);\n }\n\n const fake = client as unknown as FakeTrueNasClient<DerivedDirectory<V>>;\n\n // Assigned rather than constructed with the client: it needs the connection,\n // which the base constructor builds, and the client's type is only settled\n // once. `mock` is the fake's own member, so widening the readonly here is\n // the definition rather than a mutation of the class's contract.\n (fake as { mock: MockAnswers<DerivedDirectory<V>> }).mock = createMockAnswers(\n client.connection\n );\n\n return fake;\n}\n","/**\n * What this needs of a client, which is less than a client.\n *\n * Written structurally rather than as `FakeTrueNasClient<D>` so the directory\n * type does not have to be named at the call site, and so a spec holding a\n * client narrowed to its own version still passes one.\n */\nexport interface SpyableClient {\n readonly api: object;\n readonly connection: object;\n readonly authenticator: object;\n}\n\n/**\n * A test runner's spy factory: `vi.fn` or `jest.fn`.\n *\n * Taken as an argument rather than imported, because this package does not\n * depend on a runner and must not start.\n *\n * **One requirement the type cannot express: the returned function must invoke\n * the implementation with its own `this`.** `vi.fn` and `jest.fn` do; an arrow\n * does not, so `(impl) => vi.fn((...args) => impl(...args))` type-checks and\n * hands every verb `undefined`. `withSpies` detects that and says so.\n */\nexport type SpyFactory = <A extends unknown[], R>(\n implementation: (...args: A) => R\n) => (...args: A) => R;\n\n/**\n * The verbs of `TrueNasApi` a spec asserts on.\n *\n * Enumerated rather than discovered, and pinned by `with-spies.spec.ts`: a\n * verb added to `TrueNasApi` fails that test until someone classifies it.\n * `generateToken` is on the list because it is a public verb, not because it\n * is special.\n *\n * Exported for that test alone, not from the entry. The test used to restate\n * these strings, which pinned a copy: removing one from here left the suite\n * green while the verb silently stopped being spied.\n */\nexport const API_VERBS = [\n 'call',\n 'callAndGetJobId',\n 'events',\n 'generateToken',\n 'job',\n 'query',\n 'queryCount',\n 'queryOne',\n 'trackJob',\n] as const;\n\n/**\n * `send` is the whole of the connection's outbound surface — every verb above\n * reaches the wire through it — so a spec that wants to assert on frames\n * rather than on verbs has one place to look. The rest of the connection is\n * driven by the fake and already recorded: `connection.sent` holds the frames\n * whether or not a spy is installed.\n */\nexport const CONNECTION_METHODS = ['send'] as const;\n\n/** Every way in, and the way out. */\nexport const AUTHENTICATOR_METHODS = [\n 'loginWithApiKey',\n 'loginWithOtp',\n 'loginWithToken',\n 'loginWithUserPass',\n 'logout',\n 'newApiKey',\n] as const;\n\nfunction spyOnMethods<T extends object>(\n target: T,\n names: readonly string[],\n spy: SpyFactory\n): void {\n for (const name of names) {\n const original = (target as Record<string, unknown>)[name];\n if (typeof original !== 'function') {\n // Not defensive padding: the lists above are pinned against the real\n // classes by a test, so reaching this means the class changed shape\n // under a name that still exists — worth saying out loud rather than\n // silently installing nothing.\n throw new Error(\n `withSpies: '${name}' is not a method on ${target.constructor.name}.`\n );\n }\n\n // Not bound to `target`: both runners invoke the implementation with the\n // `this` of the call, and binding would make a *detached* verb work under\n // spies when it throws without them — a spied client more permissive than\n // an unspied one is the divergence this helper exists to observe.\n //\n // The wrapper turns the two ways of losing `this` into a sentence, instead\n // of `Cannot read properties of undefined (reading 'dispatch')` out of a\n // bundled chunk. It calls the method either way: refusing made the spied\n // client *stricter* for `callAndGetJobId`, whose body sits inside a\n // `defer` and tolerates a detached call.\n const method = original as (this: unknown, ...args: unknown[]) => unknown;\n const forwarded = function (this: unknown, ...args: unknown[]): unknown {\n if (this !== undefined) return method.apply(this, args);\n\n try {\n return method.apply(this, args);\n } catch (cause) {\n throw new Error(\n `withSpies: ${name} was called without its object. Either the verb ` +\n 'was detached from the client, or the spy factory does not forward ' +\n '`this` to the implementation it was given. An arrow function does ' +\n 'not; `vi.fn` and `jest.fn` do.',\n { cause }\n );\n }\n };\n\n // Both runners copy the implementation's `name` and `length` onto the\n // mock, so without these every spied method would report `forwarded` — and\n // the moment `expect(client.api.call).toHaveBeenCalledWith(…)` matters is\n // the moment it fails and prints that name.\n Object.defineProperty(forwarded, 'name', { value: name, configurable: true });\n Object.defineProperty(forwarded, 'length', {\n value: method.length,\n configurable: true,\n });\n\n (target as Record<string, unknown>)[name] = spy(forwarded);\n }\n}\n\n/**\n * Wrap a fake client's verbs in the caller's spies, in place.\n *\n * ```typescript\n * const client = withSpies(createFakeClient({ version: 'v27.0.0' }), vi.fn);\n * expect(client.api.call).toHaveBeenCalledWith('system.info');\n * ```\n *\n * Behaviour is preserved: each spy wraps the real method, which the runner\n * invokes with the `this` of the call, so a spied call still dispatches and\n * still answers from whatever `mock` scripted. Nothing is bound — see\n * `spyOnMethods`. The client is mutated and returned rather than proxied,\n * because a proxy would not be the object its own collaborators hold.\n */\nexport function withSpies<C extends SpyableClient>(client: C, spy: SpyFactory): C {\n spyOnMethods(client.api, API_VERBS, spy);\n spyOnMethods(client.connection, CONNECTION_METHODS, spy);\n spyOnMethods(client.authenticator, AUTHENTICATOR_METHODS, spy);\n\n return client;\n}\n","import { SUPPORTED_API_VERSIONS } from '@/generated';\nimport type { SupportedApiVersion } from '@/generated';\nimport type { ApiVersion } from '@/types/api-version.type';\nimport { parseApiVersion } from '@/utils/api-version.utils';\nimport { present } from './present';\n\n/**\n * Overrides for {@link fakeApiVersion}: the fields the parser would have\n * derived, for the versions it will not produce.\n */\nexport type FakeApiVersionOverrides = Partial<ApiVersion>;\n\n/**\n * A parsed `ApiVersion`, built by the parser the client itself uses.\n *\n * Going through `parseApiVersion` is the point: the year/minor/patch split and\n * the websocket path are its rules, and writing them out here would be a\n * second statement free to drift. It returns `ApiVersion | null`, so this\n * throws on a string that does not parse rather than handing back a `null` a\n * spec then carries a non-null assertion for.\n *\n * `overrides` is for versions the parser will not produce — a compatibility\n * spec may want a year this package does not support — and is visible at the\n * call site rather than smuggled in through a string.\n */\nexport function fakeApiVersion(\n version: SupportedApiVersion | (string & {}) = SUPPORTED_API_VERSIONS[0],\n overrides: FakeApiVersionOverrides = {}\n): ApiVersion {\n const parsed = parseApiVersion(version);\n if (!parsed) {\n throw new Error(\n `fakeApiVersion('${version}'): not a version this package parses. ` +\n 'Expected vYY.MINOR.PATCH — v25.10.0, v27.0.0. For a version the ' +\n 'parser rejects, pass one it accepts and override its fields.'\n );\n }\n\n return { ...parsed, ...present(overrides) };\n}\n","import { AuthResponseType, type AuthResponse } from '@/types/auth.type';\nimport { UserRole } from '@/enums/user-role.enum';\nimport { present } from './present';\n\ntype UserInfo = NonNullable<AuthResponse['user_info']>;\n\n/**\n * Overrides for {@link fakeAuthResponse}.\n *\n * `user_info` is partial one level down, like `fakeJob`'s `progress`: a spec\n * that wants a different username should not have to restate twenty-odd fields\n * around it. The structures below it — `privilege`, `group`, `attributes` —\n * are replaced whole when given, because a partial `privilege` is a shape that\n * cannot reach a consumer and merging it would invent one.\n */\nexport interface FakeAuthResponseOverrides\n extends Partial<Omit<AuthResponse, 'user_info'>> {\n user_info?: Partial<UserInfo>;\n}\n\n/**\n * What each arm of the union carries beyond `response_type`.\n *\n * A `Record` keyed by the enum, so a member added to `AuthResponseType` is a\n * compile error here rather than an empty arm. That matters now: the enum is\n * two arms short of middleware's union — `AuthLoginExResult.result` at\n * `4303dc8:…/api/v27_0_0/auth.py:335-338` has seven, and `AuthRespScram`\n * (`:245-262`) requires fields of its own.\n *\n * Each entry is a *function*: as plain objects in module scope every\n * `REDIRECT` response shared one `urls` array, so a spec mutating one changed\n * every later response in that file.\n *\n * Exported for `fake-auth-response.spec.ts` alone, not from the entry.\n */\nexport const ARMS: Record<AuthResponseType, () => Partial<AuthResponse>> = {\n [AuthResponseType.Success]: () => ({\n authenticator: 'LEVEL_1',\n reconnect_token: null,\n }),\n [AuthResponseType.OtpRequired]: () => ({ username: 'root' }),\n [AuthResponseType.Redirect]: () => ({ urls: ['https://truenas.local/sso'] }),\n [AuthResponseType.AuthErr]: () => ({}),\n [AuthResponseType.Expired]: () => ({}),\n};\n\n/**\n * A complete `AuthResponse`, so a scripted login is the shape a caller reads.\n *\n * The envelope follows `response_type`: `auth.login_ex` is a discriminated\n * union and each arm carries only its own fields, so `AUTH_ERR` comes back as\n * `{ response_type }` rather than a success wearing a failure's label.\n *\n * | `response_type` | fields |\n * |---|---|\n * | `SUCCESS` | `user_info`, `authenticator`, `reconnect_token` |\n * | `OTP_REQUIRED` | `username` |\n * | `REDIRECT` | `urls` |\n * | `AUTH_ERR`, `EXPIRED` | none |\n */\nexport function fakeAuthResponse(\n overrides: FakeAuthResponseOverrides = {}\n): AuthResponse {\n // The defaults are a successful password login by a full admin. `user_info`\n // has twenty-two required fields, which every spec here used to supply three\n // of and cast the rest away — the cast a consumer then copies.\n //\n // Any field passed explicitly is kept whatever the type: the builder\n // declines to add, it does not take away. `max_session_age` and\n // `max_inactivity` are settable and never defaulted, being on no arm of\n // middleware's union at any version. `satisfies` below checks the literal,\n // but only its *required* fields — `ARMS` is the guard that bites.\n const { user_info: userInfoOverrides, ...rest } = overrides;\n const responseType = rest.response_type ?? AuthResponseType.Success;\n const succeeded = responseType === AuthResponseType.Success;\n\n const userInfo: UserInfo | undefined =\n succeeded || userInfoOverrides\n ? ({\n username: 'root',\n fullname: 'root',\n builtin: true,\n email: null,\n groups: [0],\n privilege: { roles: { $set: [UserRole.FullAdmin] } },\n two_factor_auth_configured: false,\n immutable: true,\n sid: 'S-1-5-21-0-0-0-1000',\n id: 1,\n uid: 0,\n gid: 0,\n shell: '/usr/bin/bash',\n home: '/root',\n locked: false,\n sudo: true,\n sudo_nopasswd: true,\n sudo_commands: [],\n smb: false,\n group: {\n id: 1,\n bsdgrp_builtin: true,\n bsdgrp_gid: 0,\n bsdgrp_group: 'root',\n bsdgrp_sudo: true,\n bsdgrp_sudo_nopasswd: true,\n bsdgrp_sudo_commands: [],\n bsdgrp_smb: false,\n bsdgrp_users: [],\n },\n sshpubkey: null,\n attributes: { preferences: { language: 'en', lifetime: 300 } },\n ...present(userInfoOverrides ?? {}),\n } satisfies UserInfo)\n : undefined;\n\n return {\n response_type: responseType,\n ...ARMS[responseType](),\n ...present(rest),\n ...(userInfo ? { user_info: userInfo } : {}),\n } satisfies AuthResponse;\n}\n"]}
|
package/dist/testing/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as rxjs from 'rxjs';
|
|
2
2
|
import { Subscription, Observable } from 'rxjs';
|
|
3
|
-
import { aE as TrueNasConnection, aF as TrueNasMessage, an as TrueNasErrorFrame, aG as TrueNasAuthenticator, d as ApiVersion, ak as TrueNasAuthMechanism, R as AuthResponse, S as AuthResponseType, a2 as Job, a5 as JobProgress, n as ApiDirectoryShape, V as CallMethod, ab as QueryMethod, X as CallResponse, W as CallParams, a9 as QueryEntity, a3 as JobMethod, a6 as JobResult, a0 as EventName, a1 as EventUnion, aj as SupportedApiVersion, T as TrueNasApiClient, aH as DerivedDirectory, am as TrueNasErrorData } from '../truenas-auth-mechanism.enum-
|
|
3
|
+
import { aE as TrueNasConnection, aF as TrueNasMessage, an as TrueNasErrorFrame, aG as TrueNasAuthenticator, d as ApiVersion, ak as TrueNasAuthMechanism, R as AuthResponse, S as AuthResponseType, a2 as Job, a5 as JobProgress, n as ApiDirectoryShape, V as CallMethod, ab as QueryMethod, X as CallResponse, W as CallParams, a9 as QueryEntity, a3 as JobMethod, a6 as JobResult, a0 as EventName, a1 as EventUnion, aj as SupportedApiVersion, T as TrueNasApiClient, aH as DerivedDirectory, am as TrueNasErrorData } from '../truenas-auth-mechanism.enum-CHo_zr2p.cjs';
|
|
4
4
|
import 'rxjs/webSocket';
|
|
5
5
|
|
|
6
6
|
/** How a fake connection is set up. Every field has a usable default. */
|
|
@@ -36,8 +36,8 @@ interface FakeConnectionOptions {
|
|
|
36
36
|
* A real subclass, so the real `TrueNasApi` and clients run on it without an
|
|
37
37
|
* `as unknown as` cast. `send` matches the real one: it queues while closed,
|
|
38
38
|
* writes on open, and drops frames whose caller unsubscribed. The rest is
|
|
39
|
-
* inherited and inert
|
|
40
|
-
*
|
|
39
|
+
* inherited and inert: the base ping timer is derived from the socket stream,
|
|
40
|
+
* and a fake never yields a socket, so no timer is ever started.
|
|
41
41
|
*/
|
|
42
42
|
declare class FakeConnection extends TrueNasConnection {
|
|
43
43
|
private readonly incoming;
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as rxjs from 'rxjs';
|
|
2
2
|
import { Subscription, Observable } from 'rxjs';
|
|
3
|
-
import { aE as TrueNasConnection, aF as TrueNasMessage, an as TrueNasErrorFrame, aG as TrueNasAuthenticator, d as ApiVersion, ak as TrueNasAuthMechanism, R as AuthResponse, S as AuthResponseType, a2 as Job, a5 as JobProgress, n as ApiDirectoryShape, V as CallMethod, ab as QueryMethod, X as CallResponse, W as CallParams, a9 as QueryEntity, a3 as JobMethod, a6 as JobResult, a0 as EventName, a1 as EventUnion, aj as SupportedApiVersion, T as TrueNasApiClient, aH as DerivedDirectory, am as TrueNasErrorData } from '../truenas-auth-mechanism.enum-
|
|
3
|
+
import { aE as TrueNasConnection, aF as TrueNasMessage, an as TrueNasErrorFrame, aG as TrueNasAuthenticator, d as ApiVersion, ak as TrueNasAuthMechanism, R as AuthResponse, S as AuthResponseType, a2 as Job, a5 as JobProgress, n as ApiDirectoryShape, V as CallMethod, ab as QueryMethod, X as CallResponse, W as CallParams, a9 as QueryEntity, a3 as JobMethod, a6 as JobResult, a0 as EventName, a1 as EventUnion, aj as SupportedApiVersion, T as TrueNasApiClient, aH as DerivedDirectory, am as TrueNasErrorData } from '../truenas-auth-mechanism.enum-CHo_zr2p.js';
|
|
4
4
|
import 'rxjs/webSocket';
|
|
5
5
|
|
|
6
6
|
/** How a fake connection is set up. Every field has a usable default. */
|
|
@@ -36,8 +36,8 @@ interface FakeConnectionOptions {
|
|
|
36
36
|
* A real subclass, so the real `TrueNasApi` and clients run on it without an
|
|
37
37
|
* `as unknown as` cast. `send` matches the real one: it queues while closed,
|
|
38
38
|
* writes on open, and drops frames whose caller unsubscribed. The rest is
|
|
39
|
-
* inherited and inert
|
|
40
|
-
*
|
|
39
|
+
* inherited and inert: the base ping timer is derived from the socket stream,
|
|
40
|
+
* and a fake never yields a socket, so no timer is ever started.
|
|
41
41
|
*/
|
|
42
42
|
declare class FakeConnection extends TrueNasConnection {
|
|
43
43
|
private readonly incoming;
|
package/dist/testing/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SUPPORTED_API_VERSIONS, TrueNasConnection, noopLogger, TrueNasAuthenticator, parseApiVersion, clientClassFor } from '../chunk-
|
|
2
|
-
export { AuthResponseType } from '../chunk-
|
|
1
|
+
import { SUPPORTED_API_VERSIONS, TrueNasConnection, noopLogger, TrueNasAuthenticator, parseApiVersion, clientClassFor } from '../chunk-SAJ5HZHK.js';
|
|
2
|
+
export { AuthResponseType } from '../chunk-SAJ5HZHK.js';
|
|
3
3
|
import { Subject, distinctUntilChanged, takeUntil, filter, map, Subscription } from 'rxjs';
|
|
4
4
|
|
|
5
5
|
// src/testing/unmocked-call-error.ts
|