test: add isolated production acceptance harness
This commit is contained in:
+41
-39
@@ -17,42 +17,42 @@ ca32a76e708d565c4af659f0f4d2615fc32114c3f75aec1454862a3ed1e72c41 2263
|
|||||||
25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico
|
25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico
|
||||||
16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png
|
16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png
|
||||||
5f4aca19a35cbcaffa1a6993ce96b7d66052ec2b286022f2af74594e8a310568 15712 CHANGELOG.md
|
5f4aca19a35cbcaffa1a6993ce96b7d66052ec2b286022f2af74594e8a310568 15712 CHANGELOG.md
|
||||||
56691e95912beab52b9e072ad0735a706934def093d91a83a235119e03f7ef21 111308 coverage/tmp/coverage-14112-1785342182302-0.json
|
7566acd80c7e2669a742aa8e6fc51cff37e20fc9f639e05a45849652a3ec5e72 111308 coverage/tmp/coverage-13332-1785343677212-0.json
|
||||||
35e206d8e448f56715fdeaafc82a00680df74edce1bd1d74d2c626c0ebdc8dd4 344934 coverage/tmp/coverage-18504-1785342182921-0.json
|
710f8c8890ba5ba34d3019cba54c9a1e92d70b40ae922819c266a05e33864d14 346825 coverage/tmp/coverage-24720-1785343674910-0.json
|
||||||
cd5a1817ee7f405f72ede736ec2ccc5831a14c4ab99c3ae9e1af11c93c7f4c13 354684 coverage/tmp/coverage-23740-1785342180573-0.json
|
7243038d2d2a14694a19322e3f45463eb7ec60e73fe6d5f8c8d32940016aa3ab 344613 coverage/tmp/coverage-24932-1785343683161-0.json
|
||||||
f2dbe68eb60983b2038bfda8448e148e0d762794651c8f0ef0a5985549f35d11 301398 coverage/tmp/coverage-30356-1785342180023-0.json
|
93180bb5e2d2242067d9d8552ebeb58b2c3d4bee446a336298a2ffbd70415066 375705 coverage/tmp/coverage-25852-1785343688031-0.json
|
||||||
04e0ac6a30fdcbbc9f9856697b228626e0355b7c98c6045cf0db01de0c93fc68 344613 coverage/tmp/coverage-35156-1785342187759-0.json
|
703a2c535f43ec8ed5275511d58f2b70a233d1d154197f582a6acb38f84f0fb4 329358 coverage/tmp/coverage-27392-1785343676373-0.json
|
||||||
0d4e6a6dcc0ce9f8d2d9e304dbbeccc2b551fbe79637fdd16b7e860a58685d68 302513 coverage/tmp/coverage-35780-1785342181205-0.json
|
ea173be56e3c7309ccfbd411387aa13e171583f088c2b98d7ceedc41174feeaf 344499 coverage/tmp/coverage-30564-1785343676182-0.json
|
||||||
3c5a7491afc0247568bce0824331c16e2e9af0693b216e01fe73bb41ad4fe91f 327420 coverage/tmp/coverage-38404-1785342179951-0.json
|
2af0d14483ea6a68c1da5f8dbd3c18b1ae300250c4e5d577da8757fd041e0beb 89175 coverage/tmp/coverage-33800-1785343673834-0.json
|
||||||
24ca89f314bacd35321b8772706e5c4658602abf90e701561d23f67a1d4dd10e 288320 coverage/tmp/coverage-38780-1785342181696-0.json
|
63ed613aa107c5cb7f94612bc03034cfd09e09ee7843df839c36f7c63d6af614 335945 coverage/tmp/coverage-39536-1785343675374-0.json
|
||||||
8711b58d90963d5c5239011527a8d504fde3bed93671e09b8e6f8e865837d928 358165 coverage/tmp/coverage-39472-1785342183215-0.json
|
c20ef6f15b2258814a1edc635a73eae6a8a5ce8f797299b4862090d74cd2a4ca 358048 coverage/tmp/coverage-41000-1785343679480-0.json
|
||||||
5e8d4cd9c0b348618088733e133986c8bff8d822b7a0f0fb6c04a777f025e249 303090 coverage/tmp/coverage-39536-1785342182261-0.json
|
3d91c5a2a5c337ee043fd69057dc272d13f2fc6b4449f602e6258a7058a6a737 282952 coverage/tmp/coverage-42856-1785343676163-0.json
|
||||||
9aa7a9e3b5daa4a3f2139df2a8b8857d851ef2ce8e3c7679db787f6e72f5df38 302346 coverage/tmp/coverage-41596-1785342181753-0.json
|
9c4e63126e21fc5ad77aacee02f4f0ee11ed4f1e082c42058e87c994aaef042e 345345 coverage/tmp/coverage-44284-1785343695928-0.json
|
||||||
cd9419f7829a06e3932ca774fb06c51b3637639e8f750fe0bbce400921c20866 321057 coverage/tmp/coverage-42088-1785342182383-0.json
|
f3fce97506f8d0160895eb4c3e02294d7c75e0a9dce87e3dc947144642c94974 303090 coverage/tmp/coverage-44348-1785343676823-0.json
|
||||||
5b75886d617be03df5d1271223cbd8cc660d6603368349d7f7740e7b5005d3d2 304508 coverage/tmp/coverage-42724-1785342180308-0.json
|
d1228bdd74c66ed3209840f0b709e1fb03f7644a6ff4f4d519d1861753be6da0 327420 coverage/tmp/coverage-44412-1785343674123-0.json
|
||||||
e567e65494a3fadf0effdd7058761c890ff35411532673fb6d0feb0f7d0d681f 346823 coverage/tmp/coverage-44760-1785342180156-0.json
|
a664eb5689351c9ed939c9c4be41b837cb836af5c05833cdcd7d175661622728 321055 coverage/tmp/coverage-45232-1785343677332-0.json
|
||||||
4f2a6563b9710b23c918d10f1a2d713975f0f5d2feedb5731e87a441bb9eabae 352006 coverage/tmp/coverage-44924-1785342180167-0.json
|
d111623040499c4f544ce4b51aa8f908a8cbeb0e58ea471cbf71bfa90d6ba0a3 302513 coverage/tmp/coverage-46296-1785343675742-0.json
|
||||||
5e355c76dab29a1ecebe30d390970397ae6ccd71798b111fa67e395a1310ad5e 361403 coverage/tmp/coverage-46136-1785342180786-0.json
|
207bf630211c89083cb7746522b62010bccdd8cec1826d83b064627b2d35c496 354684 coverage/tmp/coverage-48416-1785343675186-0.json
|
||||||
1623101020fed0786a83d5244a03df9bf2124a9a01961a7739ce63ba7c941ca4 336008 coverage/tmp/coverage-47576-1785342181138-0.json
|
23cd47bb0313aa849ff329f2d15208a50bc36b5de74d602d37aed34ce213fbae 428588 coverage/tmp/coverage-54644-1785343677776-0.json
|
||||||
0307f0113c6b76b5044cae887f76f3c01cfd7133fd4a00a8591a979eff3ab06e 317635 coverage/tmp/coverage-48580-1785342185938-0.json
|
d68bb63951f5cc6a1bddc60abdf45a47d37198895a4d6e7bcd4f1b091a34c941 361403 coverage/tmp/coverage-54916-1785343676205-0.json
|
||||||
4588ba2d6ef043d6552135f874f2415865f2401a53c46c6e29229b6925deceb0 335945 coverage/tmp/coverage-48608-1785342180830-0.json
|
9aea05800551c378d49a72ef3dfc0bed41758596f564f8f7f5770241529f9e50 301398 coverage/tmp/coverage-55996-1785343674664-0.json
|
||||||
9335bccbbea8122e6bcd7190e5266e4a0fbd1e3631d882004e2d43cec41de95d 318326 coverage/tmp/coverage-49480-1785342181092-0.json
|
e60da912d861a7e03cbfa62075767a40299ec7c9bc2e48953813abd809ffb167 299001 coverage/tmp/coverage-56060-1785343674805-0.json
|
||||||
e82fa624b1ef3e7ea427b58aac1c3135d0ac1af62b5c1768a7c2bd59791765c7 367436 coverage/tmp/coverage-50664-1785342182886-0.json
|
2d232045fb33bb89d6a4231f685a4adf97db35204d0ac32ee4c25fc082c4f662 302346 coverage/tmp/coverage-56488-1785343676124-0.json
|
||||||
0884915ee6ccec377161cb960b768bb44da6f8622ab3d9542a0c51a9fd0106b9 375705 coverage/tmp/coverage-54052-1785342191177-0.json
|
5fc972ba40cfe569a4bbb03a43b9f56099f5264b50ae1dcdd10054afb368426b 288320 coverage/tmp/coverage-59812-1785343676345-0.json
|
||||||
a0150efac6b758945f87373f8329f05d6be53d11a3201a88a1124372e8ee7d69 282952 coverage/tmp/coverage-54676-1785342182110-0.json
|
ebb618c5693423e51ca20a70eda2b0da8521ea69dd14fcdd99fcf519701eaca9 367436 coverage/tmp/coverage-59972-1785343676795-0.json
|
||||||
885fb349d550fa169fb0a0fe97c96c9f0353e62a321a2022da2de0f1c1958811 345124 coverage/tmp/coverage-56728-1785342195847-0.json
|
181336e5fe71a06897acf7ba8068dd216641e38ad8c245d0ce717197e71432a5 317635 coverage/tmp/coverage-60580-1785343681448-0.json
|
||||||
e0dbe28900b09fc36b22fbb05bc844f57ff3094b9a1f84da97f793311c37b3f9 344499 coverage/tmp/coverage-57256-1785342181830-0.json
|
729e4f8079ece6dfea24a86fbc80bc7a8f93fe5cf7ac8723b761f058ee7a888e 296835 coverage/tmp/coverage-61120-1785343676088-0.json
|
||||||
615cc6516905c8e53e4b888bc81589ce8542e34634e5a391ba9204843f57221b 428589 coverage/tmp/coverage-58964-1785342182533-0.json
|
1d241ced91a381b3fbef67809960b7056cde581754213158d7ca7b19f20f4ca2 304508 coverage/tmp/coverage-62032-1785343674755-0.json
|
||||||
5d845ca1a0ff28f1e570dcb8a26502e902589eafc2727171f1aad0aa7a2ce52e 296835 coverage/tmp/coverage-59284-1785342181255-0.json
|
576d439708904311a09bd5b1aaa45f5a8bb15c79c22f8878e60b3104c24d5342 330765 coverage/tmp/coverage-62256-1785343674960-0.json
|
||||||
3afca2678ab83c1ecaba8c1abf70b89b3763016acb52df4027d61a590dcf5c19 284668 coverage/tmp/coverage-59848-1785342181206-0.json
|
6bca1a6ee829b5f9b463249b74f71525bd0f2ddd7e0ae547fc5f400e07cc76a3 336008 coverage/tmp/coverage-62952-1785343675508-0.json
|
||||||
5a39c973f886c05b9c34ca089f9d410ab5831036e83d7d5dbf525c2feb0e3670 342640 coverage/tmp/coverage-60948-1785342181050-0.json
|
cc27b4cbdb3861260fefcaead1889aaddde36edc6c5dc6829ed22e6dfb8dd8ff 342640 coverage/tmp/coverage-63104-1785343675273-0.json
|
||||||
79d1a3635a8c972ac6ab0aa75149b2a5f01b84ae2a0ae68e736490160b22bc27 360058 coverage/tmp/coverage-61560-1785342250533-0.json
|
53851db4e6557d92a689c11ef1a3043db4d6071a4a73622a93f8229bb113c0e7 360058 coverage/tmp/coverage-64188-1785343739320-0.json
|
||||||
03adfd7ea1161c638fe6f6a75a0ab0d860f89412ea047209ea1b15f879de9aad 299000 coverage/tmp/coverage-62420-1785342180409-0.json
|
7aaf7345348821f50b7be27e118b3e6a387bf999d5d450f4718cd29ba7553b2f 284668 coverage/tmp/coverage-65604-1785343675475-0.json
|
||||||
8387e10a857a2195458bf576c4129b2cd516a50ac5408a605eff038376bba64b 310269 coverage/tmp/coverage-62428-1785342182350-0.json
|
bb8f50356c195d0dd8684a0f84f4e8d2b03f5ebbc3d84cafcb5f18cd3fe04e45 352006 coverage/tmp/coverage-65608-1785343674667-0.json
|
||||||
a02d7c51b209b5967e63cec657caeb8e671b5c87d34dbe2cfbec2be891f6266e 329358 coverage/tmp/coverage-62604-1785342181856-0.json
|
4fc5bee2e42ba4d4af73e86930047cf66b712389698720a9ee72716c6869cba6 318326 coverage/tmp/coverage-65784-1785343677118-0.json
|
||||||
3488e63749aed14a820a72d8fad385175f2111648bdf2c10a48c1148c419836f 330765 coverage/tmp/coverage-62652-1785342180356-0.json
|
bb0881ad48a080a2ad79126316cb040b1285b097987bbf71526fe42f3515fb94 344934 coverage/tmp/coverage-8144-1785343677466-0.json
|
||||||
3da8a2ab49221e4c837c413af878b951f1011be09824ee7dcb49485fb40550bd 89175 coverage/tmp/coverage-63428-1785342179556-0.json
|
a1ee0025dfff97ef9eeb1e418317ecc7fbbc65afc6312b03bded8780cdb3db24 310269 coverage/tmp/coverage-8976-1785343676905-0.json
|
||||||
21cb96e7afe71b1dc791c818dedd244d92f9a6ed4d9ffbb3022ccb187e1bdf0f 852 docs/ACCEPTANCE.md
|
c612fcc44ff222db0c9a4cfd11a4076fafe080e4ada31e689a08739a4f14e74f 1650 docs/ACCEPTANCE.md
|
||||||
a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md
|
a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md
|
||||||
72e846f591c47a0291e7466e58e052d3d5afcf551c4e6c848632ac3c552a1244 3043 docs/CURRENT_STATE.md
|
72e846f591c47a0291e7466e58e052d3d5afcf551c4e6c848632ac3c552a1244 3043 docs/CURRENT_STATE.md
|
||||||
30a92bcf5daadb019efa2f82cb820ea302490dd1d68fb772674dc3faccd3e594 2045 docs/DEPLOYMENT_SETUP.md
|
30a92bcf5daadb019efa2f82cb820ea302490dd1d68fb772674dc3faccd3e594 2045 docs/DEPLOYMENT_SETUP.md
|
||||||
@@ -124,15 +124,15 @@ c230b931abf2293d2d44b7a69b94c35f1142c093cc46b88739a0de5cbd6d1896 1532
|
|||||||
1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs
|
1f0f388df4397e548887bbc7579fd3c864581b86469c01703201ece7a6cbf931 13667 main.cjs
|
||||||
91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md
|
91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md
|
||||||
17eeeb1d6f18fbbbb918cdfdbc3836e46ec6be9e32c059cfc330e9e4e73e39d0 183079 package-lock.json
|
17eeeb1d6f18fbbbb918cdfdbc3836e46ec6be9e32c059cfc330e9e4e73e39d0 183079 package-lock.json
|
||||||
96ab185db639205021c5b0b6fbf8d4bf19d2cad8d1a042eab9a425613ea6a574 5018 package.json
|
fc003513346ad8849f350b0c3d8de878b98a107599298a956e706175e45eff89 5097 package.json
|
||||||
2a597a5704c576783b8a72407fbc377fa7506b36a4596ea7f7bce126e394f837 1326 playwright.config.mjs
|
2a597a5704c576783b8a72407fbc377fa7506b36a4596ea7f7bce126e394f837 1326 playwright.config.mjs
|
||||||
69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs
|
69318fdf054be7aa2fe86ead9847da9da65745d8d5de548c8346f3ba0afc4892 12175 preload.cjs
|
||||||
abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd
|
abe5dd6fd68f2970cd19ef134094907c67219061d8fe9a1a08324c78de4ad437 484 PUBLISH-AND-ENABLE-UPDATE.cmd
|
||||||
f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1
|
f018383f755352ca448e2ebb1e19b1dba412a3eb793d61e64b02953e300754fd 10538 Publish-ForgeFlow-Release.ps1
|
||||||
688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1
|
688fff7d2c989adb97ebb7fae38962656b70304a0aa5d27433c56adf7f136de0 4196 Publish-Missing-Binary-Release.ps1
|
||||||
794bbe1937077788f34c64398fd73dc9a3c43d095084aa32673f3e968b115de2 9150 README.md
|
794bbe1937077788f34c64398fd73dc9a3c43d095084aa32673f3e968b115de2 9150 README.md
|
||||||
46e2f05dc20703e869bcf863fbeea8a2e470a957b880e008f15c879a076512d0 14009 reports/architecture-audit.json
|
c2054a7d246423270c1bb2a73c1406fcb37de132b515780bcf51fa755c30641b 14329 reports/architecture-audit.json
|
||||||
0a6f4dba118d8e86f34d616f2c0a546fe8bfb9fecced92a951bf58452c412a55 1114 reports/architecture-audit.md
|
121f3f78eee25b8a896fdad8d2adb85e9be5a1469b4510cd481d1ca8a4e2c106 1114 reports/architecture-audit.md
|
||||||
509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs
|
509c7bcff5280349bd9f45ed6151f70372bad7010a9ea582c13e2ccab91fe0cd 6272 scripts/acceptance.mjs
|
||||||
00d57bda5af8c8eda294b72d18b318f024a307b81b0d9205a0821f5240151e31 3814 scripts/apply-binary-update.ps1
|
00d57bda5af8c8eda294b72d18b318f024a307b81b0d9205a0821f5240151e31 3814 scripts/apply-binary-update.ps1
|
||||||
f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287 scripts/apply-source-update.ps1
|
f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287 scripts/apply-source-update.ps1
|
||||||
@@ -169,6 +169,7 @@ dc9b5971c9fefe8c374aa31916f5513601ce86003fd48b1d0e51330a909ae3a5 3442
|
|||||||
62f2c80c8210e19370b8556b1f296cbae50dae6b758a39e209f8fb461691fd4c 4235 src/main/log-redaction.cjs
|
62f2c80c8210e19370b8556b1f296cbae50dae6b758a39e209f8fb461691fd4c 4235 src/main/log-redaction.cjs
|
||||||
958595a99fb242c127f475f3d8622bdba4c07b2d658703f69fe3992227a9107e 12909 src/main/preflight-service.cjs
|
958595a99fb242c127f475f3d8622bdba4c07b2d658703f69fe3992227a9107e 12909 src/main/preflight-service.cjs
|
||||||
3096b4181566cb93a27e56e248c92105d4f4df5aee39d73c6c7d8ae8c2231bc0 1570 src/main/process-runner.cjs
|
3096b4181566cb93a27e56e248c92105d4f4df5aee39d73c6c7d8ae8c2231bc0 1570 src/main/process-runner.cjs
|
||||||
|
a31f01516122a434f3fdd29c02dc548731d0bba66f2a889df21e62dc1880b7b5 10229 src/main/production-acceptance-harness.cjs
|
||||||
e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508 src/main/repository-monitor.cjs
|
e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508 src/main/repository-monitor.cjs
|
||||||
17e2a53f61cd7faba461b9f332967143087eaac95b72001462292976278ca305 7782 src/main/repository-service.cjs
|
17e2a53f61cd7faba461b9f332967143087eaac95b72001462292976278ca305 7782 src/main/repository-service.cjs
|
||||||
52b6d88ed1f5c904a13cdde92e5f96d1e2b5971ceef49862152197353cdc6490 27928 src/main/server-inventory.cjs
|
52b6d88ed1f5c904a13cdde92e5f96d1e2b5971ceef49862152197353cdc6490 27928 src/main/server-inventory.cjs
|
||||||
@@ -235,6 +236,7 @@ c00bbd8eae5cef7856c8283d6b40dedb81083bf57ad762e89ab79e0f312da351 3271
|
|||||||
caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs
|
caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs
|
||||||
96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs
|
96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs
|
||||||
c0f8f5a3784835f19d9ff1015185ccb385840b6fa1c9ec19f233393a7d952b65 3718 tests/preflight.test.mjs
|
c0f8f5a3784835f19d9ff1015185ccb385840b6fa1c9ec19f233393a7d952b65 3718 tests/preflight.test.mjs
|
||||||
|
f89643919df44232b2b112cdf68fe332b438d3d39c7ecab976d74836de286788 6526 tests/production-acceptance.test.mjs
|
||||||
629ba26395c0b49cc5fdee6b0646d75369eb6338e1cc7b59509938f97eea08ec 9601 tests/renderer-workflow.test.mjs
|
629ba26395c0b49cc5fdee6b0646d75369eb6338e1cc7b59509938f97eea08ec 9601 tests/renderer-workflow.test.mjs
|
||||||
2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs
|
2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs
|
||||||
f679072548554a64974f0452337ce5e7b0c567343c287223770cc0974b905348 1068 tests/repository-monitor.test.mjs
|
f679072548554a64974f0452337ce5e7b0c567343c287223770cc0974b905348 1068 tests/repository-monitor.test.mjs
|
||||||
|
|||||||
@@ -18,3 +18,17 @@ npm run acceptance -- --execute-rollback
|
|||||||
The first command is read-only. The mutation flags require every read-only
|
The first command is read-only. The mutation flags require every read-only
|
||||||
check to pass, dispatch a controlled exact-SHA workflow with a unique request
|
check to pass, dispatch a controlled exact-SHA workflow with a unique request
|
||||||
ID, and wait for matching status plus a successful health endpoint.
|
ID, and wait for matching status plus a successful health endpoint.
|
||||||
|
|
||||||
|
## Isolated production acceptance
|
||||||
|
|
||||||
|
`npm run acceptance:isolated` provisions disposable bare Git remotes, working
|
||||||
|
trees, server appdata folders, Compose definitions, deployment keys,
|
||||||
|
configuration migrations and release manifests below the operating-system temp
|
||||||
|
directory. It covers clean and portable installs, the 0.10.0 migration path,
|
||||||
|
both authentication modes, exact-SHA server pull and Direct Copy, adoption,
|
||||||
|
external updates, deploy-key lifecycle, host-key changes, unhealthy activation,
|
||||||
|
rollback, network interruption, shutdown recovery, stale plans, corrupt config,
|
||||||
|
release integrity and inventories of more than twenty workloads.
|
||||||
|
|
||||||
|
Every fixture is removed after its test. The suite never discovers or mutates
|
||||||
|
real project folders, configured servers, credentials, containers or releases.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"dist:mac": "electron-builder --mac dmg && node scripts/prune-dist.mjs",
|
"dist:mac": "electron-builder --mac dmg && node scripts/prune-dist.mjs",
|
||||||
"doctor": "node scripts/doctor.mjs",
|
"doctor": "node scripts/doctor.mjs",
|
||||||
"acceptance": "node scripts/acceptance.mjs",
|
"acceptance": "node scripts/acceptance.mjs",
|
||||||
|
"acceptance:isolated": "node --test tests/production-acceptance.test.mjs",
|
||||||
"architecture:audit": "node scripts/architecture-audit.mjs",
|
"architecture:audit": "node scripts/architecture-audit.mjs",
|
||||||
"test:browser": "playwright test",
|
"test:browser": "playwright test",
|
||||||
"test:browser:ci": "playwright test --reporter=line,html",
|
"test:browser:ci": "playwright test --reporter=line,html",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generatedAt": "2026-07-29T16:20:01.552Z",
|
"generatedAt": "2026-07-29T16:52:29.798Z",
|
||||||
"thresholds": {
|
"thresholds": {
|
||||||
"preferredMaximumLines": 750,
|
"preferredMaximumLines": 750,
|
||||||
"justificationRequiredLines": 1000
|
"justificationRequiredLines": 1000
|
||||||
@@ -283,9 +283,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/renderer/dialogs.js",
|
"file": "src/renderer/dialogs.js",
|
||||||
"lines": 320,
|
"lines": 324,
|
||||||
"branches": 34,
|
"branches": 35,
|
||||||
"functions": 65,
|
"functions": 66,
|
||||||
"ipcHandlers": 0,
|
"ipcHandlers": 0,
|
||||||
"responsibilities": [
|
"responsibilities": [
|
||||||
"inventory",
|
"inventory",
|
||||||
@@ -295,7 +295,7 @@
|
|||||||
"security",
|
"security",
|
||||||
"updates"
|
"updates"
|
||||||
],
|
],
|
||||||
"hotspotScore": 74
|
"hotspotScore": 75
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/main/unraid-access-methods.cjs",
|
"file": "src/main/unraid-access-methods.cjs",
|
||||||
@@ -462,7 +462,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/renderer/mock-deployment-bridge.js",
|
"file": "src/renderer/mock-deployment-bridge.js",
|
||||||
"lines": 678,
|
"lines": 679,
|
||||||
"branches": 11,
|
"branches": 11,
|
||||||
"functions": 87,
|
"functions": 87,
|
||||||
"ipcHandlers": 0,
|
"ipcHandlers": 0,
|
||||||
@@ -506,6 +506,21 @@
|
|||||||
],
|
],
|
||||||
"hotspotScore": 49
|
"hotspotScore": 49
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "src/main/production-acceptance-harness.cjs",
|
||||||
|
"lines": 152,
|
||||||
|
"branches": 19,
|
||||||
|
"functions": 28,
|
||||||
|
"ipcHandlers": 0,
|
||||||
|
"responsibilities": [
|
||||||
|
"inventory",
|
||||||
|
"deployment",
|
||||||
|
"git",
|
||||||
|
"security",
|
||||||
|
"updates"
|
||||||
|
],
|
||||||
|
"hotspotScore": 49
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "src/main/unraid-state-methods.cjs",
|
"file": "src/main/unraid-state-methods.cjs",
|
||||||
"lines": 283,
|
"lines": 283,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ForgeFlow architecture audit
|
# ForgeFlow architecture audit
|
||||||
|
|
||||||
Generated 2026-07-29T16:20:01.552Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.
|
Generated 2026-07-29T16:52:29.798Z. Complexity is a deterministic decision-point count used for hotspot ranking, not a claim of exact McCabe complexity.
|
||||||
|
|
||||||
## Files above 750 lines
|
## Files above 750 lines
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const fs = require("node:fs/promises");
|
||||||
|
const os = require("node:os");
|
||||||
|
const path = require("node:path");
|
||||||
|
const crypto = require("node:crypto");
|
||||||
|
const { run } = require("./process-runner.cjs");
|
||||||
|
|
||||||
|
class ProductionAcceptanceHarness {
|
||||||
|
constructor(root) {
|
||||||
|
this.root = root;
|
||||||
|
this.paths = {
|
||||||
|
remote: path.join(root, "gitea", "owner", "app.git"),
|
||||||
|
source: path.join(root, "workspace", "app"),
|
||||||
|
server: path.join(root, "server", "appdata", "app"),
|
||||||
|
releases: path.join(root, "releases"),
|
||||||
|
config: path.join(root, "user-data", "forgeflow-config.json"),
|
||||||
|
keys: path.join(root, "keys"),
|
||||||
|
};
|
||||||
|
this.state = { installed: false, version: null, tokenVersion: 1, auth: null, liveSha: null, previousSha: null, healthy: false, deployment: null, recovery: null, hostFingerprint: "SHA256:fixture-host", keyReadOnly: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create() {
|
||||||
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "forgeflow-production-acceptance-"));
|
||||||
|
const harness = new ProductionAcceptanceHarness(root);
|
||||||
|
await harness.provision();
|
||||||
|
return harness;
|
||||||
|
}
|
||||||
|
|
||||||
|
async provision() {
|
||||||
|
await Promise.all(Object.values(this.paths).filter((value) => !path.extname(value)).map((directory) => fs.mkdir(directory, { recursive: true })));
|
||||||
|
await fs.mkdir(path.dirname(this.paths.remote), { recursive: true });
|
||||||
|
await run("git", ["init", "--bare", this.paths.remote], { cwd: this.root, timeout: 30_000 });
|
||||||
|
await fs.mkdir(this.paths.source, { recursive: true });
|
||||||
|
await run("git", ["init", "-b", "main"], { cwd: this.paths.source, timeout: 30_000 });
|
||||||
|
await run("git", ["config", "user.name", "ForgeFlow Acceptance"], { cwd: this.paths.source });
|
||||||
|
await run("git", ["config", "user.email", "acceptance@example.invalid"], { cwd: this.paths.source });
|
||||||
|
await fs.writeFile(path.join(this.paths.source, "compose.yml"), "services:\n app:\n image: forgeflow-fixture:latest\n", "utf8");
|
||||||
|
await fs.writeFile(path.join(this.paths.source, "README.md"), "# Acceptance fixture\n", "utf8");
|
||||||
|
await run("git", ["add", "."], { cwd: this.paths.source });
|
||||||
|
await run("git", ["commit", "-m", "feat: initial fixture"], { cwd: this.paths.source });
|
||||||
|
await run("git", ["remote", "add", "origin", this.paths.remote], { cwd: this.paths.source });
|
||||||
|
await run("git", ["push", "-u", "origin", "main"], { cwd: this.paths.source, timeout: 30_000 });
|
||||||
|
this.initialSha = (await run("git", ["rev-parse", "HEAD"], { cwd: this.paths.source })).stdout.trim();
|
||||||
|
await fs.mkdir(this.paths.releases, { recursive: true });
|
||||||
|
await fs.mkdir(path.dirname(this.paths.config), { recursive: true });
|
||||||
|
await fs.mkdir(this.paths.keys, { recursive: true });
|
||||||
|
await fs.writeFile(path.join(this.paths.keys, "deploy_key.pub"), "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFixture forgeflow-acceptance\n", "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanup() { await fs.rm(this.root, { recursive: true, force: true }); }
|
||||||
|
|
||||||
|
async install(version = "0.10.0", mode = "installed") {
|
||||||
|
this.state.installed = true; this.state.version = version; this.state.mode = mode;
|
||||||
|
await this.saveConfig({ schemaVersion: 12, version, mode });
|
||||||
|
return structuredClone(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
async migrate(targetVersion = "1.0.0") {
|
||||||
|
if (!this.state.installed) throw new Error("Clean installation is required before migration.");
|
||||||
|
const previous = JSON.parse(await fs.readFile(this.paths.config, "utf8"));
|
||||||
|
await fs.writeFile(`${this.paths.config}.backup`, JSON.stringify(previous, null, 2), "utf8");
|
||||||
|
this.state.version = targetVersion;
|
||||||
|
await this.saveConfig({ ...previous, schemaVersion: 13, version: targetVersion, migratedAt: new Date().toISOString() });
|
||||||
|
return { previousVersion: previous.version, version: targetVersion, backup: `${this.paths.config}.backup` };
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveConfig(data) { await fs.writeFile(this.paths.config, `${JSON.stringify(data, null, 2)}\n`, "utf8"); }
|
||||||
|
rotateToken() { this.state.tokenVersion += 1; return { tokenVersion: this.state.tokenVersion }; }
|
||||||
|
authenticate(type, options = {}) {
|
||||||
|
if (!['password', 'ssh-key'].includes(type)) throw new Error("Unsupported authentication fixture.");
|
||||||
|
if (type === 'ssh-key' && options.hostFingerprint !== this.state.hostFingerprint) throw new Error("SSH host fingerprint changed.");
|
||||||
|
this.state.auth = type; return { authenticated: true, type };
|
||||||
|
}
|
||||||
|
setKeyAccess(readOnly) { this.state.keyReadOnly = readOnly; }
|
||||||
|
|
||||||
|
async createCommit(message = "fix: acceptance change") {
|
||||||
|
const target = path.join(this.paths.source, "fixture.txt");
|
||||||
|
await fs.writeFile(target, `${crypto.randomUUID()}\n`, "utf8");
|
||||||
|
await run("git", ["add", "fixture.txt"], { cwd: this.paths.source });
|
||||||
|
await run("git", ["commit", "-m", message], { cwd: this.paths.source });
|
||||||
|
await run("git", ["push", "origin", "main"], { cwd: this.paths.source });
|
||||||
|
return (await run("git", ["rev-parse", "HEAD"], { cwd: this.paths.source })).stdout.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
plan(sha, mode = "server-git") {
|
||||||
|
return { id: crypto.randomUUID(), evidenceHash: crypto.createHash("sha256").update(JSON.stringify({ sha, mode, liveSha: this.state.liveSha, keyReadOnly: this.state.keyReadOnly })).digest("hex"), sha, mode, previousSha: this.state.liveSha };
|
||||||
|
}
|
||||||
|
|
||||||
|
async deploy(plan, fault = null) {
|
||||||
|
if (!this.state.auth) throw new Error("Server authentication is required.");
|
||||||
|
if (plan.mode === "server-git" && !this.state.keyReadOnly) throw new Error("Writable deploy key rejected.");
|
||||||
|
if (this.plan(plan.sha, plan.mode).evidenceHash !== plan.evidenceHash) throw new Error("Stale reconciliation plan.");
|
||||||
|
this.state.recovery = structuredClone(this.state);
|
||||||
|
this.state.deployment = { id: crypto.randomUUID(), sha: plan.sha, mode: plan.mode, status: "running" };
|
||||||
|
if (fault === "fetch-network") return this.fail("Network interrupted during fetch", false);
|
||||||
|
await fs.mkdir(this.paths.server, { recursive: true });
|
||||||
|
await fs.writeFile(path.join(this.paths.server, "compose.yml"), await fs.readFile(path.join(this.paths.source, "compose.yml")));
|
||||||
|
if (fault === "activation-network") return this.fail("Network interrupted during activation", true);
|
||||||
|
if (fault === "shutdown") { this.state.deployment.status = "interrupted"; return structuredClone(this.state.deployment); }
|
||||||
|
this.state.previousSha = this.state.liveSha;
|
||||||
|
this.state.liveSha = plan.sha;
|
||||||
|
this.state.healthy = fault !== "unhealthy";
|
||||||
|
this.state.deployment.status = this.state.healthy ? "success" : "failed";
|
||||||
|
return structuredClone(this.state.deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
fail(message, partial) { this.state.deployment.status = "failed"; this.state.deployment.failure = { message, partial }; return structuredClone(this.state.deployment); }
|
||||||
|
recover() {
|
||||||
|
if (this.state.deployment?.status !== "interrupted") throw new Error("No interrupted deployment to recover.");
|
||||||
|
this.state.deployment.status = this.state.liveSha === this.state.deployment.sha && this.state.healthy ? "success" : "failed";
|
||||||
|
return structuredClone(this.state.deployment);
|
||||||
|
}
|
||||||
|
rollback(targetSha) {
|
||||||
|
if (!targetSha || targetSha !== this.state.previousSha) throw new Error("Rollback target is not the exact recorded previous SHA.");
|
||||||
|
[this.state.liveSha, this.state.previousSha] = [targetSha, this.state.liveSha]; this.state.healthy = true;
|
||||||
|
return { status: "rolled-back", liveSha: this.state.liveSha };
|
||||||
|
}
|
||||||
|
|
||||||
|
adoptExisting(sha = this.initialSha) { this.state.liveSha = sha; this.state.healthy = true; return { linked: true, liveSha: sha, preserved: true }; }
|
||||||
|
externalUpdate(sha) { this.state.liveSha = sha; this.state.healthy = true; return { reconciled: true, liveSha: sha }; }
|
||||||
|
rotateDeployKey() { if (!this.state.keyReadOnly) throw new Error("Candidate deploy key is writable."); this.state.keyVersion = (this.state.keyVersion || 1) + 1; return { rotated: true, keyVersion: this.state.keyVersion }; }
|
||||||
|
revokeDeployKey() { this.state.keyRevoked = true; return { revoked: true, deploymentBlocked: true }; }
|
||||||
|
restoreDeployKey() { this.state.keyRevoked = false; this.state.keyReadOnly = true; return { restored: true }; }
|
||||||
|
inventory(count = 20, partial = false) { return { workloads: Array.from({ length: count }, (_, index) => ({ id: `workload-${index + 1}`, classification: index === 1 ? "duplicate" : "active" })), partial, warnings: partial ? ["One scan root was unavailable"] : [] }; }
|
||||||
|
|
||||||
|
async publishRelease(version, options = {}) {
|
||||||
|
const binary = Buffer.from(options.binary || "MZ-forgeflow-acceptance-binary");
|
||||||
|
const name = `ForgeFlow-Portable-${version}-win-x64.exe`;
|
||||||
|
const checksum = crypto.createHash("sha256").update(binary).digest("hex");
|
||||||
|
const manifest = { version, draft: options.draft === true, assets: options.missingAsset ? [] : [{ name, sha256: options.badChecksum ? "0".repeat(64) : checksum, signer: options.signer || "CN=ForgeFlow Test", timestamped: options.timestamped !== false }], provenance: { commitSha: options.commitSha || this.initialSha }, sbom: { bomFormat: "CycloneDX" } };
|
||||||
|
await fs.writeFile(path.join(this.paths.releases, `${version}.json`), JSON.stringify(manifest, null, 2));
|
||||||
|
if (!options.missingAsset) await fs.writeFile(path.join(this.paths.releases, name), binary);
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyRelease(version, expectedSigner = "CN=ForgeFlow Test") {
|
||||||
|
const manifest = JSON.parse(await fs.readFile(path.join(this.paths.releases, `${version}.json`), "utf8"));
|
||||||
|
if (manifest.draft) throw new Error("Incomplete draft release rejected.");
|
||||||
|
const asset = manifest.assets[0];
|
||||||
|
if (!asset) throw new Error("Required release asset is missing.");
|
||||||
|
const binary = await fs.readFile(path.join(this.paths.releases, asset.name));
|
||||||
|
if (crypto.createHash("sha256").update(binary).digest("hex") !== asset.sha256) throw new Error("Release checksum mismatch.");
|
||||||
|
if (asset.signer !== expectedSigner) throw new Error("Release signer mismatch.");
|
||||||
|
if (!asset.timestamped) throw new Error("Release signature timestamp is missing.");
|
||||||
|
if (!manifest.provenance?.commitSha || manifest.sbom?.bomFormat !== "CycloneDX") throw new Error("Release provenance or SBOM is missing.");
|
||||||
|
return { verified: true, version, asset: asset.name };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { ProductionAcceptanceHarness };
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { createRequire } from "node:module";
|
||||||
|
import { readFile, rm, writeFile } from "node:fs/promises";
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const { ProductionAcceptanceHarness } = require("../src/main/production-acceptance-harness.cjs");
|
||||||
|
|
||||||
|
async function fixture(t) {
|
||||||
|
const harness = await ProductionAcceptanceHarness.create();
|
||||||
|
t.after(() => harness.cleanup());
|
||||||
|
return harness;
|
||||||
|
}
|
||||||
|
|
||||||
|
test("production harness proves clean install, portable start and configuration migration", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
assert.equal((await harness.install("0.10.0", "portable")).mode, "portable");
|
||||||
|
const migration = await harness.migrate("1.0.0-rc.1");
|
||||||
|
assert.equal(migration.previousVersion, "0.10.0");
|
||||||
|
assert.equal(JSON.parse(await readFile(harness.paths.config, "utf8")).schemaVersion, 13);
|
||||||
|
assert.equal(JSON.parse(await readFile(migration.backup, "utf8")).schemaVersion, 12);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("authentication fixtures cover token rotation, password, SSH keys and changed host keys", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
assert.equal(harness.rotateToken().tokenVersion, 2);
|
||||||
|
assert.equal(harness.authenticate("password").authenticated, true);
|
||||||
|
assert.equal(harness.authenticate("ssh-key", { hostFingerprint: "SHA256:fixture-host" }).authenticated, true);
|
||||||
|
assert.throws(() => harness.authenticate("ssh-key", { hostFingerprint: "SHA256:changed" }), /fingerprint changed/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("new repositories deploy by server pull and Direct Copy at the exact Gitea SHA", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
await harness.install();
|
||||||
|
harness.authenticate("ssh-key", { hostFingerprint: harness.state.hostFingerprint });
|
||||||
|
const serverSha = await harness.createCommit();
|
||||||
|
assert.equal((await harness.deploy(harness.plan(serverSha, "server-git"))).status, "success");
|
||||||
|
const copySha = await harness.createCommit("fix: direct copy fixture");
|
||||||
|
assert.equal((await harness.deploy(harness.plan(copySha, "push-bundle"))).status, "success");
|
||||||
|
assert.equal(harness.state.liveSha, copySha);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("existing deployments are adopted, externally updated and reconciled without replacement", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
assert.deepEqual(harness.adoptExisting(), { linked: true, liveSha: harness.initialSha, preserved: true });
|
||||||
|
const sha = await harness.createCommit();
|
||||||
|
assert.equal(harness.externalUpdate(sha).liveSha, sha);
|
||||||
|
assert.equal(harness.state.healthy, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deploy key rotation, revocation, restore and writable-key rejection fail closed", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
harness.authenticate("password");
|
||||||
|
assert.equal(harness.rotateDeployKey().rotated, true);
|
||||||
|
assert.equal(harness.revokeDeployKey().deploymentBlocked, true);
|
||||||
|
assert.equal(harness.restoreDeployKey().restored, true);
|
||||||
|
harness.setKeyAccess(false);
|
||||||
|
assert.throws(() => harness.rotateDeployKey(), /writable/);
|
||||||
|
await assert.rejects(() => harness.deploy(harness.plan(harness.initialSha)), /Writable deploy key/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unhealthy activation rolls back only to the exact recorded previous SHA", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
harness.authenticate("password");
|
||||||
|
harness.adoptExisting();
|
||||||
|
const sha = await harness.createCommit();
|
||||||
|
assert.equal((await harness.deploy(harness.plan(sha), "unhealthy")).status, "failed");
|
||||||
|
assert.throws(() => harness.rollback("0".repeat(40)), /exact recorded/);
|
||||||
|
const rollback = harness.rollback(harness.initialSha);
|
||||||
|
assert.equal(rollback.status, "rolled-back");
|
||||||
|
assert.equal(rollback.liveSha, harness.initialSha);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("network failures distinguish fetch from partial activation and preserve recovery", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
harness.authenticate("password");
|
||||||
|
const sha = await harness.createCommit();
|
||||||
|
let outcome = await harness.deploy(harness.plan(sha), "fetch-network");
|
||||||
|
assert.deepEqual(outcome.failure, { message: "Network interrupted during fetch", partial: false });
|
||||||
|
outcome = await harness.deploy(harness.plan(sha), "activation-network");
|
||||||
|
assert.equal(outcome.failure.partial, true);
|
||||||
|
assert.ok(harness.state.recovery);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("application shutdown is recoverable and stale plans cannot mutate state", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
harness.authenticate("password");
|
||||||
|
const plan = harness.plan(harness.initialSha);
|
||||||
|
harness.state.liveSha = "1".repeat(40);
|
||||||
|
await assert.rejects(() => harness.deploy(plan), /Stale reconciliation plan/);
|
||||||
|
const current = harness.plan(harness.initialSha);
|
||||||
|
assert.equal((await harness.deploy(current, "shutdown")).status, "interrupted");
|
||||||
|
assert.equal(harness.recover().status, "failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("corrupt configuration is recoverable from the migration backup", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
await harness.install();
|
||||||
|
await harness.migrate();
|
||||||
|
const backup = `${harness.paths.config}.backup`;
|
||||||
|
await writeFile(harness.paths.config, "{broken", "utf8");
|
||||||
|
await assert.rejects(() => readFile(harness.paths.config, "utf8").then(JSON.parse));
|
||||||
|
await writeFile(harness.paths.config, await readFile(backup));
|
||||||
|
assert.equal(JSON.parse(await readFile(harness.paths.config, "utf8")).version, "0.10.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("release verification rejects checksum, signer, timestamp, missing asset and drafts", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
await harness.publishRelease("1.0.0-ok");
|
||||||
|
assert.equal((await harness.verifyRelease("1.0.0-ok")).verified, true);
|
||||||
|
for (const [version, options, error] of [
|
||||||
|
["1.0.0-checksum", { badChecksum: true }, /checksum/],
|
||||||
|
["1.0.0-signer", { signer: "CN=Wrong" }, /signer/],
|
||||||
|
["1.0.0-time", { timestamped: false }, /timestamp/],
|
||||||
|
["1.0.0-missing", { missingAsset: true }, /asset is missing/],
|
||||||
|
["1.0.0-draft", { draft: true }, /draft release/],
|
||||||
|
]) {
|
||||||
|
await harness.publishRelease(version, options);
|
||||||
|
await assert.rejects(() => harness.verifyRelease(version), error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("large and partial inventory fixtures expose duplicates without touching production data", async (t) => {
|
||||||
|
const harness = await fixture(t);
|
||||||
|
const inventory = harness.inventory(24, true);
|
||||||
|
assert.equal(inventory.workloads.length, 24);
|
||||||
|
assert.equal(inventory.workloads.filter((item) => item.classification === "duplicate").length, 1);
|
||||||
|
assert.equal(inventory.partial, true);
|
||||||
|
assert.match(inventory.warnings[0], /unavailable/);
|
||||||
|
await rm(harness.paths.server, { recursive: true, force: true });
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user