mirror of
https://github.com/mgeeky/decode-spam-headers.git
synced 2026-02-22 05:23:31 +01:00
MAESTRO: extract headers from dropped EML
This commit is contained in:
@@ -30,7 +30,7 @@ This phase performs final integration, accessibility audit, responsive testing,
|
|||||||
## Completion
|
## Completion
|
||||||
|
|
||||||
- [x] Complete flow works end-to-end: paste headers → configure tests → analyse → view report → export. Notes: replaced Playwright example spec with end-to-end flow test (paste + configure + analyse + report + export), adjusted Playwright webServer ports/CORS for local 3100 runs, ran `npx playwright test e2e/example.spec.ts --project=chromium`.
|
- [x] Complete flow works end-to-end: paste headers → configure tests → analyse → view report → export. Notes: replaced Playwright example spec with end-to-end flow test (paste + configure + analyse + report + export), adjusted Playwright webServer ports/CORS for local 3100 runs, ran `npx playwright test e2e/example.spec.ts --project=chromium`.
|
||||||
- [ ] File drop flow works: drop EML → auto-populate → analyse → report
|
- [x] File drop flow works: drop EML → auto-populate → analyse → report. Notes: extract header block from dropped EML before populating input; updated FileDropZone tests and ran `npx vitest run src/__tests__/FileDropZone.test.tsx`.
|
||||||
- [ ] Cache flow works: analyse → reload → see cached results → clear cache
|
- [ ] Cache flow works: analyse → reload → see cached results → clear cache
|
||||||
- [ ] Rate limiting flow works: exceed limit → CAPTCHA modal → solve → retry succeeds
|
- [ ] Rate limiting flow works: exceed limit → CAPTCHA modal → solve → retry succeeds
|
||||||
- [ ] `pytest backend/tests/` passes with ≥80% coverage on new modules
|
- [ ] `pytest backend/tests/` passes with ≥80% coverage on new modules
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ describe("FileDropZone", () => {
|
|||||||
|
|
||||||
it("reads dropped EML/TXT file content", () => {
|
it("reads dropped EML/TXT file content", () => {
|
||||||
const handleContent = vi.fn();
|
const handleContent = vi.fn();
|
||||||
const restore = mockFileReader("Header from file");
|
const restore = mockFileReader(
|
||||||
|
"From: sender@example.com\r\nSubject: Hello\r\n\r\nBody: should be ignored",
|
||||||
|
);
|
||||||
const { container } = render(<FileDropZone onFileContent={handleContent} />);
|
const { container } = render(<FileDropZone onFileContent={handleContent} />);
|
||||||
const dropZone = getDropZone(container);
|
const dropZone = getDropZone(container);
|
||||||
const file = new File(["Header from file"], "sample.eml", { type: "message/rfc822" });
|
const file = new File(["Header from file"], "sample.eml", { type: "message/rfc822" });
|
||||||
@@ -108,7 +110,7 @@ describe("FileDropZone", () => {
|
|||||||
|
|
||||||
restore();
|
restore();
|
||||||
|
|
||||||
expect(handleContent).toHaveBeenCalledWith("Header from file");
|
expect(handleContent).toHaveBeenCalledWith("From: sender@example.com\nSubject: Hello");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejects unsupported file types with feedback", () => {
|
it("rejects unsupported file types with feedback", () => {
|
||||||
|
|||||||
@@ -58,6 +58,28 @@ const getFirstFile = (transfer: DataTransfer | null): File | null => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeLineEndings = (value: string): string =>
|
||||||
|
value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||||
|
|
||||||
|
const extractHeaderBlock = (content: string): string => {
|
||||||
|
const normalized = normalizeLineEndings(content);
|
||||||
|
const lines = normalized.split("\n");
|
||||||
|
const headerLines: string[] = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim() === "") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
headerLines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerLines.length === 0) {
|
||||||
|
return normalized.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return headerLines.join("\n").trimEnd();
|
||||||
|
};
|
||||||
|
|
||||||
export default function FileDropZone({ onFileContent }: FileDropZoneProps) {
|
export default function FileDropZone({ onFileContent }: FileDropZoneProps) {
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -80,7 +102,7 @@ export default function FileDropZone({ onFileContent }: FileDropZoneProps) {
|
|||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const result = reader.result;
|
const result = reader.result;
|
||||||
const content = typeof result === "string" ? result : "";
|
const content = typeof result === "string" ? result : "";
|
||||||
onFileContent(content);
|
onFileContent(extractHeaderBlock(content));
|
||||||
};
|
};
|
||||||
reader.onerror = () => {
|
reader.onerror = () => {
|
||||||
setError("Unable to read the dropped file.");
|
setError("Unable to read the dropped file.");
|
||||||
|
|||||||
Reference in New Issue
Block a user