Files
2026-08-14 11:44:00 +02:00

514 lines
10 KiB
JavaScript

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"
}
);
}
);