Files
distribution/tests/validate_artifact_file.sx
agra 6c19f1073f lang migration: rename signed integer types sN -> iN
Mechanical sweep of all .sx sources and plan docs (PLAN.md, current/,
.agents/) for the sx language rename (s8/s16/s32/s64 -> i8/i16/i32/i64).
Verified: make build + make test, 14/14.
2026-06-12 09:39:49 +03:00

110 lines
5.0 KiB
Plaintext

// Acceptance for P3.3 — the platform-agnostic ("common") artifact
// validation pass (subplan 05 "Common Validation" subset).
//
// Runs validate_artifact_file against the on-disk fixtures under
// examples/fixtures/ and asserts:
// 1. a GOOD artifact (correct size + sha256 + content type + extension
// matching the platform) -> status .valid, reason .ok — for BOTH the
// .apk/android_apk and .ipa/ios fixtures (cross-checks the ext map).
// 2. each tampered case -> status .invalid with the SPECIFIC reason:
// - wrong declared size -> .size_mismatch
// - wrong declared sha256 -> .digest_mismatch
// - a .apk file declared as `ios` -> .extension_mismatch
// - a disallowed content type -> .content_type_denied
// - a path with no file -> .missing_file
//
// The expected sha256 values are the real digests of the fixtures,
// cross-checked against `shasum -a 256` and recomputed here by std.hash
// inside validate_artifact_file. Prints PASS/FAIL per case; exits non-zero
// if any case behaves unexpectedly so the runner counts it as a failure.
#import "modules/std.sx";
#import "../src/domain/platform.sx";
#import "../src/domain/artifact.sx";
#import "../src/validation/artifact_file.sx";
APK_PATH :: "examples/fixtures/acme-1.2.3-android.apk";
IPA_PATH :: "examples/fixtures/acme-1.2.3-ios.ipa";
APK_SHA :: "55a008aa634d45313ef0a758624e0d2a356c156e507f28a2c60d19d38893af09";
IPA_SHA :: "1581f47098fd83277fa07c3f234ec92d9ce456804738278ede4e76f7f7bced84";
APK_SIZE :: 5;
IPA_SIZE :: 5;
APK_CT :: "application/vnd.android.package-archive";
IPA_CT :: "application/octet-stream";
ZERO_SHA :: "0000000000000000000000000000000000000000000000000000000000000000";
passed :: (o: ValidationOutcome) -> bool {
return o.status == .valid and o.reason == .ok;
}
failed_with :: (o: ValidationOutcome, r: ValidationReason) -> bool {
return o.status == .invalid and o.reason == r;
}
// ── good fixtures -> .valid / .ok ───────────────────────────────────────
check_good_apk :: () -> bool {
o := validate_artifact_file(APK_PATH, APK_SIZE, APK_SHA, .android_apk, APK_CT);
return passed(o);
}
check_good_ipa :: () -> bool {
o := validate_artifact_file(IPA_PATH, IPA_SIZE, IPA_SHA, .ios, IPA_CT);
return passed(o);
}
// ── tampered: wrong declared size -> .size_mismatch ─────────────────────
check_size_mismatch :: () -> bool {
o := validate_artifact_file(APK_PATH, 999, APK_SHA, .android_apk, APK_CT);
return failed_with(o, .size_mismatch);
}
// ── tampered: wrong declared sha256 -> .digest_mismatch ─────────────────
check_digest_mismatch :: () -> bool {
o := validate_artifact_file(APK_PATH, APK_SIZE, ZERO_SHA, .android_apk, APK_CT);
return failed_with(o, .digest_mismatch);
}
// ── tampered: .apk declared as `ios` -> .extension_mismatch ─────────────
// Size, digest, and content type all pass; only the extension/platform
// policy rejects it.
check_extension_mismatch :: () -> bool {
o := validate_artifact_file(APK_PATH, APK_SIZE, APK_SHA, .ios, APK_CT);
return failed_with(o, .extension_mismatch);
}
// ── tampered: disallowed content type -> .content_type_denied ───────────
check_content_type_denied :: () -> bool {
o := validate_artifact_file(APK_PATH, APK_SIZE, APK_SHA, .android_apk, "text/plain");
return failed_with(o, .content_type_denied);
}
// ── tampered: no file at path -> .missing_file ──────────────────────────
check_missing_file :: () -> bool {
o := validate_artifact_file("examples/fixtures/does-not-exist.apk", APK_SIZE, APK_SHA, .android_apk, APK_CT);
return failed_with(o, .missing_file);
}
run_case :: (label: string, ok: bool) -> i32 {
if ok { print(" PASS {}\n", label); return 0; }
print(" FAIL {}\n", label);
return 1;
}
main :: () -> i32 {
failures : i32 = 0;
failures += run_case("good .apk/android_apk -> valid/ok", check_good_apk());
failures += run_case("good .ipa/ios -> valid/ok", check_good_ipa());
failures += run_case("wrong size -> size_mismatch", check_size_mismatch());
failures += run_case("wrong sha256 -> digest_mismatch", check_digest_mismatch());
failures += run_case(".apk declared as ios -> extension_mismatch", check_extension_mismatch());
failures += run_case("disallowed content type -> content_type_denied", check_content_type_denied());
failures += run_case("missing file -> missing_file", check_missing_file());
print("------------------------------------------------\n");
if failures == 0 {
print("validate_artifact_file: ALL CASES PASS\n");
return 0;
}
print("validate_artifact_file: {} CASE(S) FAILED\n", failures);
return 1;
}