Files
scan-receipts/tests/e2e/sprint_c_image.test.ts
Timo 84b9987c49 Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing,
Docker/deploy config, tests, docs) into version control on top of the
placeholder initial commit, and adds account self-deletion (Danger Zone
in Settings, password + typed-email confirmation, cascading DB cleanup,
Stripe cancellation) per GDPR right-to-erasure.

Excludes local build caches, node_modules, and internal agent scratch
files; .gitignore hardened to keep those out going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 20:59:04 +02:00

116 lines
3.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Sprint C: image quality — min width, contrast gate, crop box, deskew.
*/
import { describe, test, expect } from "./runner";
import {
AI_IMAGE_LONG_EDGE_PX,
AI_IMAGE_MIN_WIDTH_PX,
computeReceiptResize,
} from "../../src/lib/image/resize";
import {
contentBoundingBox,
estimateSkewDegrees,
needsContrastBoost,
} from "../../src/lib/image/enhance";
import { processReceiptImage } from "../../src/lib/image/processor";
describe("Sprint C — Mindestbreite", () => {
test("Querformat bleibt am 1536-Long-Edge", () => {
const out = computeReceiptResize(2000, 1400);
expect(out.width).toBe(1536);
expect(out.height).toBe(1075);
});
test("quadratische kleine Fotos werden nicht hochskaliert", () => {
expect(computeReceiptResize(400, 400)).toEqual({ width: 400, height: 400 });
});
test("hoher Thermobon wird breiter als bei reinem Long-Edge-Cap", () => {
const srcW = 800;
const srcH = 3500;
const longEdgeOnlyW = Math.round(srcW * (AI_IMAGE_LONG_EDGE_PX / srcH));
const out = computeReceiptResize(srcW, srcH);
expect(longEdgeOnlyW).toBeLessThan(400);
expect(out.width).toBeGreaterThan(longEdgeOnlyW);
expect(out.width).toBeGreaterThanOrEqual(650);
expect(Math.max(out.width, out.height)).toBeLessThanOrEqual(4096);
});
test("Min-Width-Konstante ist 1100", () => {
expect(AI_IMAGE_MIN_WIDTH_PX).toBe(1100);
});
});
describe("Sprint C — Kontrast", () => {
test("verwaschenes Thermobild (niedrige Stdev) bekommt Boost", () => {
expect(
needsContrastBoost({
channels: [{ mean: 170, stdev: 12, min: 140, max: 200 }],
})
).toBe(true);
});
test("farbstarkes Foto bekommt keinen Boost", () => {
expect(
needsContrastBoost({
channels: [
{ mean: 90, stdev: 55, min: 10, max: 240 },
{ mean: 100, stdev: 60, min: 8, max: 250 },
{ mean: 80, stdev: 50, min: 5, max: 230 },
],
})
).toBe(false);
});
});
describe("Sprint C — Crop", () => {
test("dunkles Rechteck auf weißem Grund wird als Inhalt erkannt", () => {
const w = 80;
const h = 80;
const data = new Uint8Array(w * h).fill(250);
for (let y = 18; y < 62; y++) {
for (let x = 16; x < 64; x++) {
data[y * w + x] = 20;
}
}
const box = contentBoundingBox(data, w, h);
expect(box).not.toBeNull();
expect(box!.left).toBeLessThanOrEqual(16);
expect(box!.top).toBeLessThanOrEqual(18);
expect(box!.left + box!.width).toBeGreaterThanOrEqual(64);
expect(box!.top + box!.height).toBeGreaterThanOrEqual(62);
});
test("einfarbiges Bild wird nicht beschnitten", () => {
const data = new Uint8Array(40 * 40).fill(200);
expect(contentBoundingBox(data, 40, 40)).toBeNull();
});
});
describe("Sprint C — Deskew", () => {
test("waagerechte Textzeilen → Winkel ~ 0", () => {
const w = 64;
const h = 80;
const data = new Uint8Array(w * h);
for (let y = 0; y < h; y++) {
const dark = y % 8 < 3;
data.fill(dark ? 15 : 230, y * w, y * w + w);
}
expect(Math.abs(estimateSkewDegrees(data, w, h))).toBeLessThan(1);
});
});
describe("Sprint C — Pipeline", () => {
test("1×1-PNG überlebt Deskew/Crop/Resize", async () => {
const png = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"base64"
);
const result = await processReceiptImage(png, "image/png");
expect(result.mimeType).toBe("image/jpeg");
expect(result.width).toBeGreaterThan(0);
expect(result.height).toBeGreaterThan(0);
});
});