File size: 2,939 Bytes
95b671e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { test } from "node:test";
import { gunzipSync } from "node:zlib";
import {
  type SourceMapObjectStore,
  prepareSourceMapUpload,
  sourceMapObjectKey,
  sourceMapUploadSchema,
} from "./sourcemaps.js";

const sourceMap = JSON.stringify({
  version: 3,
  debugId: "debug-123",
  sources: ["app/index.tsx"],
  mappings: "AAAA",
});

test("prepareSourceMapUpload verifies source map hash and compresses content", () => {
  const input = sourceMapUploadSchema.parse({
    platform: "ios",
    release: "app@1.0.0+1",
    debugId: "debug-123",
    mapFile: "dist/ios-main.hbc.map",
    sourceMap,
    sourceMapHash: createHash("sha256").update(sourceMap).digest("hex"),
    sourceMapBytes: Buffer.byteLength(sourceMap),
  });

  const prepared = prepareSourceMapUpload(input);

  assert.equal(prepared.sourceMapBytes, Buffer.byteLength(sourceMap));
  assert.equal(prepared.input.release, "app@1.0.0+1");
  assert.equal(gunzipSync(prepared.content).toString("utf8"), sourceMap);
});

test("prepareSourceMapUpload rejects mismatched hashes", () => {
  const input = sourceMapUploadSchema.parse({
    platform: "ios",
    release: "app@1.0.0+1",
    mapFile: "dist/ios-main.hbc.map",
    sourceMap,
    sourceMapHash: "0".repeat(64),
    sourceMapBytes: Buffer.byteLength(sourceMap),
  });

  assert.throws(() => prepareSourceMapUpload(input), /sourceMapHash/);
});

test("sourceMapObjectKey builds deterministic project-scoped S3 keys", () => {
  assert.equal(
    sourceMapObjectKey({
      prefix: "/source-maps/",
      projectId: "project-123",
      platform: "ios/simulator",
      sourceMapHash: "abcdef".padEnd(64, "0"),
    }),
    "source-maps/project-123/ios_simulator/ab/abcdef0000000000000000000000000000000000000000000000000000000000.map.gz",
  );
});

test("source map object stores receive compressed content outside Postgres", async () => {
  const input = sourceMapUploadSchema.parse({
    platform: "ios",
    release: "app@1.0.0+1",
    mapFile: "dist/ios-main.hbc.map",
    sourceMap,
    sourceMapHash: createHash("sha256").update(sourceMap).digest("hex"),
    sourceMapBytes: Buffer.byteLength(sourceMap),
  });
  const prepared = prepareSourceMapUpload(input);
  const stored: { bytes: Buffer; projectId: string }[] = [];
  const store: SourceMapObjectStore = {
    async putSourceMapObject(upload) {
      stored.push({ bytes: upload.artifact.content, projectId: upload.projectId });
      return { bucket: "source-map-bucket", key: "source-maps/project/hash.map.gz" };
    },
  };

  const object = await store.putSourceMapObject({ projectId: "project-123", artifact: prepared });

  assert.equal(object.bucket, "source-map-bucket");
  assert.equal(stored[0]?.projectId, "project-123");
  const storedObject = stored[0];
  assert.ok(storedObject);
  assert.equal(gunzipSync(storedObject.bytes).toString("utf8"), sourceMap);
});