@open-nav/mock-server 0.1.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/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 open-nav contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This repository vendors XML schemas, sample documents and validation message
26
+ catalogues published by the Hungarian Tax and Customs Administration
27
+ (Nemzeti Adó- és Vámhivatal) under the MIT license. Those files live under
28
+ `schemas/` and `conformance/`, retain NAV's copyright, and are covered by
29
+ `schemas/NAV-LICENCE.md`. See `schemas/README.md` for provenance.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # @open-nav/mock-server
2
+
3
+ A local stand-in for the NAV **Online Számla** invoice service, so an
4
+ integration can be tested without a technical user.
5
+
6
+ ```sh
7
+ npx @open-nav/mock-server
8
+ ```
9
+
10
+ It prints a set of fake credentials and a base URL. Export them and the
11
+ `open-nav` CLI — or your own code — talks to the mock exactly as it would to
12
+ NAV.
13
+
14
+ ## Why not just stub `fetch`
15
+
16
+ Because the parts that break are the parts a stub skips. This mock:
17
+
18
+ - **Verifies the request signature the way NAV does**, recomputing the
19
+ SHA3-512 of `requestId + timestamp + signKey`, plus the concatenated
20
+ per-operation hashes in index order for a batch. A caller that builds the
21
+ signature wrongly finds out here, against a readable error, instead of
22
+ against the live service where the only clue is `INVALID_SIGNATURE`.
23
+ - **Rejects a replayed `requestId`**, which is how a home-made identifier
24
+ scheme fails in production.
25
+ - **Spends an exchange token exactly once.**
26
+ - **Decides an invoice's fate with this project's validator**, so a broken
27
+ invoice comes back `ABORTED` carrying the same NAV fault code the service
28
+ would have reported.
29
+ - Keeps the invoices, so `queryInvoiceDigest`, `queryInvoiceData` and
30
+ `queryInvoiceCheck` answer from what was actually submitted.
31
+
32
+ ## In tests
33
+
34
+ ```ts
35
+ import { startMockServer } from '@open-nav/mock-server';
36
+ import { NavClient, waitForTransaction } from '@open-nav/client';
37
+
38
+ const mock = await startMockServer({ credentials, pollsBeforeDone: 2 });
39
+ const client = new NavClient({ credentials, software, baseUrl: mock.url });
40
+
41
+ const { transactionId } = await client.submitInvoices([{ operation: 'CREATE', invoice }]);
42
+ const outcome = await waitForTransaction(client, transactionId);
43
+
44
+ expect(outcome.accepted).toHaveLength(1);
45
+ expect(mock.state.invoices.size).toBe(1); // assert on what the service received
46
+ await mock.close();
47
+ ```
48
+
49
+ `pollsBeforeDone` controls how many polls a transaction takes to settle, so a
50
+ caller's polling loop can be exercised deliberately: it walks `RECEIVED` →
51
+ `PROCESSING` → `DONE` rather than settling immediately.
52
+
53
+ `mock.state` exposes everything received — invoices, transactions, issued
54
+ tokens, and the raw body of every request — so a test can assert on what was
55
+ sent, not only on what came back.
56
+
57
+ ## Options
58
+
59
+ | Option | Default | Meaning |
60
+ | ----------------- | ------------------- | ----------------------------------------------- |
61
+ | `credentials` | — | The technical user the mock accepts |
62
+ | `taxpayers` | `[]` | Registry `queryTaxpayer` answers from |
63
+ | `port` | `0` (any free port) | `8080` when run from the CLI |
64
+ | `pollsBeforeDone` | `0` | Polls before a transaction settles |
65
+ | `validate` | `true` | Abort invoices this project's validator rejects |
66
+ | `now` | `() => new Date()` | Injectable clock |
67
+
68
+ ## What it is not
69
+
70
+ Not a reimplementation of NAV. It does not know your invoice history, will not
71
+ tell you whether an invoice number was used two years ago, and its acceptance
72
+ is not evidence that NAV will accept the same document. It exists to catch the
73
+ mistakes that are decidable locally, early and cheaply.
74
+
75
+ ## Licence
76
+
77
+ MIT. Not affiliated with NAV.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { startMockServer } from './server.js';
4
+ /**
5
+ * Run the mock service standalone, so the CLI and any other integration can
6
+ * be pointed at it without credentials.
7
+ */
8
+ const { values } = parseArgs({
9
+ args: process.argv.slice(2),
10
+ options: {
11
+ port: { type: 'string' },
12
+ host: { type: 'string' },
13
+ polls: { type: 'string' },
14
+ 'no-validate': { type: 'boolean' },
15
+ help: { type: 'boolean', short: 'h' },
16
+ },
17
+ strict: true,
18
+ });
19
+ if (values.help === true) {
20
+ console.log(`open-nav-mock — a local stand-in for the NAV Online Számla invoice service
21
+
22
+ --port <n> Port to listen on (default 8080; 0 picks a free one)
23
+ --host <host> Interface to bind (default 127.0.0.1)
24
+ --polls <n> Polls before a transaction settles (default 0, immediate)
25
+ --no-validate Accept every invoice instead of validating it
26
+ `);
27
+ process.exit(0);
28
+ }
29
+ // Obviously fake credentials, printed on startup so they can be copied.
30
+ const credentials = {
31
+ login: 'mocklogin123',
32
+ password: 'mock-password',
33
+ signKey: 'mock-sign-key-0123456789',
34
+ exchangeKey: '0123456789abcdef',
35
+ taxNumber: '99999999',
36
+ };
37
+ const mock = await startMockServer({
38
+ credentials,
39
+ port: values.port === undefined ? 8080 : Number(values.port),
40
+ ...(values.host ? { host: values.host } : {}),
41
+ pollsBeforeDone: values.polls === undefined ? 0 : Number(values.polls),
42
+ validate: values['no-validate'] !== true,
43
+ taxpayers: [
44
+ {
45
+ taxNumber: '99999999',
46
+ name: 'Értékesítő Kft',
47
+ shortName: 'Értékesítő',
48
+ valid: true,
49
+ vatCode: '2',
50
+ countyCode: '41',
51
+ },
52
+ {
53
+ taxNumber: '99887764',
54
+ name: 'Beszerző Kft',
55
+ shortName: 'Beszerző',
56
+ valid: true,
57
+ vatCode: '2',
58
+ countyCode: '02',
59
+ },
60
+ ],
61
+ });
62
+ console.log(`Mock NAV invoice service listening on ${mock.url}`);
63
+ console.log('');
64
+ console.log('Point the CLI at it with these (fake) credentials:');
65
+ console.log('');
66
+ console.log(` export NAV_BASE_URL=${mock.url}`);
67
+ console.log(` export NAV_LOGIN=${credentials.login}`);
68
+ console.log(` export NAV_PASSWORD=${credentials.password}`);
69
+ console.log(` export NAV_SIGN_KEY=${credentials.signKey}`);
70
+ console.log(` export NAV_EXCHANGE_KEY=${credentials.exchangeKey}`);
71
+ console.log(` export NAV_TAX_NUMBER=${credentials.taxNumber}`);
72
+ console.log(' export NAV_SOFTWARE_ID=OPENNAVMOCK000001');
73
+ console.log('');
74
+ console.log('Then, for example: open-nav token');
75
+ console.log('Press Ctrl+C to stop.');
76
+ const shutdown = () => {
77
+ void mock.close().then(() => process.exit(0));
78
+ };
79
+ process.on('SIGINT', shutdown);
80
+ process.on('SIGTERM', shutdown);
81
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import type { MockState } from './state.js';
2
+ /** Credentials the mock expects, mirroring a technical user. */
3
+ export interface MockCredentials {
4
+ login: string;
5
+ password: string;
6
+ signKey: string;
7
+ exchangeKey: string;
8
+ taxNumber: string;
9
+ }
10
+ export interface HandlerConfig {
11
+ credentials: MockCredentials;
12
+ /**
13
+ * Polls before a transaction reaches a terminal state.
14
+ *
15
+ * Zero means the verdict is available immediately, which is what most tests
16
+ * want. A higher number exercises the caller's polling loop.
17
+ */
18
+ pollsBeforeDone: number;
19
+ /**
20
+ * Run the invoices through the validator and abort the invalid ones.
21
+ *
22
+ * On by default, so the mock rejects exactly what the library predicts NAV
23
+ * would reject. A test that submits a bad invoice sees it aborted.
24
+ */
25
+ validate: boolean;
26
+ /** Current time, injectable for deterministic tests. */
27
+ now: () => Date;
28
+ }
29
+ export interface RequestContext {
30
+ operation: string;
31
+ body: string;
32
+ document: {
33
+ root: string;
34
+ value: Record<string, unknown>;
35
+ };
36
+ }
37
+ export interface HandlerResult {
38
+ status: number;
39
+ body: string;
40
+ }
41
+ /** A NAV interface error code the mock can return. */
42
+ type InterfaceError = 'INVALID_REQUEST' | 'INVALID_SECURITY_USER' | 'INVALID_SIGNATURE' | 'INVALID_REQUEST_ID' | 'INVALID_EXCHANGE_TOKEN' | 'OPERATION_FAILED';
43
+ export declare class MockError extends Error {
44
+ readonly errorCode: InterfaceError;
45
+ readonly status: number;
46
+ constructor(errorCode: InterfaceError, message: string, status?: number);
47
+ }
48
+ export declare function errorResponse(config: HandlerConfig, errorCode: string, message: string): string;
49
+ /**
50
+ * Verify the request the way NAV does.
51
+ *
52
+ * This is the reason a mock is worth having: it recomputes the signature from
53
+ * the request's own fields and the expected sign key, so a caller that builds
54
+ * the signature wrongly finds out here rather than against the live service,
55
+ * where the only clue is an opaque INVALID_SIGNATURE.
56
+ */
57
+ export declare function authenticate(context: RequestContext, config: HandlerConfig, state: MockState, signedOperations?: Array<{
58
+ index: number;
59
+ operation: string;
60
+ base64Payload: string;
61
+ }>): void;
62
+ export declare function handleTokenExchange(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
63
+ export declare function handleManageInvoice(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
64
+ export declare function handleQueryTransactionStatus(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
65
+ export declare function handleQueryTaxpayer(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
66
+ export declare function handleQueryInvoiceCheck(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
67
+ export declare function handleQueryInvoiceData(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
68
+ export declare function handleQueryInvoiceDigest(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
69
+ export declare function handleQueryTransactionList(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
70
+ export declare function handleManageAnnulment(context: RequestContext, config: HandlerConfig, state: MockState): HandlerResult;
71
+ export declare const HANDLERS: Record<string, (context: RequestContext, config: HandlerConfig, state: MockState) => HandlerResult>;
72
+ export {};
73
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"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"}