diff options
author | João Augusto Costa Branco Marado Torres <torres.dev@disroot.org> | 2025-06-24 12:08:41 -0300 |
---|---|---|
committer | João Augusto Costa Branco Marado Torres <torres.dev@disroot.org> | 2025-06-24 12:50:43 -0300 |
commit | f9a77c5c27aede4e5978eb55d9b7af781b680a1d (patch) | |
tree | d545e325ba1ae756fc2eac66fac1001b6753c40d /tests |
feat!: initial commit
Signed-off-by: João Augusto Costa Branco Marado Torres <torres.dev@disroot.org>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/e2e/user_flow_test.ts | 0 | ||||
-rw-r--r-- | tests/fixtures/setup.ts | 146 | ||||
-rw-r--r-- | tests/fixtures/test_data.ts | 63 | ||||
-rw-r--r-- | tests/integration/api_test.ts | 0 | ||||
-rw-r--r-- | tests/integration/db_test.ts | 0 |
5 files changed, 209 insertions, 0 deletions
diff --git a/tests/e2e/user_flow_test.ts b/tests/e2e/user_flow_test.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/e2e/user_flow_test.ts 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, +}); diff --git a/tests/integration/api_test.ts b/tests/integration/api_test.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/integration/api_test.ts diff --git a/tests/integration/db_test.ts b/tests/integration/db_test.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/integration/db_test.ts |