Files
HY2XS_flamy/orchestrator/test/release-rollover.test.ts
T
founder 10d2c48cf0 build: сверка артефакта Hysteria с upstream hashes.txt
SHA-256 считался локально от уже скачанного файла. Это защищает target
от последующей подмены, но не доказывает, что builder скачал именно
ожидаемый upstream artifact: сумма фиксирует то, что пришло, каким бы
оно ни было. То есть trust-on-first-use, а не проверка происхождения.

Upstream публикует контрольные суммы релиза ассетом hashes.txt:

  6493dfff...f94  build/hysteria-linux-amd64
  f24f63be...189  build/hysteria-linux-amd64-avx

Теперь резолвер отдаёт и URL этого ассета, сборка скачивает его,
берёт оттуда ожидаемую сумму и сверяет с ней бинарник - и только после
этого записывает SHA-256 в HY2XS lock и metadata.

Сопоставление идёт по базовому имени и строго на равенство: build/ -
часть пути, а hysteria-linux-amd64-avx - другой артефакт, который не
должен совпасть по префиксу. Разбор вынесен в parseUpstreamHashes и
покрыт тестами, включая форму sha256:<hex>, верхний регистр,
противоречивые и отсутствующие записи.

Релиз без hashes.txt для production-сборки непригоден и отклоняется.
Источник ожидаемой суммы фиксируется в metadata как hysteria_sha_source.
2026-08-27 12:15:38 +05:00

101 lines
4.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import {
HYSTERIA_HASHES_ASSET,
HYSTERIA_LINUX_AMD64_ASSET,
selectLatestStableRelease,
type GithubRelease
} from "../src/build/hysteriaRelease";
function assetUrl(tag: string, name: string): string {
return `https://github.com/HyNetworks/hysteria/releases/download/${tag}/${name}`;
}
function release(tag: string): GithubRelease {
return {
tag_name: tag,
draft: false,
prerelease: false,
published_at: "2026-08-23T00:59:00Z",
assets: [
{
name: HYSTERIA_LINUX_AMD64_ASSET,
browser_download_url: assetUrl(tag, HYSTERIA_LINUX_AMD64_ASSET)
},
{
name: HYSTERIA_HASHES_ASSET,
browser_download_url: assetUrl(tag, HYSTERIA_HASHES_ASSET)
}
]
};
}
function srcFile(...parts: string[]): string {
return readFileSync(join(import.meta.dir, "..", "src", ...parts), "utf8");
}
describe("release rollover", () => {
// Пакет A собран сегодня, пакет B — завтра. Выход новой upstream-версии
// не должен менять содержимое уже собранного пакета A.
const upstreamToday = [release("app/v2.12.1"), release("app/v2.12.2")];
const upstreamTomorrow = [...upstreamToday, release("app/v2.12.3")];
test("сборка сегодня закрепляет 2.12.2", () => {
expect(selectLatestStableRelease(upstreamToday).version).toBe("v2.12.2");
});
test("сборка завтра закрепляет 2.12.3", () => {
expect(selectLatestStableRelease(upstreamTomorrow).version).toBe("v2.12.3");
});
test("резолв чистый: тот же список всегда даёт тот же результат", () => {
const first = selectLatestStableRelease(upstreamToday);
const second = selectLatestStableRelease(upstreamToday);
expect(first).toEqual(second);
});
});
describe("install-time никогда не резолвит latest", () => {
// Гарантия acceptance-критерия: "latest" относится к моменту сборки,
// поэтому переустановка старого пакета обязана ставить старую версию.
const installTimeSources = [
["commands", "install.ts"],
["steps", "hysteria.ts"],
["lib", "packageMeta.ts"],
["commands", "reconfigure.ts"]
];
test("install-time код не импортирует build-time резолвер", () => {
for (const parts of installTimeSources) {
expect(srcFile(...parts)).not.toContain("build/hysteriaRelease");
}
});
test("install-time код не обращается к upstream API и не использует moving latest", () => {
for (const parts of installTimeSources) {
const source = srcFile(...parts);
expect(source).not.toContain("api.github.com");
expect(source).not.toContain("download.hysteria.network");
expect(source).not.toMatch(/releases\/latest/);
}
});
test("установка берёт версию, url и sha256 только из metadata пакета", () => {
const install = srcFile("commands", "install.ts");
expect(install).toContain('readPackageValue(options.packageDir, "hysteria.version"');
expect(install).toContain('readPackageValue(options.packageDir, "hysteria.url"');
expect(install).toContain('readPackageValue(options.packageDir, "hysteria.sha256"');
});
test("отсутствие любой части lock-метаданных прерывает установку", () => {
expect(srcFile("commands", "install.ts")).toContain("missing Hysteria lock metadata in package");
});
test("установленный бинарник сверяется с закреплённой версией", () => {
const step = srcFile("steps", "hysteria.ts");
expect(step).toContain("installed Hysteria version mismatch");
expect(step).toContain("sha256sum -c -");
});
});