@frihet/mcp-server 1.16.3 → 1.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/client-interface.d.ts +1 -1
- package/dist/client.d.ts +13 -1
- package/dist/client.js +70 -6
- package/dist/demo-client.d.ts +1 -1
- package/dist/demo-client.js +23 -3
- package/dist/openai-profile.js +1 -1
- package/dist/tools/invoices.js +24 -11
- package/package.json +11 -5
- package/scripts/postinstall.js +30 -8
- package/dist/__tests__/audit-server-version.test.d.ts +0 -13
- package/dist/__tests__/audit-server-version.test.d.ts.map +0 -1
- package/dist/__tests__/audit-server-version.test.js +0 -96
- package/dist/__tests__/audit-server-version.test.js.map +0 -1
- package/dist/__tests__/banking-client-contract.test.d.ts +0 -20
- package/dist/__tests__/banking-client-contract.test.d.ts.map +0 -1
- package/dist/__tests__/banking-client-contract.test.js +0 -133
- package/dist/__tests__/banking-client-contract.test.js.map +0 -1
- package/dist/__tests__/banking-tools.test.d.ts +0 -18
- package/dist/__tests__/banking-tools.test.d.ts.map +0 -1
- package/dist/__tests__/banking-tools.test.js +0 -252
- package/dist/__tests__/banking-tools.test.js.map +0 -1
- package/dist/__tests__/contract.test.d.ts +0 -33
- package/dist/__tests__/contract.test.d.ts.map +0 -1
- package/dist/__tests__/contract.test.js +0 -208
- package/dist/__tests__/contract.test.js.map +0 -1
- package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.d.ts +0 -13
- package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.d.ts.map +0 -1
- package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js +0 -420
- package/dist/__tests__/d4b-hr-payroll-onboarding-tools.test.js.map +0 -1
- package/dist/__tests__/demo-mode.test.d.ts +0 -11
- package/dist/__tests__/demo-mode.test.d.ts.map +0 -1
- package/dist/__tests__/demo-mode.test.js +0 -119
- package/dist/__tests__/demo-mode.test.js.map +0 -1
- package/dist/__tests__/einvoice-day4-tools.test.d.ts +0 -16
- package/dist/__tests__/einvoice-day4-tools.test.d.ts.map +0 -1
- package/dist/__tests__/einvoice-day4-tools.test.js +0 -421
- package/dist/__tests__/einvoice-day4-tools.test.js.map +0 -1
- package/dist/__tests__/einvoice-integration.test.d.ts +0 -24
- package/dist/__tests__/einvoice-integration.test.d.ts.map +0 -1
- package/dist/__tests__/einvoice-integration.test.js +0 -142
- package/dist/__tests__/einvoice-integration.test.js.map +0 -1
- package/dist/__tests__/einvoice-tools.test.d.ts +0 -16
- package/dist/__tests__/einvoice-tools.test.d.ts.map +0 -1
- package/dist/__tests__/einvoice-tools.test.js +0 -411
- package/dist/__tests__/einvoice-tools.test.js.map +0 -1
- package/dist/__tests__/fiscal-tools.test.d.ts +0 -21
- package/dist/__tests__/fiscal-tools.test.d.ts.map +0 -1
- package/dist/__tests__/fiscal-tools.test.js +0 -271
- package/dist/__tests__/fiscal-tools.test.js.map +0 -1
- package/dist/__tests__/get-envelope-unwrap-regression.test.d.ts +0 -27
- package/dist/__tests__/get-envelope-unwrap-regression.test.d.ts.map +0 -1
- package/dist/__tests__/get-envelope-unwrap-regression.test.js +0 -223
- package/dist/__tests__/get-envelope-unwrap-regression.test.js.map +0 -1
- package/dist/__tests__/intelligence-duplicate-invoice.test.d.ts +0 -14
- package/dist/__tests__/intelligence-duplicate-invoice.test.d.ts.map +0 -1
- package/dist/__tests__/intelligence-duplicate-invoice.test.js +0 -108
- package/dist/__tests__/intelligence-duplicate-invoice.test.js.map +0 -1
- package/dist/__tests__/kitchen-tools.test.d.ts +0 -18
- package/dist/__tests__/kitchen-tools.test.d.ts.map +0 -1
- package/dist/__tests__/kitchen-tools.test.js +0 -399
- package/dist/__tests__/kitchen-tools.test.js.map +0 -1
- package/dist/__tests__/mutation-unwrap-and-schema-regression.test.d.ts +0 -30
- package/dist/__tests__/mutation-unwrap-and-schema-regression.test.d.ts.map +0 -1
- package/dist/__tests__/mutation-unwrap-and-schema-regression.test.js +0 -164
- package/dist/__tests__/mutation-unwrap-and-schema-regression.test.js.map +0 -1
- package/dist/__tests__/observability-redaction.test.d.ts +0 -13
- package/dist/__tests__/observability-redaction.test.d.ts.map +0 -1
- package/dist/__tests__/observability-redaction.test.js +0 -94
- package/dist/__tests__/observability-redaction.test.js.map +0 -1
- package/dist/__tests__/openai-grouped-compose.test.d.ts +0 -26
- package/dist/__tests__/openai-grouped-compose.test.d.ts.map +0 -1
- package/dist/__tests__/openai-grouped-compose.test.js +0 -245
- package/dist/__tests__/openai-grouped-compose.test.js.map +0 -1
- package/dist/__tests__/openai-profile.test.d.ts +0 -10
- package/dist/__tests__/openai-profile.test.d.ts.map +0 -1
- package/dist/__tests__/openai-profile.test.js +0 -168
- package/dist/__tests__/openai-profile.test.js.map +0 -1
- package/dist/__tests__/pagination-cursor-param.test.d.ts +0 -27
- package/dist/__tests__/pagination-cursor-param.test.d.ts.map +0 -1
- package/dist/__tests__/pagination-cursor-param.test.js +0 -96
- package/dist/__tests__/pagination-cursor-param.test.js.map +0 -1
- package/dist/__tests__/pos-tools.test.d.ts +0 -16
- package/dist/__tests__/pos-tools.test.d.ts.map +0 -1
- package/dist/__tests__/pos-tools.test.js +0 -244
- package/dist/__tests__/pos-tools.test.js.map +0 -1
- package/dist/__tests__/recurring-tools.test.d.ts +0 -23
- package/dist/__tests__/recurring-tools.test.d.ts.map +0 -1
- package/dist/__tests__/recurring-tools.test.js +0 -352
- package/dist/__tests__/recurring-tools.test.js.map +0 -1
- package/dist/__tests__/schema-envelope-guard.test.d.ts +0 -18
- package/dist/__tests__/schema-envelope-guard.test.d.ts.map +0 -1
- package/dist/__tests__/schema-envelope-guard.test.js +0 -56
- package/dist/__tests__/schema-envelope-guard.test.js.map +0 -1
- package/dist/__tests__/stay-tools.test.d.ts +0 -17
- package/dist/__tests__/stay-tools.test.d.ts.map +0 -1
- package/dist/__tests__/stay-tools.test.js +0 -259
- package/dist/__tests__/stay-tools.test.js.map +0 -1
- package/dist/__tests__/team-tools.test.d.ts +0 -18
- package/dist/__tests__/team-tools.test.d.ts.map +0 -1
- package/dist/__tests__/team-tools.test.js +0 -228
- package/dist/__tests__/team-tools.test.js.map +0 -1
- package/dist/__tests__/time-tools.test.d.ts +0 -19
- package/dist/__tests__/time-tools.test.d.ts.map +0 -1
- package/dist/__tests__/time-tools.test.js +0 -286
- package/dist/__tests__/time-tools.test.js.map +0 -1
- package/dist/__tests__/tool-exposure.test.d.ts +0 -11
- package/dist/__tests__/tool-exposure.test.d.ts.map +0 -1
- package/dist/__tests__/tool-exposure.test.js +0 -264
- package/dist/__tests__/tool-exposure.test.js.map +0 -1
- package/dist/client-interface.d.ts.map +0 -1
- package/dist/client-interface.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/demo-client.d.ts.map +0 -1
- package/dist/demo-client.js.map +0 -1
- package/dist/demo-fixtures.d.ts.map +0 -1
- package/dist/demo-fixtures.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/metrics.d.ts.map +0 -1
- package/dist/metrics.js.map +0 -1
- package/dist/observability.d.ts.map +0 -1
- package/dist/observability.js.map +0 -1
- package/dist/openai-profile.d.ts.map +0 -1
- package/dist/openai-profile.js.map +0 -1
- package/dist/prompts/register-all.d.ts.map +0 -1
- package/dist/prompts/register-all.js.map +0 -1
- package/dist/redaction.d.ts.map +0 -1
- package/dist/redaction.js.map +0 -1
- package/dist/resources/register-all.d.ts.map +0 -1
- package/dist/resources/register-all.js.map +0 -1
- package/dist/tool-exposure.d.ts.map +0 -1
- package/dist/tool-exposure.js.map +0 -1
- package/dist/tools/accountingClose.d.ts.map +0 -1
- package/dist/tools/accountingClose.js.map +0 -1
- package/dist/tools/audit_gl.d.ts.map +0 -1
- package/dist/tools/audit_gl.js.map +0 -1
- package/dist/tools/backend-availability.d.ts.map +0 -1
- package/dist/tools/backend-availability.js.map +0 -1
- package/dist/tools/bank_rules.d.ts.map +0 -1
- package/dist/tools/bank_rules.js.map +0 -1
- package/dist/tools/banking.d.ts.map +0 -1
- package/dist/tools/banking.js.map +0 -1
- package/dist/tools/clients.d.ts.map +0 -1
- package/dist/tools/clients.js.map +0 -1
- package/dist/tools/crm.d.ts.map +0 -1
- package/dist/tools/crm.js.map +0 -1
- package/dist/tools/deposits.d.ts.map +0 -1
- package/dist/tools/deposits.js.map +0 -1
- package/dist/tools/einvoice.d.ts.map +0 -1
- package/dist/tools/einvoice.js.map +0 -1
- package/dist/tools/expenses.d.ts.map +0 -1
- package/dist/tools/expenses.js.map +0 -1
- package/dist/tools/fiscal.d.ts.map +0 -1
- package/dist/tools/fiscal.js.map +0 -1
- package/dist/tools/gestoria.d.ts.map +0 -1
- package/dist/tools/gestoria.js.map +0 -1
- package/dist/tools/hr.d.ts.map +0 -1
- package/dist/tools/hr.js.map +0 -1
- package/dist/tools/igic.d.ts.map +0 -1
- package/dist/tools/igic.js.map +0 -1
- package/dist/tools/impuesto_sociedades.d.ts.map +0 -1
- package/dist/tools/impuesto_sociedades.js.map +0 -1
- package/dist/tools/intelligence.d.ts.map +0 -1
- package/dist/tools/intelligence.js.map +0 -1
- package/dist/tools/invoices.d.ts.map +0 -1
- package/dist/tools/invoices.js.map +0 -1
- package/dist/tools/kitchen.d.ts.map +0 -1
- package/dist/tools/kitchen.js.map +0 -1
- package/dist/tools/onboard_vies.d.ts.map +0 -1
- package/dist/tools/onboard_vies.js.map +0 -1
- package/dist/tools/onboarding.d.ts.map +0 -1
- package/dist/tools/onboarding.js.map +0 -1
- package/dist/tools/payroll.d.ts.map +0 -1
- package/dist/tools/payroll.js.map +0 -1
- package/dist/tools/permissions.d.ts.map +0 -1
- package/dist/tools/permissions.js.map +0 -1
- package/dist/tools/portal_domain.d.ts.map +0 -1
- package/dist/tools/portal_domain.js.map +0 -1
- package/dist/tools/pos.d.ts.map +0 -1
- package/dist/tools/pos.js.map +0 -1
- package/dist/tools/products.d.ts.map +0 -1
- package/dist/tools/products.js.map +0 -1
- package/dist/tools/quotes.d.ts.map +0 -1
- package/dist/tools/quotes.js.map +0 -1
- package/dist/tools/recurring.d.ts.map +0 -1
- package/dist/tools/recurring.js.map +0 -1
- package/dist/tools/register-all.d.ts.map +0 -1
- package/dist/tools/register-all.js.map +0 -1
- package/dist/tools/shared.d.ts.map +0 -1
- package/dist/tools/shared.js.map +0 -1
- package/dist/tools/stay.d.ts.map +0 -1
- package/dist/tools/stay.js.map +0 -1
- package/dist/tools/team.d.ts.map +0 -1
- package/dist/tools/team.js.map +0 -1
- package/dist/tools/time.d.ts.map +0 -1
- package/dist/tools/time.js.map +0 -1
- package/dist/tools/vendors.d.ts.map +0 -1
- package/dist/tools/vendors.js.map +0 -1
- package/dist/tools/webhooks.d.ts.map +0 -1
- package/dist/tools/webhooks.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/scripts/audit-mcp-refs.mjs +0 -402
- package/scripts/no-public-leak.sh +0 -60
- package/scripts/test-openai-grouped-compose.mjs +0 -173
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Output-schema CONTRACT tests.
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS FILE EXISTS (the bug class it locks down forever):
|
|
5
|
-
*
|
|
6
|
-
* The MCP tools declare Zod `outputSchema`s and put the raw Cloud Function (CF)
|
|
7
|
-
* response into `structuredContent`. The CF response has TWO shapes that have
|
|
8
|
-
* historically drifted from the schemas and broken tools silently:
|
|
9
|
-
*
|
|
10
|
-
* 1. The `{ data, meta }` ENVELOPE. Single-object reads (e.g. the shipped
|
|
11
|
-
* `/v1/fiscal/modelo/:code` CF) return `res.json({ data: <item>, meta })`;
|
|
12
|
-
* list reads return `{ data: [<item>...], total, limit, offset }`. The
|
|
13
|
-
* client unwraps single objects to `body.data` (`requestUnwrapped`) and
|
|
14
|
-
* keeps the paginated envelope (`requestPaginated`). If the schema and the
|
|
15
|
-
* unwrap disagree, the tool emits a structuredContent the model can't parse.
|
|
16
|
-
*
|
|
17
|
-
* 2. Firestore-TIMESTAMP-shaped fields. Firestore-direct reads can leak
|
|
18
|
-
* `{ _seconds, _nanoseconds }` objects on date-ish fields instead of ISO
|
|
19
|
-
* strings. The enumerated date fields are typed `z.string()`, so a raw
|
|
20
|
-
* timestamp on `createdAt` would FAIL — but `.passthrough()` carries extra
|
|
21
|
-
* Firestore-internal timestamp fields (`_syncedAt`, `lastTouchedAt`, …)
|
|
22
|
-
* through untouched. This test pins BOTH facts: enumerated dates stay ISO
|
|
23
|
-
* strings, and extra `{_seconds}` fields pass via `.passthrough()`.
|
|
24
|
-
*
|
|
25
|
-
* If a future refactor tightens a schema (drops `.passthrough()`, or retypes a
|
|
26
|
-
* passthrough timestamp field as `z.string()`), or the CF starts returning a
|
|
27
|
-
* shape the schema rejects, THIS test goes red in CI — instead of the bug
|
|
28
|
-
* reaching an LLM as an unparseable tool result.
|
|
29
|
-
*
|
|
30
|
-
* Run: npm test (after build) — node:test + node:assert.
|
|
31
|
-
*/
|
|
32
|
-
import { test, describe } from "node:test";
|
|
33
|
-
import assert from "node:assert/strict";
|
|
34
|
-
import { fiscalModeloSummaryOutput, verifactuStatusOutput, paginatedOutput, invoiceItemOutput, expenseItemOutput, clientItemOutput, } from "../tools/shared.js";
|
|
35
|
-
// ── Fixtures: representative CF responses ────────────────────────────────────
|
|
36
|
-
/** A Firestore Timestamp as it appears in a JSON-serialized CF response. */
|
|
37
|
-
const FIRESTORE_TS = { _seconds: 1_771_200_000, _nanoseconds: 0 };
|
|
38
|
-
/**
|
|
39
|
-
* The fiscal-summary CF body AFTER `requestUnwrapped` strips the `{ data, meta }`
|
|
40
|
-
* envelope — i.e. exactly what the tool receives. Includes the `model` (not
|
|
41
|
-
* `modeloCode`) shape the live CF emits, the `modelo303` totals block, a
|
|
42
|
-
* `summary` counts block, and a passthrough Firestore-timestamp field
|
|
43
|
-
* (`computedAt`) that is NOT in the schema's enumerated fields.
|
|
44
|
-
*/
|
|
45
|
-
const FISCAL_303_UNWRAPPED = {
|
|
46
|
-
model: "303",
|
|
47
|
-
period: "2026-Q1",
|
|
48
|
-
months: ["2026-01", "2026-02", "2026-03"],
|
|
49
|
-
modelo303: {
|
|
50
|
-
baseImponible: 10_000,
|
|
51
|
-
cuotaRepercutida: 2_100,
|
|
52
|
-
baseDeducible: 4_000,
|
|
53
|
-
cuotaDeducible: 840,
|
|
54
|
-
resultado: 1_260,
|
|
55
|
-
},
|
|
56
|
-
summary: { invoiceCount: 12, expenseCount: 7 },
|
|
57
|
-
readonly: true,
|
|
58
|
-
note: "Informational summary — never filed to AEAT.",
|
|
59
|
-
// Firestore-internal timestamp leaking through .passthrough() — must validate.
|
|
60
|
-
computedAt: FIRESTORE_TS,
|
|
61
|
-
};
|
|
62
|
-
/** The raw `{ data, meta }` envelope as the CF emits it pre-unwrap. */
|
|
63
|
-
const FISCAL_303_ENVELOPE = {
|
|
64
|
-
data: FISCAL_303_UNWRAPPED,
|
|
65
|
-
meta: { generatedAt: FIRESTORE_TS, source: "cf:getFiscalModeloSummary" },
|
|
66
|
-
};
|
|
67
|
-
/** Mirror of `requestUnwrapped`: pull `.data` out of a single-object envelope. */
|
|
68
|
-
function unwrap(body) {
|
|
69
|
-
if (body !== null && typeof body === "object" && !Array.isArray(body) && "data" in body) {
|
|
70
|
-
const inner = body.data;
|
|
71
|
-
if (inner !== null && typeof inner === "object" && !Array.isArray(inner)) {
|
|
72
|
-
return inner;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return body;
|
|
76
|
-
}
|
|
77
|
-
const VERIFACTU_STATUS = {
|
|
78
|
-
invoiceId: "inv_2026_001",
|
|
79
|
-
lastSubmissionAt: "2026-04-15T10:30:00.000Z", // enumerated date field stays ISO
|
|
80
|
-
hash: "a1b2c3",
|
|
81
|
-
status: "success",
|
|
82
|
-
aeatResponse: "Aceptado",
|
|
83
|
-
qrUrl: "https://www2.agenciatributaria.gob.es/qr/...",
|
|
84
|
-
// Firestore-internal timestamp via passthrough — must validate.
|
|
85
|
-
_chainTouchedAt: FIRESTORE_TS,
|
|
86
|
-
};
|
|
87
|
-
/** A paginated list envelope as `requestPaginated` keeps it: data[] + counts. */
|
|
88
|
-
const INVOICE_LIST_ENVELOPE = {
|
|
89
|
-
data: [
|
|
90
|
-
{
|
|
91
|
-
id: "inv_1",
|
|
92
|
-
clientName: "ACME SL",
|
|
93
|
-
items: [{ description: "Consulting", quantity: 1, unitPrice: 1000 }],
|
|
94
|
-
issueDate: "2026-03-01",
|
|
95
|
-
dueDate: "2026-03-31",
|
|
96
|
-
status: "sent",
|
|
97
|
-
total: 1210,
|
|
98
|
-
createdAt: "2026-03-01T09:00:00.000Z", // enumerated date → ISO string
|
|
99
|
-
// Firestore-internal timestamp via passthrough.
|
|
100
|
-
_firestoreUpdatedAt: FIRESTORE_TS,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
total: 1,
|
|
104
|
-
limit: 20,
|
|
105
|
-
offset: 0,
|
|
106
|
-
nextCursor: "inv_1",
|
|
107
|
-
};
|
|
108
|
-
const EXPENSE_LIST_ENVELOPE = {
|
|
109
|
-
data: [
|
|
110
|
-
{
|
|
111
|
-
id: "exp_1",
|
|
112
|
-
description: "AWS",
|
|
113
|
-
amount: 42.5,
|
|
114
|
-
category: "software",
|
|
115
|
-
date: "2026-03-02",
|
|
116
|
-
taxDeductible: true,
|
|
117
|
-
createdAt: "2026-03-02T00:00:00.000Z",
|
|
118
|
-
_syncedAt: FIRESTORE_TS,
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
total: 1,
|
|
122
|
-
limit: 20,
|
|
123
|
-
offset: 0,
|
|
124
|
-
};
|
|
125
|
-
const CLIENT_LIST_ENVELOPE = {
|
|
126
|
-
data: [
|
|
127
|
-
{
|
|
128
|
-
id: "cli_1",
|
|
129
|
-
name: "ACME SL",
|
|
130
|
-
email: "facturas@acme.es",
|
|
131
|
-
taxId: "B12345678",
|
|
132
|
-
address: { city: "Madrid", country: "ES" },
|
|
133
|
-
createdAt: "2026-01-10T00:00:00.000Z",
|
|
134
|
-
_importedAt: FIRESTORE_TS,
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
total: 1,
|
|
138
|
-
limit: 20,
|
|
139
|
-
offset: 0,
|
|
140
|
-
};
|
|
141
|
-
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
142
|
-
describe("output-schema contract — fiscal single-object reads", () => {
|
|
143
|
-
test("fiscalModeloSummaryOutput accepts the unwrapped 303 CF body", () => {
|
|
144
|
-
const unwrapped = unwrap(FISCAL_303_ENVELOPE);
|
|
145
|
-
const parsed = fiscalModeloSummaryOutput.safeParse(unwrapped);
|
|
146
|
-
assert.equal(parsed.success, true, parsed.success ? "" : JSON.stringify(parsed.error.issues, null, 2));
|
|
147
|
-
});
|
|
148
|
-
test("unwrap() strips the { data, meta } envelope (matches client.requestUnwrapped)", () => {
|
|
149
|
-
const unwrapped = unwrap(FISCAL_303_ENVELOPE);
|
|
150
|
-
assert.equal(unwrapped.model, "303");
|
|
151
|
-
assert.equal("meta" in unwrapped, false, "meta must NOT leak into the unwrapped item");
|
|
152
|
-
});
|
|
153
|
-
test("a Firestore-Timestamp {_seconds} field survives via .passthrough()", () => {
|
|
154
|
-
const parsed = fiscalModeloSummaryOutput.safeParse(unwrap(FISCAL_303_ENVELOPE));
|
|
155
|
-
assert.equal(parsed.success, true);
|
|
156
|
-
if (parsed.success) {
|
|
157
|
-
const ts = parsed.data.computedAt;
|
|
158
|
-
assert.equal(ts?._seconds, FIRESTORE_TS._seconds, "timestamp field must pass through intact");
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
test("verifactuStatusOutput accepts the status CF body incl. passthrough timestamp", () => {
|
|
162
|
-
const parsed = verifactuStatusOutput.safeParse(VERIFACTU_STATUS);
|
|
163
|
-
assert.equal(parsed.success, true, parsed.success ? "" : JSON.stringify(parsed.error.issues, null, 2));
|
|
164
|
-
});
|
|
165
|
-
test("enumerated date fields MUST stay strings — a raw {_seconds} there is rejected", () => {
|
|
166
|
-
// This pins the contract: the CF must emit ISO strings for enumerated dates.
|
|
167
|
-
// If it ever leaks a raw timestamp onto `lastSubmissionAt`, the tool would
|
|
168
|
-
// emit garbage — so we assert the schema (correctly) refuses it.
|
|
169
|
-
const bad = { ...VERIFACTU_STATUS, lastSubmissionAt: FIRESTORE_TS };
|
|
170
|
-
const parsed = verifactuStatusOutput.safeParse(bad);
|
|
171
|
-
assert.equal(parsed.success, false, "raw timestamp on an enumerated z.string() date must fail");
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
describe("output-schema contract — core list reads ({ data, ... } envelope)", () => {
|
|
175
|
-
test("invoices list envelope validates against paginatedOutput(invoiceItemOutput)", () => {
|
|
176
|
-
const schema = paginatedOutput(invoiceItemOutput);
|
|
177
|
-
const parsed = schema.safeParse(INVOICE_LIST_ENVELOPE);
|
|
178
|
-
assert.equal(parsed.success, true, parsed.success ? "" : JSON.stringify(parsed.error.issues, null, 2));
|
|
179
|
-
});
|
|
180
|
-
test("a list item carrying a Firestore Timestamp passthrough field validates", () => {
|
|
181
|
-
const schema = paginatedOutput(invoiceItemOutput);
|
|
182
|
-
const parsed = schema.safeParse(INVOICE_LIST_ENVELOPE);
|
|
183
|
-
assert.equal(parsed.success, true);
|
|
184
|
-
if (parsed.success) {
|
|
185
|
-
const item = parsed.data.data[0];
|
|
186
|
-
const ts = item._firestoreUpdatedAt;
|
|
187
|
-
assert.equal(ts?._seconds, FIRESTORE_TS._seconds);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
test("expenses list envelope validates against paginatedOutput(expenseItemOutput)", () => {
|
|
191
|
-
const schema = paginatedOutput(expenseItemOutput);
|
|
192
|
-
const parsed = schema.safeParse(EXPENSE_LIST_ENVELOPE);
|
|
193
|
-
assert.equal(parsed.success, true, parsed.success ? "" : JSON.stringify(parsed.error.issues, null, 2));
|
|
194
|
-
});
|
|
195
|
-
test("clients list envelope validates against paginatedOutput(clientItemOutput)", () => {
|
|
196
|
-
const schema = paginatedOutput(clientItemOutput);
|
|
197
|
-
const parsed = schema.safeParse(CLIENT_LIST_ENVELOPE);
|
|
198
|
-
assert.equal(parsed.success, true, parsed.success ? "" : JSON.stringify(parsed.error.issues, null, 2));
|
|
199
|
-
});
|
|
200
|
-
test("paginatedOutput requires the envelope counts (data/total/limit/offset)", () => {
|
|
201
|
-
const schema = paginatedOutput(invoiceItemOutput);
|
|
202
|
-
// Missing `total`/`limit`/`offset` (a bare array-ish body) must fail — this
|
|
203
|
-
// is the drift that breaks pagination if the CF stops emitting counts.
|
|
204
|
-
const parsed = schema.safeParse({ data: INVOICE_LIST_ENVELOPE.data });
|
|
205
|
-
assert.equal(parsed.success, false, "envelope without counts must be rejected");
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
//# sourceMappingURL=contract.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"contract.test.js","sourceRoot":"","sources":["../../src/__tests__/contract.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for D4-B megasprint MCP tools — HR + Webhook test + Payroll + Onboarding + Permissions + Period close (20 tools).
|
|
3
|
-
*
|
|
4
|
-
* Uses Node.js built-in test runner (node:test + node:assert). Run: npm test (after build).
|
|
5
|
-
*
|
|
6
|
-
* Coverage:
|
|
7
|
-
* - Tool registration counts per module
|
|
8
|
-
* - Happy path for every tool (mock client returns fixture)
|
|
9
|
-
* - Error path: 404 propagated as isError=true
|
|
10
|
-
* - Trust-area gates: confirm=false for period_close / period_reopen returns isError
|
|
11
|
-
*/
|
|
12
|
-
export {};
|
|
13
|
-
//# sourceMappingURL=d4b-hr-payroll-onboarding-tools.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"d4b-hr-payroll-onboarding-tools.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/d4b-hr-payroll-onboarding-tools.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for D4-B megasprint MCP tools — HR + Webhook test + Payroll + Onboarding + Permissions + Period close (20 tools).
|
|
3
|
-
*
|
|
4
|
-
* Uses Node.js built-in test runner (node:test + node:assert). Run: npm test (after build).
|
|
5
|
-
*
|
|
6
|
-
* Coverage:
|
|
7
|
-
* - Tool registration counts per module
|
|
8
|
-
* - Happy path for every tool (mock client returns fixture)
|
|
9
|
-
* - Error path: 404 propagated as isError=true
|
|
10
|
-
* - Trust-area gates: confirm=false for period_close / period_reopen returns isError
|
|
11
|
-
*/
|
|
12
|
-
import { test, describe, beforeEach } from "node:test";
|
|
13
|
-
import assert from "node:assert/strict";
|
|
14
|
-
class StubMcpServer {
|
|
15
|
-
tools = new Map();
|
|
16
|
-
registerTool(name, config, handler) {
|
|
17
|
-
this.tools.set(name, { name, config, handler });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
// ── Mock fixtures ────────────────────────────────────────────────────────────
|
|
21
|
-
const MOCK_LEAVE = {
|
|
22
|
-
id: "leave_001",
|
|
23
|
-
employeeId: "emp_001",
|
|
24
|
-
type: "vacation",
|
|
25
|
-
startDate: "2026-06-01",
|
|
26
|
-
endDate: "2026-06-15",
|
|
27
|
-
durationDays: 15,
|
|
28
|
-
status: "pending",
|
|
29
|
-
reason: "Summer break",
|
|
30
|
-
createdAt: "2026-05-16T08:00:00Z",
|
|
31
|
-
};
|
|
32
|
-
const MOCK_LEAVES_LIST = { data: [MOCK_LEAVE], total: 1, limit: 20, offset: 0 };
|
|
33
|
-
const MOCK_ATTENDANCE = {
|
|
34
|
-
id: "att_001",
|
|
35
|
-
employeeId: "emp_001",
|
|
36
|
-
clockInAt: "2026-05-16T09:00:00Z",
|
|
37
|
-
status: "open",
|
|
38
|
-
};
|
|
39
|
-
const MOCK_OVERTIME = {
|
|
40
|
-
period: "2026-05",
|
|
41
|
-
totalRegularHours: 160,
|
|
42
|
-
totalOvertimeHours: 12,
|
|
43
|
-
estimatedCostEur: 480,
|
|
44
|
-
byEmployee: [],
|
|
45
|
-
};
|
|
46
|
-
const MOCK_ANOMALY = {
|
|
47
|
-
id: "anom_001",
|
|
48
|
-
type: "overtime_spike",
|
|
49
|
-
severity: "medium",
|
|
50
|
-
subjectId: "emp_001",
|
|
51
|
-
detectedAt: "2026-05-16T10:00:00Z",
|
|
52
|
-
status: "open",
|
|
53
|
-
};
|
|
54
|
-
const MOCK_ANOMALIES_LIST = { data: [MOCK_ANOMALY], total: 1, limit: 20, offset: 0 };
|
|
55
|
-
const MOCK_WEBHOOK_TEST = {
|
|
56
|
-
webhookId: "wh_abc",
|
|
57
|
-
delivered: true,
|
|
58
|
-
statusCode: 200,
|
|
59
|
-
responseTimeMs: 142,
|
|
60
|
-
eventType: "webhook.test",
|
|
61
|
-
attemptedAt: "2026-05-16T10:30:00Z",
|
|
62
|
-
};
|
|
63
|
-
const MOCK_PAYROLL_EXPORT = {
|
|
64
|
-
format: "a3",
|
|
65
|
-
month: "2026-05",
|
|
66
|
-
fileUrl: "https://files.frihet.io/payroll/2026-05-a3.csv",
|
|
67
|
-
filename: "payroll-2026-05.csv",
|
|
68
|
-
rowCount: 12,
|
|
69
|
-
generatedAt: "2026-05-16T10:00:00Z",
|
|
70
|
-
};
|
|
71
|
-
const MOCK_PAYROLL_CHECKLIST = {
|
|
72
|
-
month: "2026-05",
|
|
73
|
-
totalEmployees: 12,
|
|
74
|
-
readyEmployees: 10,
|
|
75
|
-
missingEmployees: 2,
|
|
76
|
-
employees: [],
|
|
77
|
-
};
|
|
78
|
-
const MOCK_ONBOARDING_STATUS = {
|
|
79
|
-
workspaceId: "ws_001",
|
|
80
|
-
persona: "empresa",
|
|
81
|
-
completedSteps: ["create_workspace", "set_persona"],
|
|
82
|
-
pendingSteps: ["connect_bank", "invite_team"],
|
|
83
|
-
percentComplete: 50,
|
|
84
|
-
};
|
|
85
|
-
const MOCK_ONBOARDING_PERSONA_RESULT = {
|
|
86
|
-
workspaceId: "ws_001",
|
|
87
|
-
persona: "gestoria",
|
|
88
|
-
updatedAt: "2026-05-16T10:00:00Z",
|
|
89
|
-
};
|
|
90
|
-
const MOCK_PERMISSIONS_MATRIX = {
|
|
91
|
-
roles: [{ role: "admin", permissions: ["invoices.*", "expenses.*"] }],
|
|
92
|
-
resources: ["invoices", "expenses"],
|
|
93
|
-
};
|
|
94
|
-
const MOCK_PERMISSIONS_ME = {
|
|
95
|
-
userId: "user_001",
|
|
96
|
-
role: "admin",
|
|
97
|
-
permissions: ["invoices.read", "invoices.write"],
|
|
98
|
-
workspaceId: "ws_001",
|
|
99
|
-
};
|
|
100
|
-
const MOCK_PERIOD = {
|
|
101
|
-
id: "period_2026_q2",
|
|
102
|
-
type: "quarterly",
|
|
103
|
-
status: "open",
|
|
104
|
-
startDate: "2026-04-01",
|
|
105
|
-
endDate: "2026-06-30",
|
|
106
|
-
};
|
|
107
|
-
const MOCK_PERIOD_CLOSED = { ...MOCK_PERIOD, status: "closed", closedAt: "2026-05-16T10:00:00Z" };
|
|
108
|
-
// ── Client stubs ─────────────────────────────────────────────────────────────
|
|
109
|
-
function makeSuccessClient() {
|
|
110
|
-
return {
|
|
111
|
-
// HR
|
|
112
|
-
listLeaves: async () => MOCK_LEAVES_LIST,
|
|
113
|
-
createLeaveRequest: async (d) => ({ ...MOCK_LEAVE, ...d }),
|
|
114
|
-
approveLeave: async (id) => ({ ...MOCK_LEAVE, id, status: "approved" }),
|
|
115
|
-
rejectLeave: async (id, d) => ({ ...MOCK_LEAVE, id, status: "rejected", decisionReason: d["reason"] }),
|
|
116
|
-
cancelLeave: async (id) => ({ ...MOCK_LEAVE, id, status: "cancelled" }),
|
|
117
|
-
attendanceClockIn: async (d) => ({ ...MOCK_ATTENDANCE, ...d }),
|
|
118
|
-
attendanceClockOut: async (id) => ({ ...MOCK_ATTENDANCE, id, clockOutAt: "2026-05-16T17:00:00Z", status: "closed", durationMinutes: 480 }),
|
|
119
|
-
getOvertimeReport: async () => MOCK_OVERTIME,
|
|
120
|
-
listAnomalies: async () => MOCK_ANOMALIES_LIST,
|
|
121
|
-
// Webhook test (existing webhooks methods need stubs too for registerWebhookTools to call them)
|
|
122
|
-
listWebhooks: async () => ({ data: [], total: 0, limit: 20, offset: 0 }),
|
|
123
|
-
getWebhook: async () => ({ id: "wh_abc" }),
|
|
124
|
-
createWebhook: async (d) => ({ id: "wh_new", ...d }),
|
|
125
|
-
updateWebhook: async (id, d) => ({ id, ...d }),
|
|
126
|
-
deleteWebhook: async () => undefined,
|
|
127
|
-
testWebhook: async () => MOCK_WEBHOOK_TEST,
|
|
128
|
-
// Payroll
|
|
129
|
-
exportPayroll: async () => MOCK_PAYROLL_EXPORT,
|
|
130
|
-
getPayrollChecklist: async () => MOCK_PAYROLL_CHECKLIST,
|
|
131
|
-
// Onboarding
|
|
132
|
-
getOnboardingStatus: async () => MOCK_ONBOARDING_STATUS,
|
|
133
|
-
setOnboardingPersona: async (d) => ({ ...MOCK_ONBOARDING_PERSONA_RESULT, persona: d["persona"] }),
|
|
134
|
-
// Permissions
|
|
135
|
-
getPermissionsMatrix: async () => MOCK_PERMISSIONS_MATRIX,
|
|
136
|
-
getMyPermissions: async () => MOCK_PERMISSIONS_ME,
|
|
137
|
-
// Period close
|
|
138
|
-
getCurrentPeriod: async () => MOCK_PERIOD,
|
|
139
|
-
closePeriod: async () => MOCK_PERIOD_CLOSED,
|
|
140
|
-
reopenPeriod: async (d) => ({ ...MOCK_PERIOD, status: "reopened", reopenReason: d["reason"] }),
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function make404Client() {
|
|
144
|
-
const notFound = () => {
|
|
145
|
-
const err = Object.assign(new Error("Not Found"), { statusCode: 404, errorCode: "not_found" });
|
|
146
|
-
return Promise.reject(err);
|
|
147
|
-
};
|
|
148
|
-
return new Proxy({}, { get: () => notFound });
|
|
149
|
-
}
|
|
150
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
151
|
-
async function makeHrServer(clientFn) {
|
|
152
|
-
const server = new StubMcpServer();
|
|
153
|
-
const { registerHrTools } = await import("../tools/hr.js");
|
|
154
|
-
registerHrTools(server, clientFn());
|
|
155
|
-
return server;
|
|
156
|
-
}
|
|
157
|
-
async function makeWebhookServer(clientFn) {
|
|
158
|
-
const server = new StubMcpServer();
|
|
159
|
-
const { registerWebhookTools } = await import("../tools/webhooks.js");
|
|
160
|
-
registerWebhookTools(server, clientFn());
|
|
161
|
-
return server;
|
|
162
|
-
}
|
|
163
|
-
async function makePayrollServer(clientFn) {
|
|
164
|
-
const server = new StubMcpServer();
|
|
165
|
-
const { registerPayrollTools } = await import("../tools/payroll.js");
|
|
166
|
-
registerPayrollTools(server, clientFn());
|
|
167
|
-
return server;
|
|
168
|
-
}
|
|
169
|
-
async function makeOnboardingServer(clientFn) {
|
|
170
|
-
const server = new StubMcpServer();
|
|
171
|
-
const { registerOnboardingTools } = await import("../tools/onboarding.js");
|
|
172
|
-
registerOnboardingTools(server, clientFn());
|
|
173
|
-
return server;
|
|
174
|
-
}
|
|
175
|
-
async function makePermissionsServer(clientFn) {
|
|
176
|
-
const server = new StubMcpServer();
|
|
177
|
-
const { registerPermissionsTools } = await import("../tools/permissions.js");
|
|
178
|
-
registerPermissionsTools(server, clientFn());
|
|
179
|
-
return server;
|
|
180
|
-
}
|
|
181
|
-
async function makeAccountingCloseServer(clientFn) {
|
|
182
|
-
const server = new StubMcpServer();
|
|
183
|
-
const { registerAccountingCloseTools } = await import("../tools/accountingClose.js");
|
|
184
|
-
registerAccountingCloseTools(server, clientFn());
|
|
185
|
-
return server;
|
|
186
|
-
}
|
|
187
|
-
// ── Registration tests ───────────────────────────────────────────────────────
|
|
188
|
-
describe("D4-B Registration counts", () => {
|
|
189
|
-
test("HR tools — 9 registered", async () => {
|
|
190
|
-
const s = await makeHrServer(makeSuccessClient);
|
|
191
|
-
assert.equal(s.tools.size, 9);
|
|
192
|
-
for (const name of [
|
|
193
|
-
"leave_request_create",
|
|
194
|
-
"leave_approve",
|
|
195
|
-
"leave_reject",
|
|
196
|
-
"leave_cancel",
|
|
197
|
-
"leave_list",
|
|
198
|
-
"attendance_clock_in",
|
|
199
|
-
"attendance_clock_out",
|
|
200
|
-
"overtime_report",
|
|
201
|
-
"anomaly_list",
|
|
202
|
-
]) {
|
|
203
|
-
assert.ok(s.tools.has(name), `missing tool ${name}`);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
test("Webhook tools — 6 registered (5 existing + 1 test_webhook)", async () => {
|
|
207
|
-
const s = await makeWebhookServer(makeSuccessClient);
|
|
208
|
-
assert.equal(s.tools.size, 6);
|
|
209
|
-
assert.ok(s.tools.has("test_webhook"));
|
|
210
|
-
});
|
|
211
|
-
test("Payroll tools — 2 registered", async () => {
|
|
212
|
-
const s = await makePayrollServer(makeSuccessClient);
|
|
213
|
-
assert.equal(s.tools.size, 2);
|
|
214
|
-
assert.ok(s.tools.has("payroll_export"));
|
|
215
|
-
assert.ok(s.tools.has("payroll_checklist"));
|
|
216
|
-
});
|
|
217
|
-
test("Onboarding tools — 2 registered", async () => {
|
|
218
|
-
const s = await makeOnboardingServer(makeSuccessClient);
|
|
219
|
-
assert.equal(s.tools.size, 2);
|
|
220
|
-
assert.ok(s.tools.has("onboarding_status"));
|
|
221
|
-
assert.ok(s.tools.has("onboarding_persona_set"));
|
|
222
|
-
});
|
|
223
|
-
test("Permissions tools — 2 registered", async () => {
|
|
224
|
-
const s = await makePermissionsServer(makeSuccessClient);
|
|
225
|
-
assert.equal(s.tools.size, 2);
|
|
226
|
-
assert.ok(s.tools.has("permissions_matrix"));
|
|
227
|
-
assert.ok(s.tools.has("permissions_me"));
|
|
228
|
-
});
|
|
229
|
-
test("Period close tools — 3 registered", async () => {
|
|
230
|
-
const s = await makeAccountingCloseServer(makeSuccessClient);
|
|
231
|
-
assert.equal(s.tools.size, 3);
|
|
232
|
-
assert.ok(s.tools.has("period_close_status"));
|
|
233
|
-
assert.ok(s.tools.has("period_close"));
|
|
234
|
-
assert.ok(s.tools.has("period_reopen"));
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
// ── HR happy-path tests ──────────────────────────────────────────────────────
|
|
238
|
-
describe("HR Tools — happy path", () => {
|
|
239
|
-
let server;
|
|
240
|
-
beforeEach(async () => { server = await makeHrServer(makeSuccessClient); });
|
|
241
|
-
test("leave_request_create returns pending leave", async () => {
|
|
242
|
-
const r = await server.tools.get("leave_request_create").handler({
|
|
243
|
-
employeeId: "emp_001", type: "vacation", startDate: "2026-06-01", endDate: "2026-06-15",
|
|
244
|
-
});
|
|
245
|
-
assert.ok(!r.isError);
|
|
246
|
-
assert.equal(r.structuredContent["status"], "pending");
|
|
247
|
-
assert.equal(r.structuredContent["employeeId"], "emp_001");
|
|
248
|
-
});
|
|
249
|
-
test("leave_approve sets status approved", async () => {
|
|
250
|
-
const r = await server.tools.get("leave_approve").handler({ leaveId: "leave_001" });
|
|
251
|
-
assert.ok(!r.isError);
|
|
252
|
-
assert.equal(r.structuredContent["status"], "approved");
|
|
253
|
-
});
|
|
254
|
-
test("leave_reject requires reason and sets rejected", async () => {
|
|
255
|
-
const r = await server.tools.get("leave_reject").handler({ leaveId: "leave_001", reason: "Coverage conflict" });
|
|
256
|
-
assert.ok(!r.isError);
|
|
257
|
-
assert.equal(r.structuredContent["status"], "rejected");
|
|
258
|
-
assert.equal(r.structuredContent["decisionReason"], "Coverage conflict");
|
|
259
|
-
});
|
|
260
|
-
test("leave_cancel sets status cancelled", async () => {
|
|
261
|
-
const r = await server.tools.get("leave_cancel").handler({ leaveId: "leave_001" });
|
|
262
|
-
assert.ok(!r.isError);
|
|
263
|
-
assert.equal(r.structuredContent["status"], "cancelled");
|
|
264
|
-
});
|
|
265
|
-
test("leave_list returns paginated leaves", async () => {
|
|
266
|
-
const r = await server.tools.get("leave_list").handler({ status: "pending" });
|
|
267
|
-
assert.ok(!r.isError);
|
|
268
|
-
assert.equal(r.structuredContent["data"].length, 1);
|
|
269
|
-
assert.equal(r.structuredContent["total"], 1);
|
|
270
|
-
});
|
|
271
|
-
test("attendance_clock_in returns open entry", async () => {
|
|
272
|
-
const r = await server.tools.get("attendance_clock_in").handler({ employeeId: "emp_001", mood: "ok" });
|
|
273
|
-
assert.ok(!r.isError);
|
|
274
|
-
assert.equal(r.structuredContent["status"], "open");
|
|
275
|
-
});
|
|
276
|
-
test("attendance_clock_out closes entry with duration", async () => {
|
|
277
|
-
const r = await server.tools.get("attendance_clock_out").handler({ entryId: "att_001" });
|
|
278
|
-
assert.ok(!r.isError);
|
|
279
|
-
assert.equal(r.structuredContent["status"], "closed");
|
|
280
|
-
assert.equal(r.structuredContent["durationMinutes"], 480);
|
|
281
|
-
});
|
|
282
|
-
test("overtime_report returns aggregated hours", async () => {
|
|
283
|
-
const r = await server.tools.get("overtime_report").handler({ period: "2026-05" });
|
|
284
|
-
assert.ok(!r.isError);
|
|
285
|
-
assert.equal(r.structuredContent["totalOvertimeHours"], 12);
|
|
286
|
-
});
|
|
287
|
-
test("anomaly_list returns paginated anomalies", async () => {
|
|
288
|
-
const r = await server.tools.get("anomaly_list").handler({ severity: "medium" });
|
|
289
|
-
assert.ok(!r.isError);
|
|
290
|
-
assert.equal(r.structuredContent["data"].length, 1);
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
// ── Webhook test happy-path + error ──────────────────────────────────────────
|
|
294
|
-
describe("Webhook test_webhook", () => {
|
|
295
|
-
test("happy path returns delivered=true", async () => {
|
|
296
|
-
const server = await makeWebhookServer(makeSuccessClient);
|
|
297
|
-
const r = await server.tools.get("test_webhook").handler({ id: "wh_abc", eventType: "invoice.paid" });
|
|
298
|
-
assert.ok(!r.isError);
|
|
299
|
-
assert.equal(r.structuredContent["delivered"], true);
|
|
300
|
-
assert.equal(r.structuredContent["statusCode"], 200);
|
|
301
|
-
});
|
|
302
|
-
test("404 propagates as isError=true", async () => {
|
|
303
|
-
const server = await makeWebhookServer(make404Client);
|
|
304
|
-
const r = await server.tools.get("test_webhook").handler({ id: "wh_missing" });
|
|
305
|
-
assert.ok(r.isError);
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
// ── Payroll ──────────────────────────────────────────────────────────────────
|
|
309
|
-
describe("Payroll Tools", () => {
|
|
310
|
-
test("payroll_export returns CSV URL for A3 format", async () => {
|
|
311
|
-
const server = await makePayrollServer(makeSuccessClient);
|
|
312
|
-
const r = await server.tools.get("payroll_export").handler({ format: "a3", month: "2026-05" });
|
|
313
|
-
assert.ok(!r.isError);
|
|
314
|
-
assert.equal(r.structuredContent["format"], "a3");
|
|
315
|
-
assert.equal(r.structuredContent["rowCount"], 12);
|
|
316
|
-
});
|
|
317
|
-
test("payroll_checklist returns readiness counts", async () => {
|
|
318
|
-
const server = await makePayrollServer(makeSuccessClient);
|
|
319
|
-
const r = await server.tools.get("payroll_checklist").handler({ month: "2026-05" });
|
|
320
|
-
assert.ok(!r.isError);
|
|
321
|
-
assert.equal(r.structuredContent["readyEmployees"], 10);
|
|
322
|
-
assert.equal(r.structuredContent["missingEmployees"], 2);
|
|
323
|
-
});
|
|
324
|
-
test("payroll_export 404 → isError", async () => {
|
|
325
|
-
const server = await makePayrollServer(make404Client);
|
|
326
|
-
const r = await server.tools.get("payroll_export").handler({ format: "a3", month: "2026-05" });
|
|
327
|
-
assert.ok(r.isError);
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
// ── Onboarding ───────────────────────────────────────────────────────────────
|
|
331
|
-
describe("Onboarding Tools", () => {
|
|
332
|
-
test("onboarding_status returns persona + steps", async () => {
|
|
333
|
-
const server = await makeOnboardingServer(makeSuccessClient);
|
|
334
|
-
const r = await server.tools.get("onboarding_status").handler({});
|
|
335
|
-
assert.ok(!r.isError);
|
|
336
|
-
assert.equal(r.structuredContent["persona"], "empresa");
|
|
337
|
-
assert.equal(r.structuredContent["percentComplete"], 50);
|
|
338
|
-
});
|
|
339
|
-
test("onboarding_persona_set updates persona", async () => {
|
|
340
|
-
const server = await makeOnboardingServer(makeSuccessClient);
|
|
341
|
-
const r = await server.tools.get("onboarding_persona_set").handler({ persona: "gestoria" });
|
|
342
|
-
assert.ok(!r.isError);
|
|
343
|
-
assert.equal(r.structuredContent["persona"], "gestoria");
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
// ── Permissions ──────────────────────────────────────────────────────────────
|
|
347
|
-
describe("Permissions Tools", () => {
|
|
348
|
-
test("permissions_matrix returns roles + resources", async () => {
|
|
349
|
-
const server = await makePermissionsServer(makeSuccessClient);
|
|
350
|
-
const r = await server.tools.get("permissions_matrix").handler({});
|
|
351
|
-
assert.ok(!r.isError);
|
|
352
|
-
assert.ok(Array.isArray(r.structuredContent["roles"]));
|
|
353
|
-
});
|
|
354
|
-
test("permissions_me returns caller role", async () => {
|
|
355
|
-
const server = await makePermissionsServer(makeSuccessClient);
|
|
356
|
-
const r = await server.tools.get("permissions_me").handler({});
|
|
357
|
-
assert.ok(!r.isError);
|
|
358
|
-
assert.equal(r.structuredContent["role"], "admin");
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
// ── Period close (TRUST AREA) ────────────────────────────────────────────────
|
|
362
|
-
describe("Period Close Tools", () => {
|
|
363
|
-
test("period_close_status returns open period", async () => {
|
|
364
|
-
const server = await makeAccountingCloseServer(makeSuccessClient);
|
|
365
|
-
const r = await server.tools.get("period_close_status").handler({});
|
|
366
|
-
assert.ok(!r.isError);
|
|
367
|
-
assert.equal(r.structuredContent["status"], "open");
|
|
368
|
-
});
|
|
369
|
-
test("period_close confirm=false returns isError without calling API", async () => {
|
|
370
|
-
const server = await makeAccountingCloseServer(makeSuccessClient);
|
|
371
|
-
const r = await server.tools.get("period_close").handler({ type: "quarterly", confirm: false });
|
|
372
|
-
assert.ok(r.isError);
|
|
373
|
-
assert.ok(r.content[0].text.includes("confirm=true"));
|
|
374
|
-
});
|
|
375
|
-
test("period_close confirm=true closes period", async () => {
|
|
376
|
-
const server = await makeAccountingCloseServer(makeSuccessClient);
|
|
377
|
-
const r = await server.tools.get("period_close").handler({ type: "quarterly", confirm: true });
|
|
378
|
-
assert.ok(!r.isError);
|
|
379
|
-
assert.equal(r.structuredContent["status"], "closed");
|
|
380
|
-
});
|
|
381
|
-
test("period_reopen confirm=false returns isError", async () => {
|
|
382
|
-
const server = await makeAccountingCloseServer(makeSuccessClient);
|
|
383
|
-
const r = await server.tools.get("period_reopen").handler({ periodId: "period_2026_q2", reason: "Audit correction", confirm: false });
|
|
384
|
-
assert.ok(r.isError);
|
|
385
|
-
assert.ok(r.content[0].text.includes("confirm=true"));
|
|
386
|
-
});
|
|
387
|
-
test("period_reopen confirm=true with reason reopens period", async () => {
|
|
388
|
-
const server = await makeAccountingCloseServer(makeSuccessClient);
|
|
389
|
-
const r = await server.tools.get("period_reopen").handler({ periodId: "period_2026_q2", reason: "Audit correction", confirm: true });
|
|
390
|
-
assert.ok(!r.isError);
|
|
391
|
-
assert.equal(r.structuredContent["status"], "reopened");
|
|
392
|
-
assert.equal(r.structuredContent["reopenReason"], "Audit correction");
|
|
393
|
-
});
|
|
394
|
-
test("period_close 404 → isError", async () => {
|
|
395
|
-
const server = await makeAccountingCloseServer(make404Client);
|
|
396
|
-
const r = await server.tools.get("period_close").handler({ type: "monthly", confirm: true });
|
|
397
|
-
assert.ok(r.isError);
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
// ── HR error paths ───────────────────────────────────────────────────────────
|
|
401
|
-
describe("HR Tools — error paths", () => {
|
|
402
|
-
test("leave_list 404 → isError", async () => {
|
|
403
|
-
const server = await makeHrServer(make404Client);
|
|
404
|
-
const r = await server.tools.get("leave_list").handler({});
|
|
405
|
-
assert.ok(r.isError);
|
|
406
|
-
});
|
|
407
|
-
test("leave_request_create 404 → isError", async () => {
|
|
408
|
-
const server = await makeHrServer(make404Client);
|
|
409
|
-
const r = await server.tools.get("leave_request_create").handler({
|
|
410
|
-
employeeId: "emp_001", type: "vacation", startDate: "2026-06-01", endDate: "2026-06-15",
|
|
411
|
-
});
|
|
412
|
-
assert.ok(r.isError);
|
|
413
|
-
});
|
|
414
|
-
test("attendance_clock_out 404 → isError", async () => {
|
|
415
|
-
const server = await makeHrServer(make404Client);
|
|
416
|
-
const r = await server.tools.get("attendance_clock_out").handler({ entryId: "att_missing" });
|
|
417
|
-
assert.ok(r.isError);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
//# sourceMappingURL=d4b-hr-payroll-onboarding-tools.test.js.map
|