Skip to content

Commit 2ec31bc

Browse files
authoredJul 27, 2023
refactor repo-mock package (#408)
Signed-off-by: Brian DeHamer <bdehamer@github.com>
1 parent 932e55b commit 2ec31bc

File tree

8 files changed

+169
-52
lines changed

8 files changed

+169
-52
lines changed
 

‎.changeset/fast-coins-laugh.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tufjs/repo-mock': minor
3+
---
4+
5+
Export new helpers: `initializeTUFRepo` and `tufHandlers`

‎packages/repo-mock/src/__tests__/index.test.ts

+6
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ describe('mockRepo', () => {
6262
await expect(fetch(`${baseURL}/metadata/2.root.json`)).rejects.toThrow(
6363
/404/
6464
);
65+
66+
// No mock should be set-up for the 1.root.json file as this should never be
67+
// fetched in a normal TUF flow.
68+
await expect(fetch(`${baseURL}/metadata/1.root.json`)).rejects.toThrow(
69+
/No match for request/
70+
);
6571
});
6672

6773
it('mocks the targets endpoints', async () => {

‎packages/repo-mock/src/handler.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Metadata } from '@tufjs/models';
2+
import { TUFRepo } from './repo';
3+
import { Handler, HandlerFn } from './shared.types';
4+
5+
export interface TUFHandlerOptions {
6+
metadataPathPrefix?: string;
7+
targetPathPrefix?: string;
8+
}
9+
10+
export function tufHandlers(
11+
tufRepo: TUFRepo,
12+
opts: TUFHandlerOptions
13+
): Handler[] {
14+
const metadataPrefix = opts.metadataPathPrefix ?? '/metadata';
15+
const targetPrefix = opts.targetPathPrefix ?? '/targets';
16+
17+
const handlers: Handler[] = [
18+
{
19+
path: `${metadataPrefix}/1.root.json`,
20+
fn: respondWithMetadata(tufRepo.rootMeta),
21+
},
22+
{
23+
path: `${metadataPrefix}/timestamp.json`,
24+
fn: respondWithMetadata(tufRepo.timestampMeta),
25+
},
26+
{
27+
path: `${metadataPrefix}/snapshot.json`,
28+
fn: respondWithMetadata(tufRepo.snapshotMeta),
29+
},
30+
{
31+
path: `${metadataPrefix}/targets.json`,
32+
fn: respondWithMetadata(tufRepo.targetsMeta),
33+
},
34+
{
35+
path: `${metadataPrefix}/2.root.json`,
36+
fn: () => ({ statusCode: 404, response: '' }),
37+
},
38+
];
39+
40+
tufRepo.targets.forEach((target) => {
41+
handlers.push({
42+
path: `${targetPrefix}/${target.name}`,
43+
fn: () => ({ statusCode: 200, response: target.content }),
44+
});
45+
});
46+
47+
return handlers;
48+
}
49+
50+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
51+
function respondWithMetadata(meta: Metadata<any>): HandlerFn {
52+
return () => ({
53+
statusCode: 200,
54+
response: JSON.stringify(meta.toJSON()),
55+
contentType: 'application/json',
56+
});
57+
}

‎packages/repo-mock/src/index.ts

+21-48
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,39 @@ import fs from 'fs';
22
import nock from 'nock';
33
import os from 'os';
44
import path from 'path';
5-
import { KeyPair } from './key';
6-
import {
7-
createRootMeta,
8-
createSnapshotMeta,
9-
createTargetsMeta,
10-
createTimestampMeta,
11-
} from './metadata';
12-
import { Target, collectTargets } from './target';
5+
import { TUFHandlerOptions, tufHandlers } from './handler';
6+
import { mock } from './mock';
7+
import { initializeTUFRepo } from './repo';
8+
import { Target } from './shared.types';
139

14-
export type { Target } from './target';
10+
export { TUFHandlerOptions, tufHandlers } from './handler';
11+
export { TUFRepo, initializeTUFRepo } from './repo';
12+
export type { Target } from './shared.types';
1513

16-
interface MockRepoOptions {
14+
type MockRepoOptions = {
1715
baseURL?: string;
18-
metadataPathPrefix?: string;
19-
targetPathPrefix?: string;
2016
cachePath?: string;
21-
responseCount?: number;
22-
}
17+
} & TUFHandlerOptions;
2318

2419
export function mockRepo(
2520
baseURL: string,
2621
targets: Target[],
27-
options: Omit<MockRepoOptions, 'baseURL' | 'cachePath'> = {}
22+
options: TUFHandlerOptions = {}
2823
): string {
29-
const metadataPrefix = options.metadataPathPrefix ?? '/metadata';
30-
const targetPrefix = options.targetPathPrefix ?? '/targets';
31-
const count = options.responseCount ?? 1;
32-
const keyPair = new KeyPair();
33-
34-
// Translate the input targets into TUF TargetFile objects
35-
const targetFiles = collectTargets(targets);
36-
37-
// Generate all of the TUF metadata objects
38-
const targetsMeta = createTargetsMeta(targetFiles, keyPair);
39-
const snapshotMeta = createSnapshotMeta(targetsMeta, keyPair);
40-
const timestampMeta = createTimestampMeta(snapshotMeta, keyPair);
41-
const rootMeta = createRootMeta(keyPair);
42-
43-
// Calculate paths for all of the metadata files
44-
const rootPath = `${metadataPrefix}/2.root.json`;
45-
const timestampPath = `${metadataPrefix}/timestamp.json`;
46-
const snapshotPath = `${metadataPrefix}/snapshot.json`;
47-
const targetsPath = `${metadataPrefix}/targets.json`;
48-
49-
// Mock the metadata endpoints
50-
// Note: the root metadata file request always returns a 404 to indicate that
51-
// the client should use the initial root metadata file from the cache
52-
nock(baseURL).get(rootPath).times(count).reply(404);
53-
nock(baseURL).get(timestampPath).times(count).reply(200, timestampMeta);
54-
nock(baseURL).get(snapshotPath).times(count).reply(200, snapshotMeta);
55-
nock(baseURL).get(targetsPath).times(count).reply(200, targetsMeta);
24+
const tufRepo = initializeTUFRepo(targets);
25+
const handlers = tufHandlers(tufRepo, options);
26+
27+
handlers.forEach((handler) => {
28+
// Don't set-up a mock for the 1.root.json file as this should never be
29+
// fetched in a normal TUF flow.
30+
if (handler.path.endsWith('1.root.json')) {
31+
return;
32+
}
5633

57-
// Mock the target endpoints
58-
targets.forEach((target) => {
59-
nock(baseURL)
60-
.get(`${targetPrefix}/${target.name}`)
61-
.reply(200, target.content);
34+
mock(baseURL, handler);
6235
});
6336

64-
return JSON.stringify(rootMeta);
37+
return JSON.stringify(tufRepo.rootMeta.toJSON());
6538
}
6639

6740
export function clearMock() {

‎packages/repo-mock/src/mock.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import nock from 'nock';
2+
import type { Handler, HandlerFn } from './shared.types';
3+
4+
type NockHandler = (uri: string, request: nock.Body) => nock.ReplyFnResult;
5+
6+
// Sets-up nock-based mocking for the given handler
7+
export function mock(base: string, handler: Handler): void {
8+
nock(base).get(handler.path).reply(adapt(handler.fn));
9+
}
10+
11+
// Adapts our HandlerFn to nock's NockHandler format
12+
function adapt(handler: HandlerFn): NockHandler {
13+
/* istanbul ignore next */
14+
return (): nock.ReplyFnResult => {
15+
const { statusCode, response, contentType } = handler();
16+
17+
return [
18+
statusCode,
19+
response,
20+
{ 'Content-Type': contentType || 'text/plain' },
21+
];
22+
};
23+
}

‎packages/repo-mock/src/repo.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Metadata, Root, Snapshot, Targets, Timestamp } from '@tufjs/models';
2+
import { KeyPair } from './key';
3+
import {
4+
createRootMeta,
5+
createSnapshotMeta,
6+
createTargetsMeta,
7+
createTimestampMeta,
8+
} from './metadata';
9+
import { collectTargets } from './target';
10+
11+
import type { Target } from './shared.types';
12+
13+
export interface TUFRepo {
14+
rootMeta: Metadata<Root>;
15+
timestampMeta: Metadata<Timestamp>;
16+
snapshotMeta: Metadata<Snapshot>;
17+
targetsMeta: Metadata<Targets>;
18+
targets: Target[];
19+
}
20+
21+
export function initializeTUFRepo(targets: Target[]): TUFRepo {
22+
const keyPair = new KeyPair();
23+
// Translate the input targets into TUF TargetFile objects
24+
const targetFiles = collectTargets(targets);
25+
26+
// Generate all of the TUF metadata objects
27+
const targetsMeta = createTargetsMeta(targetFiles, keyPair);
28+
const snapshotMeta = createSnapshotMeta(targetsMeta, keyPair);
29+
const timestampMeta = createTimestampMeta(snapshotMeta, keyPair);
30+
const rootMeta = createRootMeta(keyPair);
31+
32+
return {
33+
rootMeta,
34+
snapshotMeta,
35+
timestampMeta,
36+
targetsMeta,
37+
targets,
38+
};
39+
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export interface Target {
2+
name: string;
3+
content: string | Buffer;
4+
}
5+
6+
export type HandlerFn = () => HandlerFnResult;
7+
8+
export type HandlerFnResult = {
9+
statusCode: number;
10+
response: string | Buffer;
11+
contentType?: string;
12+
};
13+
14+
export type Handler = {
15+
path: string;
16+
fn: HandlerFn;
17+
};

‎packages/repo-mock/src/target.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { TargetFile } from '@tufjs/models';
22
import { digestSHA256 } from './crypto';
33

4-
export interface Target {
5-
name: string;
6-
content: string | Buffer;
7-
}
4+
import type { Target } from './shared.types';
85

96
export function collectTargets(targets: Target[]): TargetFile[] {
107
return targets.map((target) => {

0 commit comments

Comments
 (0)
Please sign in to comment.