| 1 | const assert = require("node:assert/strict"); |
| 2 | const path = require("node:path"); |
| 3 | const test = require("node:test"); |
| 4 | const os = require("os"); |
| 5 | |
| 6 | const ARTIFACTS_PATH = path.join(__dirname, "..", "scripts", "artifacts.js"); |
| 7 | |
| 8 | function withMockedOs(platform, arch, fn) { |
| 9 | const origPlatform = os.platform; |
| 10 | const origArch = os.arch; |
| 11 | os.platform = () => platform; |
| 12 | os.arch = () => arch; |
| 13 | delete require.cache[ARTIFACTS_PATH]; |
| 14 | try { |
| 15 | return fn(); |
| 16 | } finally { |
| 17 | os.platform = origPlatform; |
| 18 | os.arch = origArch; |
| 19 | delete require.cache[ARTIFACTS_PATH]; |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | test("openharmony x64 resolves to linux x64 binaries", () => { |
| 24 | withMockedOs("openharmony", "x64", () => { |
| 25 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 26 | const result = detectBinaryNames(); |
| 27 | assert.equal(result.codewhale, "codewhale-linux-x64"); |
| 28 | assert.equal(result.tui, "codewhale-tui-linux-x64"); |
| 29 | assert.equal(result.codew, "codew-linux-x64"); |
| 30 | }); |
| 31 | }); |
| 32 | |
| 33 | test("openharmony arm64 resolves to linux arm64 binaries", () => { |
| 34 | withMockedOs("openharmony", "arm64", () => { |
| 35 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 36 | const result = detectBinaryNames(); |
| 37 | assert.equal(result.codewhale, "codewhale-linux-arm64"); |
| 38 | assert.equal(result.tui, "codewhale-tui-linux-arm64"); |
| 39 | assert.equal(result.codew, "codew-linux-arm64"); |
| 40 | }); |
| 41 | }); |
| 42 | |
| 43 | test("android arm64 resolves to Termux-native Android assets", () => { |
| 44 | withMockedOs("android", "arm64", () => { |
| 45 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 46 | const result = detectBinaryNames(); |
| 47 | assert.equal(result.codewhale, "codewhale-android-arm64"); |
| 48 | assert.equal(result.tui, "codewhale-tui-android-arm64"); |
| 49 | assert.equal(result.codew, "codew-android-arm64"); |
| 50 | }); |
| 51 | }); |
| 52 | |
| 53 | test("genuinely unsupported platform throws with raw platform name", () => { |
| 54 | withMockedOs("freebsd", "x64", () => { |
| 55 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 56 | assert.throws( |
| 57 | () => detectBinaryNames(), |
| 58 | (err) => { |
| 59 | assert.match(err.message, /Unsupported platform: freebsd/); |
| 60 | return true; |
| 61 | }, |
| 62 | ); |
| 63 | }); |
| 64 | }); |
| 65 | |
| 66 | test("known platforms are unaffected by alias map", () => { |
| 67 | for (const [platform, arch, expectedCodeWhale] of [ |
| 68 | ["linux", "x64", "codewhale-linux-x64"], |
| 69 | ["darwin", "arm64", "codewhale-macos-arm64"], |
| 70 | ["win32", "x64", "codewhale-windows-x64.exe"], |
| 71 | ["win32", "arm64", "codewhale-windows-arm64.exe"], |
| 72 | ]) { |
| 73 | withMockedOs(platform, arch, () => { |
| 74 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 75 | const result = detectBinaryNames(); |
| 76 | assert.equal(result.codewhale, expectedCodeWhale); |
| 77 | }); |
| 78 | } |
| 79 | }); |
| 80 | |
| 81 | test("Windows arm64 resolves the complete native binary family", () => { |
| 82 | withMockedOs("win32", "arm64", () => { |
| 83 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 84 | assert.deepEqual(detectBinaryNames(), { |
| 85 | platform: "win32", |
| 86 | arch: "arm64", |
| 87 | codewhale: "codewhale-windows-arm64.exe", |
| 88 | tui: "codewhale-tui-windows-arm64.exe", |
| 89 | codew: "codew-windows-arm64.exe", |
| 90 | }); |
| 91 | }); |
| 92 | }); |
| 93 | |
| 94 | test("linux riscv64 reports the temporary upstream binding blocker", () => { |
| 95 | withMockedOs("linux", "riscv64", () => { |
| 96 | const { detectBinaryNames } = require(ARTIFACTS_PATH); |
| 97 | assert.throws( |
| 98 | () => detectBinaryNames(), |
| 99 | (err) => { |
| 100 | assert.match(err.message, /Unsupported architecture: riscv64 on platform linux/); |
| 101 | assert.match(err.message, /rquickjs-sys/); |
| 102 | assert.match(err.message, /riscv64gc-unknown-linux-gnu/); |
| 103 | return true; |
| 104 | }, |
| 105 | ); |
| 106 | }); |
| 107 | }); |
| 108 | |
| 109 | test("release asset inventory includes binaries, archives, installer, and manifests", () => { |
| 110 | const { |
| 111 | allAssetNames, |
| 112 | allReleaseAssetNames, |
| 113 | BUNDLE_ASSET_NAMES, |
| 114 | BUNDLE_CHECKSUM_MANIFEST, |
| 115 | CHECKSUM_MANIFEST, |
| 116 | checksummedReleaseAssetNames, |
| 117 | WINDOWS_INSTALLER_ASSET, |
| 118 | } = require(ARTIFACTS_PATH); |
| 119 | const assetNames = allAssetNames(); |
| 120 | const releaseAssetNames = allReleaseAssetNames(); |
| 121 | assert.ok(assetNames.includes("codewhale-windows-x64.exe")); |
| 122 | assert.ok(assetNames.includes("codewhale-tui-windows-x64.exe")); |
| 123 | assert.ok(assetNames.includes("codew-windows-x64.exe")); |
| 124 | assert.ok(assetNames.includes("codewhale.bat")); |
| 125 | assert.ok(assetNames.includes("codewhale-windows-arm64.exe")); |
| 126 | assert.ok(assetNames.includes("codewhale-tui-windows-arm64.exe")); |
| 127 | assert.ok(assetNames.includes("codew-windows-arm64.exe")); |
| 128 | assert.ok(assetNames.includes("codewhale-android-arm64")); |
| 129 | assert.ok(assetNames.includes("codewhale-tui-android-arm64")); |
| 130 | assert.ok(assetNames.includes("codew-android-arm64")); |
| 131 | assert.ok(!assetNames.includes("codewhale-linux-riscv64")); |
| 132 | assert.ok(releaseAssetNames.includes("codew-windows-x64.exe")); |
| 133 | assert.ok(releaseAssetNames.includes("codewhale.bat")); |
| 134 | assert.ok(releaseAssetNames.includes("codew-windows-arm64.exe")); |
| 135 | assert.ok(releaseAssetNames.includes("codew-android-arm64")); |
| 136 | for (const bundle of BUNDLE_ASSET_NAMES) { |
| 137 | assert.ok(releaseAssetNames.includes(bundle)); |
| 138 | } |
| 139 | assert.ok(releaseAssetNames.includes(WINDOWS_INSTALLER_ASSET)); |
| 140 | assert.ok(releaseAssetNames.includes(BUNDLE_CHECKSUM_MANIFEST)); |
| 141 | assert.ok(releaseAssetNames.includes(CHECKSUM_MANIFEST)); |
| 142 | assert.ok(checksummedReleaseAssetNames().includes(BUNDLE_CHECKSUM_MANIFEST)); |
| 143 | assert.ok(!checksummedReleaseAssetNames().includes(CHECKSUM_MANIFEST)); |
| 144 | }); |
| 145 | |
| 146 | test("CNB mirror URLs use the repository that publishes release assets", () => { |
| 147 | withMockedOs("linux", "x64", () => { |
| 148 | const keys = [ |
| 149 | "CODEWHALE_RELEASE_BASE_URL", |
| 150 | "DEEPSEEK_TUI_RELEASE_BASE_URL", |
| 151 | "DEEPSEEK_RELEASE_BASE_URL", |
| 152 | "CODEWHALE_USE_CNB_MIRROR", |
| 153 | ]; |
| 154 | const previous = Object.fromEntries(keys.map((key) => [key, process.env[key]])); |
| 155 | try { |
| 156 | for (const key of keys) delete process.env[key]; |
| 157 | process.env.CODEWHALE_USE_CNB_MIRROR = "1"; |
| 158 | const { |
| 159 | checksumManifestUrl, |
| 160 | CNB_RELEASE_ASSET_NAMES, |
| 161 | releaseAssetUrl, |
| 162 | releaseBaseUrl, |
| 163 | } = require(ARTIFACTS_PATH); |
| 164 | |
| 165 | assert.deepEqual(CNB_RELEASE_ASSET_NAMES, [ |
| 166 | "codewhale-linux-x64", |
| 167 | "codew-linux-x64", |
| 168 | "codewhale-tui-linux-x64", |
| 169 | "codewhale-artifacts-sha256.txt", |
| 170 | ]); |
| 171 | assert.equal( |
| 172 | releaseBaseUrl("0.8.68"), |
| 173 | "https://cnb.cool/codewhale.net/codewhale/-/releases/v0.8.68/", |
| 174 | ); |
| 175 | assert.equal( |
| 176 | releaseAssetUrl("codewhale-linux-x64", "0.8.68"), |
| 177 | "https://cnb.cool/codewhale.net/codewhale/-/releases/v0.8.68/codewhale-linux-x64", |
| 178 | ); |
| 179 | assert.equal( |
| 180 | checksumManifestUrl("0.8.68"), |
| 181 | "https://cnb.cool/codewhale.net/codewhale/-/releases/v0.8.68/codewhale-artifacts-sha256.txt", |
| 182 | ); |
| 183 | } finally { |
| 184 | for (const key of keys) { |
| 185 | if (previous[key] === undefined) delete process.env[key]; |
| 186 | else process.env[key] = previous[key]; |
| 187 | } |
| 188 | } |
| 189 | }); |
| 190 | }); |
| 191 | |
| 192 | test("CNB mirror fails clearly outside its Linux x64 build matrix", () => { |
| 193 | withMockedOs("darwin", "arm64", () => { |
| 194 | const previous = process.env.CODEWHALE_USE_CNB_MIRROR; |
| 195 | process.env.CODEWHALE_USE_CNB_MIRROR = "1"; |
| 196 | try { |
| 197 | const { releaseBaseUrl } = require(ARTIFACTS_PATH); |
| 198 | assert.throws( |
| 199 | () => releaseBaseUrl("0.8.68"), |
| 200 | /currently supports only Linux x64.*detected darwin arm64/, |
| 201 | ); |
| 202 | } finally { |
| 203 | if (previous === undefined) delete process.env.CODEWHALE_USE_CNB_MIRROR; |
| 204 | else process.env.CODEWHALE_USE_CNB_MIRROR = previous; |
| 205 | } |
| 206 | }); |
| 207 | }); |
| 208 | |
| 209 | test("an explicit release base takes precedence over the CNB shortcut", () => { |
| 210 | withMockedOs("darwin", "arm64", () => { |
| 211 | const previousBase = process.env.CODEWHALE_RELEASE_BASE_URL; |
| 212 | const previousCnb = process.env.CODEWHALE_USE_CNB_MIRROR; |
| 213 | process.env.CODEWHALE_RELEASE_BASE_URL = "https://mirror.example/v0.8.68"; |
| 214 | process.env.CODEWHALE_USE_CNB_MIRROR = "1"; |
| 215 | try { |
| 216 | const { releaseBaseUrl, usesCnbMirror } = require(ARTIFACTS_PATH); |
| 217 | assert.equal(usesCnbMirror(), false); |
| 218 | assert.equal(releaseBaseUrl("0.8.68"), "https://mirror.example/v0.8.68/"); |
| 219 | } finally { |
| 220 | if (previousBase === undefined) delete process.env.CODEWHALE_RELEASE_BASE_URL; |
| 221 | else process.env.CODEWHALE_RELEASE_BASE_URL = previousBase; |
| 222 | if (previousCnb === undefined) delete process.env.CODEWHALE_USE_CNB_MIRROR; |
| 223 | else process.env.CODEWHALE_USE_CNB_MIRROR = previousCnb; |
| 224 | } |
| 225 | }); |
| 226 | }); |
| 227 |