From 4cebdc4b708ce22782578d50d2aea838b80ffd36 Mon Sep 17 00:00:00 2001 From: grokbot_ty Date: Mon, 7 Sep 2026 19:49:59 +0000 Subject: [PATCH] =?UTF-8?q?results(gate-b):=20pass4=20evidence=20=E2=80=94?= =?UTF-8?q?=2024=20rows,=2020=20pass=20/=200=20fail=20/=204=20blocked;=20T?= =?UTF-8?q?V-3/6/7/10=20product-state=20blocked;=20UI-BVT-1=20Select=20Pho?= =?UTF-8?q?tos=20e2e;=20Gate=20B=20not=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gate-b-2026-09-07-pass4/RESULTS.md | 36 ++++++ .../health-body-https.json | 1 + .../gate-b-2026-09-07-pass4/health.txt | 15 +++ .../playwright-bvt.txt | 10 ++ .../gate-b-2026-09-07-pass4/pytest.txt | 60 ++++++++++ .../gate-b-2026-09-07-pass4/started.txt | 2 + .../gate-b-2026-09-07-pass4/vitest.txt | 113 ++++++++++++++++++ 7 files changed, 237 insertions(+) create mode 100644 test-results/gate-b-2026-09-07-pass4/RESULTS.md create mode 100644 test-results/gate-b-2026-09-07-pass4/health-body-https.json create mode 100644 test-results/gate-b-2026-09-07-pass4/health.txt create mode 100644 test-results/gate-b-2026-09-07-pass4/playwright-bvt.txt create mode 100644 test-results/gate-b-2026-09-07-pass4/pytest.txt create mode 100644 test-results/gate-b-2026-09-07-pass4/started.txt create mode 100644 test-results/gate-b-2026-09-07-pass4/vitest.txt diff --git a/test-results/gate-b-2026-09-07-pass4/RESULTS.md b/test-results/gate-b-2026-09-07-pass4/RESULTS.md new file mode 100644 index 0000000..cba1ee7 --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/RESULTS.md @@ -0,0 +1,36 @@ +# Gate B Results — 2026-09-07 pass 4 + +Evidence on **gate-b** @ `18a453a`. + +| Case | What this proves (mill-owner) | Evidence class | Result | Evidence | Blocker | +|---|---|---|---|---|---| +| TV-1 | Green-sheet mask: green interior patch stays, border pixels removed, U2Net not invoked | proxy-synthetic | pass | vitest.txt:4 | | +| TV-2 | Black-sheet mask: dark wood interior stays, flood-fill isolates border-connected pixels only | proxy-synthetic | pass | vitest.txt:4 | | +| TV-3 | bdft = sqft × thickness_in; identity 1 sqft @ 1" = 1 bdft; no divide-by-12 | product-state | blocked | No PNG under `fixtures/real/`; vitest skip :6 | | +| TV-3 | bdft identity computed correctly by pure module | proxy-synthetic | pass | vitest.txt:74-75,80 | | +| TV-4 | Width samples at 6" stations report min/max/average perpendicular to confirmed axis | proxy-synthetic | pass | vitest.txt:7-8,14 | | +| TV-5 | Angled-end slab: length axis follows prevailing length; widths stay perpendicular after confirm | proxy-synthetic | pass | vitest.txt:10-11,14 | | +| TV-6 | 3:4 crop, 80% fill, transparent PNG, shorter side ≥1600 when source allows | product-state | blocked | No PNG under `fixtures/real/`; vitest skip :5 | | +| TV-6 | 3:4 crop, 80% fill, ≥1600px computed correctly by pure module | proxy-synthetic | pass | vitest.txt:84-105 | | +| TV-7 | Undersized source warns, refuses upscale, photo not publishable until retake | product-state | blocked | No PNG under `fixtures/real/`; vitest skip :13 | | +| TV-7 | Undersized detection and no-upscale logic correct | proxy-synthetic | pass | vitest.txt:96-106 | | +| TV-8 | Pricing: bdft × price_per_bdft; override sticks; missing rule leaves rec empty | proxy-synthetic | pass | vitest.txt:83 | | +| TV-9 | Thickness 1" → Woo range rounds up using synced attribute term | proxy-synthetic | pass | vitest.txt:82 | | +| TV-10 | Rounding: 96.04→96", 0.641→0.64 sqft, 11.525→11.53 bdft, 12.345→12.35 price; half-up 2 decimals | product-state | blocked | No PNG under `fixtures/real/`; vitest skip :81 | | +| TV-10 | Rounding logic correct | proxy-synthetic | pass | vitest.txt:77-78,80 | | +| V-1 | SKU charset `[A-Z0-9-]{3,24}` accepted; invalid values rejected with errors | proxy-synthetic | pass | vitest.txt:15-44,73 | | +| V-2 | Title/short_title/description maxLength enforced at field exit and submit | proxy-synthetic | pass | vitest.txt:46-62,73 | | +| V-3 | Validation module hash: client caches; mismatch returns 412 with fresh module | proxy-synthetic | pass | vitest.txt:64-71,73 | | +| AES-1 | Encrypt-decrypt round-trip; ciphertext never in GET settings; password never returned | api-contract | pass | pytest.txt:46 (settings never returns app_password) | | +| AES-1 | Encrypt-decrypt round-trip; ciphertext ≠ plaintext; fresh nonce each call | proxy-synthetic | pass | pytest.txt:9-17 (8 roundtrip tests) | | +| AES-2 | Missing SLAB_AES_KEY refuses startup; no plaintext fallback | proxy-synthetic | pass | pytest.txt:23-24 (missing_key_refuses, too_short_refuses) | | +| H-1 | GET /api/health returns 200 `{ "status":"ok" }` with woo_reachable false | api-contract | pass | pytest.txt:27-31 (health: 200, ok status, woo_reachable false, shape, lifespan) | | +| D-1 | POST /api/v1/slabs accepts multipart data JSON + files; returns 201 calibrated with server_rev | api-contract | pass | pytest.txt:32-39 (8 TestCreateSlab: 201, calibrated, server_rev, shape, multiple photos, missing data 400, duplicate 409, lifespan) | | +| OPS-1 | GET https://${APP_HOSTNAME}/api/health returns 200 after compose up | deploy | pass | health.txt:2-13 (HTTP/2 200, status ok, woo_reachable false). Self-signed cert (curl -k); phones need CA. UI at / returns 200 text/html. | | +| UI-BVT-1 | Select Photos label has for/id, file selection advances to mask step, Process Photos button enabled, knobs adjustable | e2e | pass | playwright-bvt.txt:4-9 (4 passed: label for/id, file→mask, button enabled, full flow knobs) | | + +**Counts:** pass = 20, blocked = 4, fail = 0. Total rows = 24. + +**Gate B is not green.** TV-3, TV-6, TV-7, TV-10 product-state rows are blocked (no real fixtures under `fixtures/real/`). All proxy-synthetic companions passed. OPS-1 deploy pass confirmed on `https://100.64.0.9` but self-signed. UI-BVT-1 is a Select Photos click-path BVT only, not a full capture→publish human-walk. + +**UNPROVEN:** TV-3/6/7/10 product-state (no real PNGs); full UAT/human-walk; other UI flows beyond Select Photos BVT. diff --git a/test-results/gate-b-2026-09-07-pass4/health-body-https.json b/test-results/gate-b-2026-09-07-pass4/health-body-https.json new file mode 100644 index 0000000..a0020c2 --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/health-body-https.json @@ -0,0 +1 @@ +{"status":"ok","version":"0.1.0","woo_reachable":false} \ No newline at end of file diff --git a/test-results/gate-b-2026-09-07-pass4/health.txt b/test-results/gate-b-2026-09-07-pass4/health.txt new file mode 100644 index 0000000..ad1903b --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/health.txt @@ -0,0 +1,15 @@ +Mon Sep 7 19:47:49 UTC 2026 +=== HTTPS https://100.64.0.9/api/health (curl -k) === +HTTP/2 200 +alt-svc: h3=":443"; ma=2592000 +content-type: application/json +date: Mon, 07 Sep 2026 19:47:49 GMT +server: Caddy +server: uvicorn +content-length: 55 + + +BODY: +{"status":"ok","version":"0.1.0","woo_reachable":false} +=== HTTPS UI / === +HTTP_CODE:200 CT:text/html diff --git a/test-results/gate-b-2026-09-07-pass4/playwright-bvt.txt b/test-results/gate-b-2026-09-07-pass4/playwright-bvt.txt new file mode 100644 index 0000000..d772b67 --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/playwright-bvt.txt @@ -0,0 +1,10 @@ + +Running 4 tests using 1 worker + + ✓ 1 [chromium] › test/bvt/select-photos.spec.ts:9:3 › Select Photos BVT › label "Select Photos" has for attribute pointing to the file input id (276ms) + ✓ 2 [chromium] › test/bvt/select-photos.spec.ts:21:3 › Select Photos BVT › file selection advances to mask step (262ms) + ✓ 3 [chromium] › test/bvt/select-photos.spec.ts:38:3 › Select Photos BVT › Process Photos button is enabled after file selection (221ms) + ✓ 4 [chromium] › test/bvt/select-photos.spec.ts:50:3 › Select Photos BVT › full flow: select photo → mask step appears → knobs can be adjusted (233ms) + + 4 passed (2.9s) +EXIT:0 diff --git a/test-results/gate-b-2026-09-07-pass4/pytest.txt b/test-results/gate-b-2026-09-07-pass4/pytest.txt new file mode 100644 index 0000000..e429c3f --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/pytest.txt @@ -0,0 +1,60 @@ +============================= test session starts ============================== +platform linux -- Python 3.13.5, pytest-8.4.2, pluggy-1.6.0 -- /workspace/gitea-atd/Ty_Tech/SlabUploader/backend/.venv/bin/python3 +cachedir: .pytest_cache +rootdir: /workspace/gitea-atd/Ty_Tech/SlabUploader/backend +configfile: pyproject.toml +plugins: anyio-4.15.1, respx-0.23.1 +collecting ... collected 39 items + +tests/unit/test_aes.py::TestAESRoundTrip::test_roundtrip_username PASSED [ 2%] +tests/unit/test_aes.py::TestAESRoundTrip::test_roundtrip_password PASSED [ 5%] +tests/unit/test_aes.py::TestAESRoundTrip::test_roundtrip_empty_string PASSED [ 7%] +tests/unit/test_aes.py::TestAESRoundTrip::test_roundtrip_unicode PASSED [ 10%] +tests/unit/test_aes.py::TestAESRoundTrip::test_roundtrip_special_chars PASSED [ 12%] +tests/unit/test_aes.py::TestAESRoundTrip::test_ciphertext_is_bytes PASSED [ 15%] +tests/unit/test_aes.py::TestAESRoundTrip::test_ciphertext_starts_with_nonce PASSED [ 17%] +tests/unit/test_aes.py::TestAESRoundTrip::test_ciphertext_different_from_plaintext PASSED [ 20%] +tests/unit/test_aes.py::TestAESRoundTrip::test_ciphertext_changes_each_call PASSED [ 23%] +tests/unit/test_aes.py::TestFreshNonce::test_nonce_differs_across_encryptions PASSED [ 25%] +tests/unit/test_aes.py::TestFreshNonce::test_multiple_encryptions_all_unique_nonces PASSED [ 28%] +tests/unit/test_aes.py::TestTamperedCiphertext::test_tampered_body_raises_authentication_error PASSED [ 30%] +tests/unit/test_aes.py::TestTamperedCiphertext::test_truncated_ciphertext_raises_error PASSED [ 33%] +tests/unit/test_aes.py::TestTamperedCiphertext::test_wrong_key_raises_error PASSED [ 35%] +tests/unit/test_aes.py::TestMissingKey::test_missing_key_refuses_startup PASSED [ 38%] +tests/unit/test_aes.py::TestMissingKey::test_too_short_key_refuses_startup PASSED [ 41%] +tests/unit/test_aes.py::TestMissingKey::test_get_aes_key_returns_bytes PASSED [ 43%] +tests/unit/test_aes.py::TestNoPlaintextFallback::test_decrypt_returns_decrypted_value PASSED [ 46%] +tests/contract/test_health.py::TestHealthEndpoint::test_returns_200 PASSED [ 48%] +tests/contract/test_health.py::TestHealthEndpoint::test_returns_ok_status PASSED [ 51%] +tests/contract/test_health.py::TestHealthEndpoint::test_woo_reachable_false_when_credentials_not_configured PASSED [ 53%] +tests/contract/test_health.py::TestHealthEndpoint::test_response_shape PASSED [ 56%] +tests/contract/test_health.py::TestHealthEndpoint::test_with_test_client_lifespan PASSED [ 58%] +tests/contract/test_slabs.py::TestCreateSlab::test_returns_201 PASSED [ 61%] +tests/contract/test_slabs.py::TestCreateSlab::test_returns_status_calibrated PASSED [ 64%] +tests/contract/test_slabs.py::TestCreateSlab::test_returns_server_rev PASSED [ 66%] +tests/contract/test_slabs.py::TestCreateSlab::test_response_shape PASSED [ 69%] +tests/contract/test_slabs.py::TestCreateSlab::test_multiple_photos PASSED [ 71%] +tests/contract/test_slabs.py::TestCreateSlab::test_missing_data_field_returns_400 PASSED [ 74%] +tests/contract/test_slabs.py::TestCreateSlab::test_duplicate_sku_returns_409 PASSED [ 76%] +tests/contract/test_slabs.py::TestCreateSlab::test_with_test_client_lifespan PASSED [ 79%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlValidation::test_refuses_http_url PASSED [ 82%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlValidation::test_refuses_missing_url PASSED [ 84%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlValidation::test_accepts_https_url PASSED [ 87%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlReadOnly::test_get_settings_returns_woo_base_url PASSED [ 89%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlReadOnly::test_put_settings_rejects_woo_base_url PASSED [ 92%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlReadOnly::test_put_settings_allows_other_fields PASSED [ 94%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlReadOnly::test_get_settings_never_returns_app_password PASSED [ 97%] +tests/contract/test_woo_base_url.py::TestWooBaseUrlReadOnly::test_put_settings_rejects_consumer_keys PASSED [100%] + +=============================== warnings summary =============================== +.venv/lib/python3.13/site-packages/fastapi/testclient.py:1 + /workspace/gitea-atd/Ty_Tech/SlabUploader/backend/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +.venv/lib/python3.13/site-packages/starlette/testclient.py:53 + /workspace/gitea-atd/Ty_Tech/SlabUploader/backend/.venv/lib/python3.13/site-packages/starlette/testclient.py:53: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead. + _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]] + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +======================== 39 passed, 2 warnings in 0.45s ======================== +EXIT:0 diff --git a/test-results/gate-b-2026-09-07-pass4/started.txt b/test-results/gate-b-2026-09-07-pass4/started.txt new file mode 100644 index 0000000..daebf6a --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/started.txt @@ -0,0 +1,2 @@ +Mon Sep 7 19:47:23 UTC 2026 +18a453a diff --git a/test-results/gate-b-2026-09-07-pass4/vitest.txt b/test-results/gate-b-2026-09-07-pass4/vitest.txt new file mode 100644 index 0000000..23064e2 --- /dev/null +++ b/test-results/gate-b-2026-09-07-pass4/vitest.txt @@ -0,0 +1,113 @@ + + RUN v2.1.9 /workspace/gitea-atd/Ty_Tech/SlabUploader/frontend + + ✓ src/__tests__/tv1-tv2-mask.test.ts (5 tests) 41ms + ↓ src/__tests__/tv6-real.test.ts (1 test | 1 skipped) + ↓ src/__tests__/tv3-real.test.ts (1 test | 1 skipped) +stdout | src/__tests__/tv4-tv5-axis.test.ts > TV-4 Width samples at 6" stations > reports min, max, average perpendicular to confirmed axis on 96" length +{"case":"TV-4","lengthPx":96,"widthPx":24,"scale":1,"stations":[6,12,18,24,30,36,42,48,54,60,66,72,78,84,90],"widths":[23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684],"min":23.999999046325684,"max":23.999999046325684,"average":23.999999046325684,"expectedWidthIn":24} + +stdout | src/__tests__/tv4-tv5-axis.test.ts > TV-5 Angled-end length axis > follows prevailing length; widths stay perpendicular after confirm +{"case":"TV-5","direction":{"x":1,"y":0},"lengthPx":96,"widthPx":24,"bodySampleCount":12,"bodyWidths":[23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684,23.999999046325684],"min":0,"max":23.999999046325684,"average":21.19999910990397,"rotatedDirection":{"x":6.123233995736766e-17,"y":1}} + + ↓ src/__tests__/tv7-real.test.ts (1 test | 1 skipped) + ✓ src/__tests__/tv4-tv5-axis.test.ts (2 tests) 51ms +stdout | src/__tests__/validation.test.ts > V-1 SKU validation > accepts all valid SKU boundary strings +V-1 valid: "ABC" → length=3, valid=true +V-1 valid: "012" → length=3, valid=true +V-1 valid: "A0A" → length=3, valid=true +V-1 valid: "A-B" → length=3, valid=true +V-1 valid: "0-1" → length=3, valid=true +V-1 valid: "BW-0042" → length=7, valid=true +V-1 valid: "SLAB-UAT-001" → length=12, valid=true +V-1 valid: "A9Z" → length=3, valid=true +V-1 valid: "AAAAAAAAAAAAAAAAAAAAAAAA" → length=24, valid=true +V-1 valid: "A1B2C3D4E5F6G7H8I9J0K-12" → length=24, valid=true +V-1 valid: "A-A-A-A-A-A" → length=11, valid=true +V-1 valid: "1-2-3-4-5-6" → length=11, valid=true +V-1 valid: "ABCDEF" → length=6, valid=true + +stdout | src/__tests__/validation.test.ts > V-1 SKU validation > rejects all invalid SKU boundary strings +V-1 invalid: "A" → valid=false, errors=too_short, invalid_charset +V-1 invalid: "AB" → valid=false, errors=too_short, invalid_charset +V-1 invalid: "AAAAAAAAAAAAAAAAAAAAAAAAA" → valid=false, errors=too_long, invalid_charset +V-1 invalid: "abc" → valid=false, errors=invalid_charset +V-1 invalid: "bw-0042" → valid=false, errors=invalid_charset +V-1 invalid: "aB3" → valid=false, errors=invalid_charset +V-1 invalid: "ABC!" → valid=false, errors=invalid_charset +V-1 invalid: "A B" → valid=false, errors=invalid_charset +V-1 invalid: "A_B" → valid=false, errors=invalid_charset +V-1 invalid: "ABC.D" → valid=false, errors=invalid_charset +V-1 invalid: "ABC#1" → valid=false, errors=invalid_charset +V-1 invalid: "ABC/1" → valid=false, errors=invalid_charset +V-1 invalid: "ABC\1" → valid=false, errors=invalid_charset +V-1 invalid: "" → valid=false, errors=too_short, invalid_charset + +stdout | src/__tests__/validation.test.ts > V-2 content field maxLength validation > accepts title at exactly maxLength +V-2 title: length=200, valid=true + +stdout | src/__tests__/validation.test.ts > V-2 content field maxLength validation > rejects title that exceeds maxLength by 1 character +V-2 title invalid: length=201, valid=false, errors=exceeds_max_length + +stdout | src/__tests__/validation.test.ts > V-2 content field maxLength validation > accepts short_title at exactly maxLength +V-2 short_title: length=200, valid=true + +stdout | src/__tests__/validation.test.ts > V-2 content field maxLength validation > rejects short_title that exceeds maxLength by 1 character +V-2 short_title invalid: length=201, valid=false, errors=exceeds_max_length + +stdout | src/__tests__/validation.test.ts > V-2 content field maxLength validation > accepts description at exactly maxLength +V-2 description: length=2500, valid=true + +stdout | src/__tests__/validation.test.ts > V-2 content field maxLength validation > rejects description that exceeds maxLength by 1 character +V-2 description invalid: length=2501, valid=false, errors=exceeds_max_length + +stdout | src/__tests__/validation.test.ts > V-3 validation module version hash > computes a stable hash from current validation rules +V-3 module hash: "v1" + +stdout | src/__tests__/validation.test.ts > V-3 validation module version hash > validateModuleHash returns true when client hash matches current module +V-3 match: hash="v1", valid=true + +stdout | src/__tests__/validation.test.ts > V-3 validation module version hash > validateModuleHash returns 412 with fresh module when client hash mismatches +V-3 mismatch: clientHash="v0", serverHash="v1", errors=validation_module_stale + + ✓ src/__tests__/validation.test.ts (25 tests) 22ms +stdout | src/__tests__/tv3-tv10-units.test.ts > TV-3 Scale, sqft, bdft > bdft = sqft × thickness_in with no divide-by-12 +{"case":"TV-3","lengthIn":96,"thicknessIn":1.5,"sqft":0.64,"bdft":0.96,"expectedBdft":0.96,"identity_1sqft_1in":1} + +stdout | src/__tests__/tv3-tv10-units.test.ts > TV-10 Rounding > half-up / nearest-step matches TECH-SPEC vectors +{"case":"TV-10","length":{"raw":96.04,"rounded":96,"expected":96},"sqft":{"raw":0.641,"rounded":0.64,"expected":0.64},"bdft":{"raw":11.525,"rounded":11.53,"expected":11.53},"price":{"raw":12.345,"rounded":12.35,"expected":12.35}} + + ✓ src/__tests__/tv3-tv10-units.test.ts (3 tests) 7ms + ↓ src/__tests__/tv10-real.test.ts (1 test | 1 skipped) + ✓ src/__tests__/tv9-thickness-band.test.ts (6 tests) 6ms + ✓ src/__tests__/tv8-pricing.test.ts (6 tests) 8ms +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-6 3:4 crop, 80% fill, transparent PNG, ≥1600px > computes crop target dimensions preserving 3:4 aspect and 80% fill at extremes +{"case":"TV-6","target1":{"slabW":1281,"slabH":3000,"frame":{"width":1601,"height":2135},"aspect":0.7498829039812647},"target2":{"slabW":2000,"slabH":1600,"frame":{"width":1500,"height":2000},"aspect":0.75}} + +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-6 3:4 crop, 80% fill, transparent PNG, ≥1600px > finds slab bounding box correctly +{"case":"TV-6","bounds":{"left":1360,"top":100,"right":2640,"bottom":3099,"widthPx":1281,"heightPx":3000}} + +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-6 3:4 crop, 80% fill, transparent PNG, ≥1600px > crops large source to 3:4 with 80% fill, shorter side ≥ 1600, transparent PNG +{"case":"TV-6","frameW":1601,"frameH":2135,"shorterSide":1601,"meetsMinPx":true,"marginAlpha":0,"slabCenterAlpha":255,"aspect":0.7498829039812647} + +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-6 3:4 crop, 80% fill, transparent PNG, ≥1600px > property: fill at extremes equals FILL_TARGET at limiting dimension +{"case":"TV-6","fillAtLimitingDim":0.8001249219237976,"expectedFill":0.8} + +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-7 Too-small source — warn, no upscale, not publishable > detects undersized source and refuses upscale +{"case":"TV-7","slabW":400,"slabH":300,"frameW":281,"frameH":375,"shorterSide":281,"outputPxMin":1600,"undersized":true} + +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-7 Too-small source — warn, no upscale, not publishable > cropTo34 returns meetsMinPx = false for undersized source +{"case":"TV-7","frameW":281,"frameH":375,"shorterSide":281,"meetsMinPx":false} + +stdout | src/__tests__/tv6-tv7-crop.test.ts > TV-7 Too-small source — warn, no upscale, not publishable > property: no upscale path — output is never larger than source +{"case":"TV-7","noUpscaleCheck":{"small":{"source":"800x640","output":"281x375"},"large":{"source":"4000x3200","output":"1601x2135"}}} + + ✓ src/__tests__/tv6-tv7-crop.test.ts (7 tests) 1191ms + ✓ TV-7 Too-small source — warn, no upscale, not publishable > property: no upscale path — output is never larger than source 655ms + + Test Files 7 passed | 4 skipped (11) + Tests 54 passed | 4 skipped (58) + Start at 19:47:24 + Duration 1.78s (transform 965ms, setup 0ms, collect 1.67s, tests 1.33s, environment 3ms, prepare 1.02s) + +EXIT:0