skillrepo 4.5.0 → 4.5.2
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/package.json +1 -1
- package/src/commands/add.mjs +17 -2
- package/src/commands/get.mjs +17 -1
- package/src/commands/list.mjs +28 -9
- package/src/commands/remove.mjs +13 -0
- package/src/lib/agent-registry.mjs +15 -1
- package/src/lib/cli-config.mjs +32 -5
- package/src/lib/detect-agents.mjs +42 -5
- package/src/lib/sync.mjs +253 -3
- package/src/test/e2e/mock-server.mjs +92 -2
- package/src/test/integration/update-list-contract.integration.test.mjs +1565 -0
- package/src/test/lib/cli-config.test.mjs +57 -6
- package/src/test/lib/detect-agents.test.mjs +37 -4
- package/src/test/lib/sync.test.mjs +220 -0
|
@@ -80,6 +80,18 @@ export function createMockServer(initialPayload, options = {}) {
|
|
|
80
80
|
/** @type {{ status: number, body: any } | null} */
|
|
81
81
|
let forcedError = null;
|
|
82
82
|
|
|
83
|
+
// Library-request inspection. Tests use these to distinguish a true
|
|
84
|
+
// 304 wire exchange (`If-None-Match` sent, server replied 304) from
|
|
85
|
+
// a request that produced "up to date" output via some other path.
|
|
86
|
+
// PR #1575's placement-presence fix made this distinction
|
|
87
|
+
// load-bearing — see the assertion at the call sites in
|
|
88
|
+
// update-list-contract.integration.test.mjs.
|
|
89
|
+
/** @type {string | null} */
|
|
90
|
+
let lastLibraryIfNoneMatch = null;
|
|
91
|
+
/** @type {string | null} */
|
|
92
|
+
let lastLibrarySince = null;
|
|
93
|
+
let libraryRequestCount = 0;
|
|
94
|
+
|
|
83
95
|
// PR3a mutable slots for POST/DELETE library routes
|
|
84
96
|
//
|
|
85
97
|
// Each entry is `{ status: number, body: any }` describing the
|
|
@@ -486,18 +498,57 @@ export function createMockServer(initialPayload, options = {}) {
|
|
|
486
498
|
return;
|
|
487
499
|
}
|
|
488
500
|
|
|
489
|
-
// ETag short-circuit
|
|
501
|
+
// ETag short-circuit. Capture the inbound If-None-Match and
|
|
502
|
+
// `since` into inspectable slots BEFORE deciding 304-vs-200 so
|
|
503
|
+
// tests can assert on what the client actually sent — not just
|
|
504
|
+
// whether the response was 304. PR #1575's placement-presence
|
|
505
|
+
// fix makes the wire-level distinction load-bearing.
|
|
490
506
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
507
|
+
const sinceParam = url.searchParams.get("since");
|
|
508
|
+
lastLibraryIfNoneMatch = ifNoneMatch ?? null;
|
|
509
|
+
lastLibrarySince = sinceParam ?? null;
|
|
510
|
+
libraryRequestCount++;
|
|
491
511
|
if (libraryEtag && ifNoneMatch === libraryEtag) {
|
|
492
512
|
res.writeHead(304, { ETag: libraryEtag });
|
|
493
513
|
res.end();
|
|
494
514
|
return;
|
|
495
515
|
}
|
|
496
516
|
|
|
517
|
+
// Mirror the production server's `since` filter — only return
|
|
518
|
+
// skills updated AFTER the `since` timestamp. Pre-PR #1575 the
|
|
519
|
+
// mock returned every skill regardless of `since`, which hid a
|
|
520
|
+
// real production bug: dropping the ETag without ALSO dropping
|
|
521
|
+
// `since` made the server respond with an empty delta, leaving
|
|
522
|
+
// newly-detected vendor placements empty forever. Mirroring the
|
|
523
|
+
// filter here means any future regression of that class fails
|
|
524
|
+
// at test time. See src/lib/queries/library.ts in the server
|
|
525
|
+
// for the production behavior (`gt(skills.updatedAt, since)`).
|
|
526
|
+
let respondedSkills = libraryResponse.skills ?? [];
|
|
527
|
+
if (sinceParam && Array.isArray(respondedSkills)) {
|
|
528
|
+
const sinceDate = new Date(sinceParam);
|
|
529
|
+
if (!Number.isNaN(sinceDate.getTime())) {
|
|
530
|
+
respondedSkills = respondedSkills.filter((s) => {
|
|
531
|
+
// updatedAt is a string ISO timestamp in the fixture skills.
|
|
532
|
+
// Missing/invalid → treat as "older than since" → exclude
|
|
533
|
+
// (matches the production semantic of "not modified in
|
|
534
|
+
// the window").
|
|
535
|
+
if (typeof s.updatedAt !== "string") return false;
|
|
536
|
+
const updated = new Date(s.updatedAt);
|
|
537
|
+
if (Number.isNaN(updated.getTime())) return false;
|
|
538
|
+
return updated.getTime() > sinceDate.getTime();
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const filteredResponse = {
|
|
544
|
+
...libraryResponse,
|
|
545
|
+
skills: respondedSkills,
|
|
546
|
+
};
|
|
547
|
+
|
|
497
548
|
const headers = { "Content-Type": "application/json" };
|
|
498
549
|
if (libraryEtag) headers.ETag = libraryEtag;
|
|
499
550
|
res.writeHead(200, headers);
|
|
500
|
-
res.end(JSON.stringify(
|
|
551
|
+
res.end(JSON.stringify(filteredResponse));
|
|
501
552
|
return;
|
|
502
553
|
}
|
|
503
554
|
|
|
@@ -586,6 +637,45 @@ export function createMockServer(initialPayload, options = {}) {
|
|
|
586
637
|
libraryEtag = etag;
|
|
587
638
|
},
|
|
588
639
|
|
|
640
|
+
/**
|
|
641
|
+
* Inspect the inbound `If-None-Match` header from the LAST request
|
|
642
|
+
* to GET /api/v1/library. Returns the literal header string, or
|
|
643
|
+
* `null` if the request didn't send one (full-fetch path). Used
|
|
644
|
+
* by tests asserting that the placement-presence check correctly
|
|
645
|
+
* dropped or sent the ETag header. See PR #1575's coverage gap
|
|
646
|
+
* 2 — without this accessor, the "304 fires" tests could pass on
|
|
647
|
+
* an implementation that broke the wire-level optimization while
|
|
648
|
+
* preserving the user-facing "up to date" output.
|
|
649
|
+
*/
|
|
650
|
+
getLastLibraryIfNoneMatch() {
|
|
651
|
+
return lastLibraryIfNoneMatch;
|
|
652
|
+
},
|
|
653
|
+
|
|
654
|
+
/** Count of GET /api/v1/library requests since server start or reset. */
|
|
655
|
+
getLibraryRequestCount() {
|
|
656
|
+
return libraryRequestCount;
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Inspect the inbound `?since=` query param from the LAST request
|
|
661
|
+
* to GET /api/v1/library. Returns the literal string or `null` if
|
|
662
|
+
* not present. The placement-presence fix must drop BOTH the
|
|
663
|
+
* If-None-Match header AND the `since` query param when placements
|
|
664
|
+
* are incomplete — otherwise the server returns an empty delta
|
|
665
|
+
* and missing placements stay empty. Tests use this to assert the
|
|
666
|
+
* fix at the wire level.
|
|
667
|
+
*/
|
|
668
|
+
getLastLibrarySince() {
|
|
669
|
+
return lastLibrarySince;
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
/** Reset the inspection slots — useful between phases of a multi-step test. */
|
|
673
|
+
resetLibraryInspection() {
|
|
674
|
+
lastLibraryIfNoneMatch = null;
|
|
675
|
+
lastLibrarySince = null;
|
|
676
|
+
libraryRequestCount = 0;
|
|
677
|
+
},
|
|
678
|
+
|
|
589
679
|
/** Register a single-skill response keyed by `owner/name`. */
|
|
590
680
|
setSkillResponse(owner, name, skill) {
|
|
591
681
|
skillResponses.set(`${owner}/${name}`, skill);
|