uploads.test.js 5.44 KB
// Unit tests for the upload pipeline building blocks (dev-plan.md T2).
// Run: node --test uploads.test.js
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const {
  parseMultipart,
  boundaryFromContentType,
  sniffImageType,
  sniffVideoType,
  stripMetadataAndSave,
  saveVideoUpload,
  UPLOADS_DIR,
} = require("./uploads.js");

test("sniffImageType recognizes real magic bytes and rejects a zip disguised as .jpg", () => {
  const jpeg = Buffer.from([0xff, 0xd8, 0xff, 0xe0, 0, 0, 0, 0]);
  const png = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0, 0]);
  const webp = Buffer.concat([Buffer.from("RIFF"), Buffer.from([0, 0, 0, 0]), Buffer.from("WEBP")]);
  const zipDisguisedAsJpg = Buffer.from([0x50, 0x4b, 0x03, 0x04, 0, 0, 0, 0]); // PK.. local file header

  assert.equal(sniffImageType(jpeg).type, "image/jpeg");
  assert.equal(sniffImageType(png).type, "image/png");
  assert.equal(sniffImageType(webp).type, "image/webp");
  assert.equal(sniffImageType(zipDisguisedAsJpg), null);
});

test("parseMultipart extracts a file part from a real multipart body (built via the platform's own FormData/Response)", async () => {
  const fd = new FormData();
  fd.append("file", new Blob([Buffer.from([1, 2, 3, 4])], { type: "image/png" }), "photo.png");
  const encoded = new Response(fd);
  const body = Buffer.from(await encoded.arrayBuffer());
  const boundary = boundaryFromContentType(encoded.headers.get("content-type"));

  const parts = parseMultipart(body, boundary);
  assert.equal(parts.length, 1);
  assert.equal(parts[0].name, "file");
  assert.equal(parts[0].filename, "photo.png");
  assert.deepEqual([...parts[0].data], [1, 2, 3, 4]);
});

test("sniffVideoType recognizes a real mp4 (reused from the S1-S4 spike) and rejects a zip disguised as .mp4", () => {
  const realMp4 = fs.readFileSync(path.join(__dirname, "spike", "s1-s2-s4-2026-09-02", "danceref.mp4"));
  assert.equal(sniffVideoType(realMp4).type, "video/mp4");

  const zipDisguisedAsMp4 = Buffer.from([0x50, 0x4b, 0x03, 0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
  assert.equal(sniffVideoType(zipDisguisedAsMp4), null);
});

test("saveVideoUpload stores the video byte-for-byte under uploads/", () => {
  const realMp4 = fs.readFileSync(path.join(__dirname, "spike", "s1-s2-s4-2026-09-02", "danceref.mp4"));
  const sniffed = sniffVideoType(realMp4);
  const { absPath, relPath, ext } = saveVideoUpload(realMp4, sniffed);
  try {
    assert.equal(ext, "mp4");
    assert.match(relPath, /^\/uploads\/\d{6}\/[0-9a-f-]+\.mp4$/);
    assert.deepEqual(fs.readFileSync(absPath), realMp4);
  } finally {
    fs.rmSync(absPath);
  }
});

// Walks a JPEG's APP1 "Exif\0\0" TIFF block (if any) and reports whether
// IFD0 has a GPS IFD pointer (tag 0x8825) — i.e. whether the file carries
// GPS EXIF data. Dependency-free stand-in for what the original acceptance
// criteria describes as "exiftool sees no GPS" (no exiftool on this
// machine, see uploads.js's module comment).
function hasGpsExifTag(jpegBuffer) {
  let offset = 2; // skip SOI (0xFFD8)
  while (offset < jpegBuffer.length - 4) {
    if (jpegBuffer[offset] !== 0xff) break;
    const marker = jpegBuffer[offset + 1];
    if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
      offset += 2;
      continue;
    }
    if (marker === 0xda) break; // start of scan — no more markers to read
    const segmentLength = jpegBuffer.readUInt16BE(offset + 2);
    if (marker === 0xe1 && jpegBuffer.toString("latin1", offset + 4, offset + 10) === "Exif\0\0") {
      const tiffStart = offset + 10;
      const littleEndian = jpegBuffer.toString("latin1", tiffStart, tiffStart + 2) === "II";
      const readU16 = (p) => (littleEndian ? jpegBuffer.readUInt16LE(p) : jpegBuffer.readUInt16BE(p));
      const readU32 = (p) => (littleEndian ? jpegBuffer.readUInt32LE(p) : jpegBuffer.readUInt32BE(p));
      const ifd0Offset = tiffStart + readU32(tiffStart + 4);
      const entryCount = readU16(ifd0Offset);
      for (let i = 0; i < entryCount; i++) {
        const entryOffset = ifd0Offset + 2 + i * 12;
        if (readU16(entryOffset) === 0x8825) return true; // GPS IFD pointer tag
      }
      return false;
    }
    offset += 2 + segmentLength;
  }
  return false;
}

test("stripMetadataAndSave strips embedded GPS EXIF from a real photo and preserves pixel dimensions", () => {
  const fixture = fs.readFileSync(path.join(__dirname, "test-fixtures", "with-gps.jpg"));
  assert.equal(hasGpsExifTag(fixture), true, "test fixture should actually carry GPS EXIF");

  const sniffed = sniffImageType(fixture);
  assert.equal(sniffed.type, "image/jpeg");

  const { absPath, relPath, ext } = stripMetadataAndSave(fixture, sniffed);
  try {
    assert.equal(ext, "jpg");
    assert.match(relPath, /^\/uploads\/\d{6}\/[0-9a-f-]+\.jpg$/);

    const out = fs.readFileSync(absPath);
    assert.equal(hasGpsExifTag(out), false, "expected GPS EXIF to be gone after stripMetadataAndSave");
  } finally {
    fs.rmSync(absPath);
  }
});

test.after(() => {
  // stripMetadataAndSave writes into the real uploads/ tree (mirrors
  // production layout); clean up the yyyymm dir if the test left it empty.
  const yyyymm = new Date().toISOString().slice(0, 7).replace("-", "");
  const dir = path.join(UPLOADS_DIR, yyyymm);
  try {
    if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
    if (fs.readdirSync(UPLOADS_DIR).length === 0) fs.rmdirSync(UPLOADS_DIR);
  } catch {
    // already clean
  }
});