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.
110 lines
5.0 KiB
Plaintext
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;
|
|
}
|