summaryrefslogtreecommitdiff
path: root/tests/fixtures
diff options
context:
space:
mode:
authorJoão Augusto Costa Branco Marado Torres <torres.dev@disroot.org>2025-06-24 12:08:41 -0300
committerJoão Augusto Costa Branco Marado Torres <torres.dev@disroot.org>2025-06-24 12:50:43 -0300
commitf9a77c5c27aede4e5978eb55d9b7af781b680a1d (patch)
treed545e325ba1ae756fc2eac66fac1001b6753c40d /tests/fixtures
feat!: initial commit
Signed-off-by: João Augusto Costa Branco Marado Torres <torres.dev@disroot.org>
Diffstat (limited to 'tests/fixtures')
-rw-r--r--tests/fixtures/setup.ts146
-rw-r--r--tests/fixtures/test_data.ts63
2 files changed, 209 insertions, 0 deletions
diff --git a/tests/fixtures/setup.ts b/tests/fixtures/setup.ts
new file mode 100644
index 0000000..0b23ec8
--- /dev/null
+++ b/tests/fixtures/setup.ts
@@ -0,0 +1,146 @@
+import {
+ createMessage,
+ decryptKey,
+ generateKey,
+ PrivateKey,
+ sign,
+} from "npm:openpgp@^6.1.1";
+import { passphrase } from "./test_data.ts";
+import { MaybeIterable } from "../../src/utils/iterator.ts";
+import { afterEach, beforeEach } from "@std/testing/bdd";
+import { Stub, stub } from "@std/testing/mock";
+
+export async function generateKeyPair(
+ name: string,
+ options?: Partial<Parameters<typeof generateKey>[0]>,
+): ReturnType<typeof generateKey> {
+ const key = await generateKey({
+ type: "ecc",
+ userIDs: [{
+ name,
+ email: `${
+ name.toLowerCase().replaceAll(/\s/g, "")
+ }@localhost.localdomain`,
+ }],
+ passphrase,
+ format: "object",
+ ...options,
+ });
+ const privateKey = await decryptKey({
+ privateKey: key.privateKey,
+ passphrase,
+ });
+ return { ...key, privateKey };
+}
+
+export function generateKeyPairWithSubkey(
+ name: string,
+): ReturnType<typeof generateKey> {
+ return generateKeyPair(name, {
+ curve: "nistP256",
+ subkeys: [{ type: "ecc", curve: "nistP256", sign: true }],
+ });
+}
+
+export async function createDetachedSignature(
+ data: Uint8Array,
+ signingKeys: MaybeIterable<PrivateKey>,
+): Promise<Uint8Array<ArrayBuffer>> {
+ const message = await createMessage({ binary: data });
+ const signature = await sign({
+ message,
+ signingKeys: Symbol.iterator in signingKeys
+ ? Iterator.from(signingKeys).toArray()
+ : signingKeys,
+ detached: true,
+ format: "object",
+ });
+ return signature.write() as Uint8Array<ArrayBuffer>;
+}
+
+export function corruptData(data: Uint8Array): Uint8Array<ArrayBuffer> {
+ const corrupted = new Uint8Array(data);
+ if (corrupted.length > 0) {
+ corrupted[0] += 1;
+ corrupted[0] %= 1 << 8;
+ }
+ return corrupted;
+}
+
+export function corruptSignatureFormat(
+ signature: Uint8Array,
+): Uint8Array<ArrayBuffer> {
+ const corrupted = new Uint8Array(signature);
+
+ if (corrupted.length > 0) {
+ // Strategy 1: Change the packet tag byte
+ // The first byte contains the tag and format information.
+ // Changing the lower 6 bits (new format tag) or higher bits (packet format)
+ // can easily break parsing. Let's try flipping a bit.
+ // corrupted[0] = corrupted[0] ^ 0x01; // Flip the last bit
+
+ // Strategy 2 (Alternative - more drastic): Truncate the signature
+ // return corrupted.slice(0, corrupted.length / 2); // Cut off half the signature
+
+ // Strategy 3 (Alternative - modify length field):
+ // This is more complex as length encoding varies, but for typical new format
+ // packets, length information is in the bytes immediately following the tag.
+ // Modifying these can cause the parser to misinterpret the packet length.
+ // Example (simplified, might need adjustment based on actual encoding):
+ if (corrupted.length > 2) {
+ corrupted[1] = (corrupted[1] + 10) % 256;
+ }
+ }
+ return corrupted;
+}
+
+const inMemoryFiles = new Map<
+ string,
+ { text?: string; bytes?: Uint8Array<ArrayBuffer> }
+>();
+
+export function startMockFs(): void {
+ inMemoryFiles.clear();
+
+ beforeEach(() => {
+ Deno.readTextFile = stub(
+ Deno,
+ "readTextFile",
+ async (path: string | URL) => {
+ const url = new URL(path).href;
+ const content = inMemoryFiles.get(url)?.text;
+ if (content === undefined) {
+ throw new Deno.errors.NotFound(`File not found: ${url}`);
+ }
+ return await Promise.resolve(content);
+ },
+ );
+
+ Deno.readFile = stub(Deno, "readFile", async (path: string | URL) => {
+ const url = new URL(path).href;
+ const content = inMemoryFiles.get(url)?.bytes;
+ if (content === undefined) {
+ throw new Deno.errors.NotFound(`File not found: ${url}`);
+ }
+ return await Promise.resolve(content);
+ });
+
+ inMemoryFiles.clear();
+ });
+
+ afterEach(() => {
+ (Deno.readTextFile as Stub).restore();
+ (Deno.readFile as Stub).restore();
+ });
+}
+
+export function createInMemoryFile(
+ url: URL,
+ content: string | Uint8Array<ArrayBuffer>,
+): URL {
+ inMemoryFiles.set(
+ url.href,
+ typeof content === "string" ? { text: content } : { bytes: content },
+ );
+ return url;
+}
diff --git a/tests/fixtures/test_data.ts b/tests/fixtures/test_data.ts
new file mode 100644
index 0000000..d2143ed
--- /dev/null
+++ b/tests/fixtures/test_data.ts
@@ -0,0 +1,63 @@
+export const TRUE = true;
+export const FALSE = false;
+
+export const passphrase = "Za39PSymj5EcuzMs9kSNsAS3KbfzKHHP";
+
+export const gitDir = new URL("file:///home/user/project/");
+
+const gitLogPrettyOutput = new TextEncoder().encode([
+ "abcdef1234567890abcdef1234567890abcdef12", // long hash
+ "abcdef1", // short hash
+ "2024-06-17T12:00:00Z", // author date
+ "Alice", // author name
+ "alice@example.com", // author email
+ "2024-06-17T12:01:00Z", // committer date
+ "Bob", // committer name
+ "bob@example.com", // committer email
+ "bob@example.com", // signer
+ "ABCDEF", // key (short)
+ "ABCDEF123456", // key fingerprint (long)
+ "gpg: Signature made...", // raw line 1
+ "gpg: Good signature from...", // raw line 2
+].join("\n")) as Uint8Array<ArrayBuffer>;
+
+const gitDiffTreeOutput = new TextEncoder().encode([
+ "M\tfile.ts",
+ "A\tnew_file.ts",
+].join("\n")) as Uint8Array<ArrayBuffer>;
+
+const emptyOutput = new TextEncoder().encode("") as Uint8Array<ArrayBuffer>;
+
+export const gitLogPrettyCommandOutput: Promise<Deno.CommandOutput> = Promise
+ .resolve({
+ stdout: gitLogPrettyOutput,
+ code: 0,
+ signal: null,
+ stderr: emptyOutput,
+ success: true,
+ });
+export const gitDiffTreeCommandOutput: Promise<Deno.CommandOutput> = Promise
+ .resolve({
+ stdout: gitDiffTreeOutput,
+ code: 0,
+ signal: null,
+ stderr: emptyOutput,
+ success: true,
+ });
+export const gitRevParseCommandOutput: Promise<Deno.CommandOutput> = Promise
+ .resolve({
+ stdout: new TextEncoder().encode(gitDir.pathname) as Uint8Array<
+ ArrayBuffer
+ >,
+ code: 0,
+ signal: null,
+ stderr: emptyOutput,
+ success: true,
+ });
+export const emptyCommandOutput: Promise<Deno.CommandOutput> = Promise.resolve({
+ stdout: emptyOutput,
+ code: 0,
+ signal: null,
+ stderr: emptyOutput,
+ success: true,
+});