Skip to content

Commit

Permalink
refactor repo-mock package (#408)
Browse files Browse the repository at this point in the history
Signed-off-by: Brian DeHamer <[email protected]>
  • Loading branch information
bdehamer authored Jul 27, 2023
1 parent 932e55b commit 2ec31bc
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 52 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-coins-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tufjs/repo-mock': minor
---

Export new helpers: `initializeTUFRepo` and `tufHandlers`
6 changes: 6 additions & 0 deletions packages/repo-mock/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ describe('mockRepo', () => {
await expect(fetch(`${baseURL}/metadata/2.root.json`)).rejects.toThrow(
/404/
);

// No mock should be set-up for the 1.root.json file as this should never be
// fetched in a normal TUF flow.
await expect(fetch(`${baseURL}/metadata/1.root.json`)).rejects.toThrow(
/No match for request/
);
});

it('mocks the targets endpoints', async () => {
Expand Down
57 changes: 57 additions & 0 deletions packages/repo-mock/src/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Metadata } from '@tufjs/models';
import { TUFRepo } from './repo';
import { Handler, HandlerFn } from './shared.types';

export interface TUFHandlerOptions {
metadataPathPrefix?: string;
targetPathPrefix?: string;
}

export function tufHandlers(
tufRepo: TUFRepo,
opts: TUFHandlerOptions
): Handler[] {
const metadataPrefix = opts.metadataPathPrefix ?? '/metadata';
const targetPrefix = opts.targetPathPrefix ?? '/targets';

const handlers: Handler[] = [
{
path: `${metadataPrefix}/1.root.json`,
fn: respondWithMetadata(tufRepo.rootMeta),
},
{
path: `${metadataPrefix}/timestamp.json`,
fn: respondWithMetadata(tufRepo.timestampMeta),
},
{
path: `${metadataPrefix}/snapshot.json`,
fn: respondWithMetadata(tufRepo.snapshotMeta),
},
{
path: `${metadataPrefix}/targets.json`,
fn: respondWithMetadata(tufRepo.targetsMeta),
},
{
path: `${metadataPrefix}/2.root.json`,
fn: () => ({ statusCode: 404, response: '' }),
},
];

tufRepo.targets.forEach((target) => {
handlers.push({
path: `${targetPrefix}/${target.name}`,
fn: () => ({ statusCode: 200, response: target.content }),
});
});

return handlers;
}

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
function respondWithMetadata(meta: Metadata<any>): HandlerFn {
return () => ({
statusCode: 200,
response: JSON.stringify(meta.toJSON()),
contentType: 'application/json',
});
}
69 changes: 21 additions & 48 deletions packages/repo-mock/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,39 @@ import fs from 'fs';
import nock from 'nock';
import os from 'os';
import path from 'path';
import { KeyPair } from './key';
import {
createRootMeta,
createSnapshotMeta,
createTargetsMeta,
createTimestampMeta,
} from './metadata';
import { Target, collectTargets } from './target';
import { TUFHandlerOptions, tufHandlers } from './handler';
import { mock } from './mock';
import { initializeTUFRepo } from './repo';
import { Target } from './shared.types';

export type { Target } from './target';
export { TUFHandlerOptions, tufHandlers } from './handler';
export { TUFRepo, initializeTUFRepo } from './repo';
export type { Target } from './shared.types';

interface MockRepoOptions {
type MockRepoOptions = {
baseURL?: string;
metadataPathPrefix?: string;
targetPathPrefix?: string;
cachePath?: string;
responseCount?: number;
}
} & TUFHandlerOptions;

export function mockRepo(
baseURL: string,
targets: Target[],
options: Omit<MockRepoOptions, 'baseURL' | 'cachePath'> = {}
options: TUFHandlerOptions = {}
): string {
const metadataPrefix = options.metadataPathPrefix ?? '/metadata';
const targetPrefix = options.targetPathPrefix ?? '/targets';
const count = options.responseCount ?? 1;
const keyPair = new KeyPair();

// Translate the input targets into TUF TargetFile objects
const targetFiles = collectTargets(targets);

// Generate all of the TUF metadata objects
const targetsMeta = createTargetsMeta(targetFiles, keyPair);
const snapshotMeta = createSnapshotMeta(targetsMeta, keyPair);
const timestampMeta = createTimestampMeta(snapshotMeta, keyPair);
const rootMeta = createRootMeta(keyPair);

// Calculate paths for all of the metadata files
const rootPath = `${metadataPrefix}/2.root.json`;
const timestampPath = `${metadataPrefix}/timestamp.json`;
const snapshotPath = `${metadataPrefix}/snapshot.json`;
const targetsPath = `${metadataPrefix}/targets.json`;

// Mock the metadata endpoints
// Note: the root metadata file request always returns a 404 to indicate that
// the client should use the initial root metadata file from the cache
nock(baseURL).get(rootPath).times(count).reply(404);
nock(baseURL).get(timestampPath).times(count).reply(200, timestampMeta);
nock(baseURL).get(snapshotPath).times(count).reply(200, snapshotMeta);
nock(baseURL).get(targetsPath).times(count).reply(200, targetsMeta);
const tufRepo = initializeTUFRepo(targets);
const handlers = tufHandlers(tufRepo, options);

handlers.forEach((handler) => {
// Don't set-up a mock for the 1.root.json file as this should never be
// fetched in a normal TUF flow.
if (handler.path.endsWith('1.root.json')) {
return;
}

// Mock the target endpoints
targets.forEach((target) => {
nock(baseURL)
.get(`${targetPrefix}/${target.name}`)
.reply(200, target.content);
mock(baseURL, handler);
});

return JSON.stringify(rootMeta);
return JSON.stringify(tufRepo.rootMeta.toJSON());
}

export function clearMock() {
Expand Down
23 changes: 23 additions & 0 deletions packages/repo-mock/src/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import nock from 'nock';
import type { Handler, HandlerFn } from './shared.types';

type NockHandler = (uri: string, request: nock.Body) => nock.ReplyFnResult;

// Sets-up nock-based mocking for the given handler
export function mock(base: string, handler: Handler): void {
nock(base).get(handler.path).reply(adapt(handler.fn));
}

// Adapts our HandlerFn to nock's NockHandler format
function adapt(handler: HandlerFn): NockHandler {
/* istanbul ignore next */
return (): nock.ReplyFnResult => {
const { statusCode, response, contentType } = handler();

return [
statusCode,
response,
{ 'Content-Type': contentType || 'text/plain' },
];
};
}
39 changes: 39 additions & 0 deletions packages/repo-mock/src/repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Metadata, Root, Snapshot, Targets, Timestamp } from '@tufjs/models';
import { KeyPair } from './key';
import {
createRootMeta,
createSnapshotMeta,
createTargetsMeta,
createTimestampMeta,
} from './metadata';
import { collectTargets } from './target';

import type { Target } from './shared.types';

export interface TUFRepo {
rootMeta: Metadata<Root>;
timestampMeta: Metadata<Timestamp>;
snapshotMeta: Metadata<Snapshot>;
targetsMeta: Metadata<Targets>;
targets: Target[];
}

export function initializeTUFRepo(targets: Target[]): TUFRepo {
const keyPair = new KeyPair();
// Translate the input targets into TUF TargetFile objects
const targetFiles = collectTargets(targets);

// Generate all of the TUF metadata objects
const targetsMeta = createTargetsMeta(targetFiles, keyPair);
const snapshotMeta = createSnapshotMeta(targetsMeta, keyPair);
const timestampMeta = createTimestampMeta(snapshotMeta, keyPair);
const rootMeta = createRootMeta(keyPair);

return {
rootMeta,
snapshotMeta,
timestampMeta,
targetsMeta,
targets,
};
}
17 changes: 17 additions & 0 deletions packages/repo-mock/src/shared.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface Target {
name: string;
content: string | Buffer;
}

export type HandlerFn = () => HandlerFnResult;

export type HandlerFnResult = {
statusCode: number;
response: string | Buffer;
contentType?: string;
};

export type Handler = {
path: string;
fn: HandlerFn;
};
5 changes: 1 addition & 4 deletions packages/repo-mock/src/target.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { TargetFile } from '@tufjs/models';
import { digestSHA256 } from './crypto';

export interface Target {
name: string;
content: string | Buffer;
}
import type { Target } from './shared.types';

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

0 comments on commit 2ec31bc

Please sign in to comment.