56 lines · 2.4 KB
import { describe, expect, test } from "bun:test";
import { JobMasks, splitLogChunk } from "./job-masks";
import type { MaskState } from "./job-masks";

function storage(initial: string[] | null) {
  const data = new Map<string, unknown>(initial ? [["masks", initial]] : []);
  const fake = {
    get: async (key: string) => data.get(key),
    put: async (key: string, value: unknown) => void data.set(key, value),
  };
  return Object.assign(Object.create(null), fake);
}

describe("JobMasks", () => {
  test("hides every line until the list loads, and keeps add-mask values seen meanwhile", async () => {
    const masks = new JobMasks(storage(null));
    const state: MaskState = {};
    await masks.ensure(async () => null, state);
    await masks.add(state, "runtime-token");
    expect(masks.apply("value runtime-token")).toBe("[masking unavailable; line hidden]");
    expect(state.pendingMasks).toEqual(["runtime-token"]);
    await masks.ensure(async () => ["job-secret"], state);
    expect(masks.apply("job-secret and runtime-token")).toBe("*** and ***");
    expect(state.pendingMasks).toEqual([]);
  });

  test("added masks extend the stored list instead of replacing it", async () => {
    const masks = new JobMasks(storage(["stored-secret"]));
    const state: MaskState = {};
    await masks.ensure(async () => null, state);
    await masks.add(state, "added-value");
    expect(masks.apply("stored-secret added-value")).toBe("*** ***");
  });

  test("a line split across self-hosted log chunks is masked as one", async () => {
    const masks = new JobMasks(storage(["chunked-secret"]));
    await masks.ensure(async () => null, {});
    const first = splitLogChunk("", "ok\nvalue chunked-");
    expect(first.lines).toEqual(["ok"]);
    const second = splitLogChunk(first.carry, "secret done\n");