Skip to content

Commit 44cf188

Browse files
kgilpinclaude
andcommitted
test(cli): sequenceDiagram fixture, fingerprint coverage, and review labels
- Update the sequenceDiagram RPC integration fixture for the new action labels. - Add handler-level fingerprint tests (a real-fixture Fingerprinter test and a FingerprintDirectoryCommand.execute() test). - Annotate two security-relevant boundaries with @Label (security.deserialization on pruneWithFilter, security.path-resolution on resolveAppmapPath) for behavioral review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 87824eb commit 44cf188

5 files changed

Lines changed: 82 additions & 12 deletions

File tree

packages/cli/src/cmds/prune/pruneAppMap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export const pruneAppMap = (appMap: AppMap, size: number): PruneResult => {
2222
return { appmap };
2323
};
2424

25+
// Deserializes an untrusted serialized filter (the CLI accepts it as a
26+
// base64url-encoded string) into an AppMapFilter and applies it to the map.
27+
// Labeled so appmap-review interprets changes to this deserialization boundary.
28+
// @label security.deserialization
2529
export const pruneWithFilter = (appMap: AppMap, serializedFilter: string): PruneResult => {
2630
// TODO: update type for AppMap
2731
const fullMap = buildAppMap().source(appMap).normalize().build() as any;

packages/cli/src/cmds/query/lib/recordingId.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type { AppmapInfo } from '../queries/tree';
3434
// shapes silently collide in real corpora (multiple recordings share
3535
// the same basename or name), and the resulting wrong-recording
3636
// resolution is harder to debug than an explicit error.
37+
// @label security.path-resolution
3738
export function resolveAppmapPath(db: sqlite3.Database, ref: unknown): AppmapInfo {
3839
const s = String(ref);
3940
if (looksLikeDisplayLabel(s)) {

packages/cli/tests/integration/fixtures/sequenceDiagram/diagram.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
"elapsed": 0.038045,
8989
"eventIds": [
9090
1
91+
],
92+
"labels": [
93+
"security"
9194
]
9295
},
9396
{
@@ -250,6 +253,10 @@
250253
"elapsed": 0.150356,
251254
"eventIds": [
252255
8
256+
],
257+
"labels": [
258+
"provider.authenticate",
259+
"security"
253260
]
254261
},
255262
{
@@ -347,6 +354,9 @@
347354
"elapsed": 0.008962,
348355
"eventIds": [
349356
29
357+
],
358+
"labels": [
359+
"security"
350360
]
351361
}
352362
],
@@ -431,6 +441,10 @@
431441
"elapsed": 0.005355,
432442
"eventIds": [
433443
40
444+
],
445+
"labels": [
446+
"provider.authenticate",
447+
"security"
434448
]
435449
},
436450
{
@@ -456,6 +470,10 @@
456470
"elapsed": 0.00005,
457471
"eventIds": [
458472
46
473+
],
474+
"labels": [
475+
"json",
476+
"serialization"
459477
]
460478
}
461479
],
Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,53 @@
11
import { dir } from 'tmp';
22
import FingerprintDirectoryCommand from '../../../src/fingerprint/fingerprintDirectoryCommand';
33
import { promisify } from 'util';
4-
import { writeFile, rm } from 'node:fs/promises';
4+
import { writeFile, rm, cp, readdir } from 'node:fs/promises';
55
import { join } from 'path';
66

77
const mkTmpDir = promisify(dir);
88

99
describe('index command', () => {
10-
let appMapDir: string;
11-
let appMapPath: string;
10+
describe('identifying AppMaps', () => {
11+
let appMapDir: string;
12+
let appMapPath: string;
1213

13-
beforeEach(async () => {
14-
appMapDir = await mkTmpDir();
15-
appMapPath = join(appMapDir, 'example.appmap.json');
16-
await writeFile(appMapPath, '{}');
14+
beforeEach(async () => {
15+
appMapDir = await mkTmpDir();
16+
appMapPath = join(appMapDir, 'example.appmap.json');
17+
await writeFile(appMapPath, '{}');
18+
});
19+
20+
afterEach(() => rm(appMapDir, { recursive: true, force: true }));
21+
22+
it('identifies AppMaps for indexing', () => {
23+
const subject = new FingerprintDirectoryCommand(appMapDir);
24+
return expect(subject.files(() => {})).resolves.toEqual([appMapPath]);
25+
});
1726
});
1827

19-
afterEach(() => rm(appMapDir, { recursive: true, force: true }));
28+
describe('fingerprinting a directory', () => {
29+
let appMapDir: string;
30+
31+
beforeEach(async () => {
32+
appMapDir = await mkTmpDir();
33+
// A real recording so execute() exercises the full fingerprint pipeline
34+
// (canonicalization, hashing, index-file emission), not just globbing.
35+
await cp(
36+
join(__dirname, '..', 'fixtures', 'ruby', 'revoke_api_key.appmap.json'),
37+
join(appMapDir, 'revoke_api_key.appmap.json')
38+
);
39+
});
40+
41+
afterEach(() => rm(appMapDir, { recursive: true, force: true }));
2042

21-
it('identifies AppMaps for indexing', () => {
22-
const subject = new FingerprintDirectoryCommand(appMapDir);
23-
return expect(subject.files(() => {})).resolves.toEqual([appMapPath]);
43+
it('fingerprints AppMaps and writes an index', async () => {
44+
const subject = new FingerprintDirectoryCommand(appMapDir);
45+
const count = await subject.execute();
46+
expect(count).toBe(1);
47+
// The index is written into a directory named after the AppMap.
48+
const indexDir = join(appMapDir, 'revoke_api_key');
49+
const indexFiles = await readdir(indexDir);
50+
expect(indexFiles).toContain('metadata.json');
51+
});
2452
});
2553
});

packages/cli/tests/unit/fingerprint/fingerprinter.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mkdtemp, open, rm, writeFile } from 'fs/promises';
1+
import { cp, mkdtemp, open, readdir, rm, writeFile } from 'fs/promises';
22
import path from 'node:path';
33
import { tmpdir } from 'os';
44
import FileTooLargeError from '../../../src/fingerprint/fileTooLargeError';
@@ -18,6 +18,25 @@ function withTempDir<T>(closure: (dir: string) => Promise<T>): () => Promise<T>
1818
}
1919

2020
describe(Fingerprinter, () => {
21+
it(
22+
'fingerprints a recording and writes the on-disk index',
23+
withTempDir(async (dir) => {
24+
const filePath = path.join(dir, 'revoke_api_key.appmap.json');
25+
await cp(
26+
path.join(__dirname, '..', 'fixtures', 'ruby', 'revoke_api_key.appmap.json'),
27+
filePath
28+
);
29+
30+
await new Fingerprinter().fingerprint(filePath);
31+
32+
// The legacy per-file index is written into a directory named after the
33+
// AppMap (classMap, metadata, canonicalized forms).
34+
const indexFiles = await readdir(path.join(dir, 'revoke_api_key'));
35+
expect(indexFiles).toContain('metadata.json');
36+
expect(indexFiles).toContain('classMap.json');
37+
})
38+
);
39+
2140
it(
2241
'rejects appmaps that are too large',
2342
withTempDir(async (dir) => {

0 commit comments

Comments
 (0)