const test = require("node:test"); const assert = require("node:assert/strict"); const stego = require("../src/stego/engine"); function createPixels(width, height) { const pixels = Buffer.alloc( width * height * 4 ); // Give the test image valid RGBA values. for ( let i = 0; i < pixels.length; i += 4 ) { pixels[i] = 128; pixels[i + 1] = 128; pixels[i + 2] = 128; pixels[i + 3] = 255; } return pixels; } test( "encodes and decodes a text message", async () => { const width = 100; const height = 100; const pixels = createPixels( width, height ); const originalMessage = "kokiiii is the best :3"; const password = "kokithebestie"; const encoded = await stego.encode({ pixels, width, height, message: originalMessage, password }); const decoded = await stego.decode({ pixels: encoded.pixels, password }); assert.equal( decoded.type, "message" ); assert.equal( decoded.message, originalMessage ); } ); test( "encodes and decodes a file", async () => { const width = 200; const height = 200; const pixels = createPixels( width, height ); const originalFile = Buffer.from( "This is a secret file.\n" ); const encoded = await stego.encode({ pixels, width, height, password: "test-password", file: { originalname: "secret.txt", mimetype: "text/plain", buffer: originalFile } }); const decoded = await stego.decode({ pixels: encoded.pixels, password: "test-password" }); assert.equal( decoded.type, "file" ); assert.equal( decoded.filename, "secret.txt" ); assert.equal( decoded.mimetype, "text/plain" ); assert.deepEqual( decoded.file, originalFile ); } ); test( "rejects an incorrect password", async () => { const pixels = createPixels( 100, 100 ); const encoded = await stego.encode({ pixels, width: 100, height: 100, message: "secret message", password: "correct-password" }); await assert.rejects( () => stego.decode({ pixels: encoded.pixels, password: "wrong-password" }), { message: "Invalid password or corrupted payload" } ); } ); test( "requires a password for encrypted payloads", async () => { const pixels = createPixels( 100, 100 ); const encoded = await stego.encode({ pixels, width: 100, height: 100, message: "encrypted secret", password: "secret-password" }); await assert.rejects( () => stego.decode({ pixels: encoded.pixels }), { message: "Password is required for this payload" } ); } ); test( "rejects a missing password during encrypted encoding", async () => { /* * This test intentionally verifies that * passwordless encoding is now allowed. * * It should NOT reject. */ const pixels = createPixels( 100, 100 ); const encoded = await stego.encode({ pixels, width: 100, height: 100, message: "no password" }); assert.ok( encoded.pixels ); } ); test( "encodes and decodes a message without a password", async () => { const pixels = createPixels( 100, 100 ); const encoded = await stego.encode({ pixels, width: 100, height: 100, message: "passwordless secret" }); const decoded = await stego.decode({ pixels: encoded.pixels }); assert.equal( decoded.type, "message" ); assert.equal( decoded.message, "passwordless secret" ); } ); test( "encodes and decodes a file without a password", async () => { const pixels = createPixels( 200, 200 ); const originalFile = Buffer.from( "passwordless file" ); const encoded = await stego.encode({ pixels, width: 200, height: 200, file: { originalname: "secret.txt", mimetype: "text/plain", buffer: originalFile } }); const decoded = await stego.decode({ pixels: encoded.pixels }); assert.equal( decoded.type, "file" ); assert.equal( decoded.filename, "secret.txt" ); assert.equal( decoded.mimetype, "text/plain" ); assert.deepEqual( decoded.file, originalFile ); } ); test( "rejects when neither message nor file is supplied", async () => { const pixels = createPixels( 100, 100 ); await assert.rejects( () => stego.encode({ pixels, width: 100, height: 100, password: "password" }), { message: "Message or file is required" } ); } ); test( "rejects a payload that is too large", async () => { const pixels = createPixels( 10, 10 ); const hugeMessage = "A".repeat(10000); await assert.rejects( () => stego.encode({ pixels, width: 10, height: 10, message: hugeMessage, password: "password" }), { message: /Payload too large/ } ); } ); test( "reports image capacity", () => { const capacity = stego.getCapacity( 100, 100 ); assert.equal( capacity, 3740 ); } ); test( "rejects a tampered image", async () => { const pixels = createPixels( 100, 100 ); const encoded = await stego.encode({ pixels, width: 100, height: 100, message: "secret message", password: "test-password" }); const tampered = Buffer.from( encoded.pixels ); /* * Flip a bit inside the embedded * payload. */ tampered[100] ^= 1; await assert.rejects( () => stego.decode({ pixels: tampered, password: "test-password" }), { message: "Invalid password or corrupted payload" } ); } ); test( "rejects a corrupted payload", async () => { const pixels = createPixels( 100, 100 ); const encoded = await stego.encode({ pixels, width: 100, height: 100, message: "secret message", password: "test-password" }); const corrupted = Buffer.from( encoded.pixels ); /* * Corrupt several embedded bits * in the payload area. */ for ( let i = 100; i < 110; i++ ) { corrupted[i] ^= 1; } await assert.rejects( () => stego.decode({ pixels: corrupted, password: "test-password" }), { message: "Invalid password or corrupted payload" } ); } );