============================= 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 FAILED    [ 61%]
tests/contract/test_slabs.py::TestCreateSlab::test_returns_status_calibrated FAILED [ 64%]
tests/contract/test_slabs.py::TestCreateSlab::test_returns_server_rev FAILED [ 66%]
tests/contract/test_slabs.py::TestCreateSlab::test_response_shape FAILED [ 69%]
tests/contract/test_slabs.py::TestCreateSlab::test_multiple_photos FAILED [ 71%]
tests/contract/test_slabs.py::TestCreateSlab::test_missing_data_field_returns_400 FAILED [ 74%]
tests/contract/test_slabs.py::TestCreateSlab::test_duplicate_sku_returns_409 FAILED [ 76%]
tests/contract/test_slabs.py::TestCreateSlab::test_with_test_client_lifespan FAILED [ 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%]

=================================== FAILURES ===================================
_______________________ TestCreateSlab.test_returns_201 ________________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b20c690>

    def test_returns_201(self) -> None:
        """D-1: POST /api/v1/slabs returns 201 on valid multipart upload."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        data_json = json.dumps({
            "id": slab_id,
            "sku": "BW-0042",
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:65: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd2a905f40>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
________________ TestCreateSlab.test_returns_status_calibrated _________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b20c7d0>

    def test_returns_status_calibrated(self) -> None:
        """D-1: Response contains status: calibrated."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        data_json = json.dumps({
            "id": slab_id,
            "sku": "BW-0099",
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:122: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd2959b110>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
____________________ TestCreateSlab.test_returns_server_rev ____________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b2050f0>

    def test_returns_server_rev(self) -> None:
        """D-1: Response contains server_rev."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        data_json = json.dumps({
            "id": slab_id,
            "sku": "BW-0077",
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:181: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd2972c1b0>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
______________________ TestCreateSlab.test_response_shape ______________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b204fc0>

    def test_response_shape(self) -> None:
        """D-1: Response contains required fields."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        data_json = json.dumps({
            "id": slab_id,
            "sku": "BW-0033",
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:241: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd2a9a7d90>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
_____________________ TestCreateSlab.test_multiple_photos ______________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b1ef2f0>

    def test_multiple_photos(self) -> None:
        """D-1: Multiple files[] and meta[] pairs are accepted."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        data_json = json.dumps({
            "id": slab_id,
            "sku": "BW-0055",
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                    ("files[]", ("extra1.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:335: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd2a903890>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
______________ TestCreateSlab.test_missing_data_field_returns_400 ______________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b1d8f30>

    def test_missing_data_field_returns_400(self) -> None:
        """D-1: Missing data field returns 400."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:367: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd299d58b0>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
________________ TestCreateSlab.test_duplicate_sku_returns_409 _________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2b1d8270>

    def test_duplicate_sku_returns_409(self) -> None:
        """D-1: Duplicate SKU returns 409."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        sku = "DUPLICATE-TEST"
        data_json = json.dumps({
            "id": slab_id,
            "sku": sku,
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
            # Create first slab
>           response1 = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:427: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd299d6170>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
________________ TestCreateSlab.test_with_test_client_lifespan _________________

self = <tests.contract.test_slabs.TestCreateSlab object at 0x7fdd2bce6050>

    def test_with_test_client_lifespan(self) -> None:
        """FUND-9: TestClient with app manages lifespan correctly for slabs."""
        settings = Settings(
            aes_key="dGVzdC1rZXktZm9yLW1hbGxlb3AtdGVzdGluZy1vbmx5LXNob3VsZC1iZS11c2Vk",
            woo_base_url="https://test.example.com",
        )
        app = create_app(settings)
    
        slab_id = str(uuid.uuid4())
        data_json = json.dumps({
            "id": slab_id,
            "sku": "BW-0011",
            "length_in": 96.0,
            "thickness_in": 1.5,
            "sqft": 0.64,
            "bdft": 0.96,
            "width_min_in": 14.0,
            "width_max_in": 22.0,
            "width_avg_in": 18.2,
            "species_id": 42,
            "wood_category_ids": [15, 23],
            "edge_type_term_id": 8,
            "figure_term_ids": [3],
            "grade_term_ids": [7],
            "thickness_term_id": 12,
            "moisture_term_id": 5,
            "fig_tag_ids": [99],
            "feat_tag_ids": [42],
            "price_per_bdft": 12.50,
            "price": 12.00,
            "price_source": "recommendation",
            "title": None,
            "short_title": None,
            "description": None,
            "content_source": None,
            "client_rev": 1,
        })
    
        with TestClient(app) as client:
>           response = client.post(
                "/api/v1/slabs",
                files=[
                    ("files[]", ("topdown.jpg", b"\xff\xd8\xff\xe0", "image/jpeg")),
                ],
                data={
                    "data": data_json,
                    "meta[]": json.dumps({
                        "kind": "inventory",
                        "role": "topdown",
                        "seq": 1,
                    }),
                },
            )

tests/contract/test_slabs.py:503: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/starlette/testclient.py:556: in post
    return super().post(
.venv/lib/python3.13/site-packages/httpx/_client.py:1144: in post
    return self.request(
.venv/lib/python3.13/site-packages/starlette/testclient.py:455: in request
    return super().request(
.venv/lib/python3.13/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:914: in send
    response = self._send_handling_auth(
.venv/lib/python3.13/site-packages/httpx/_client.py:942: in _send_handling_auth
    response = self._send_handling_redirects(
.venv/lib/python3.13/site-packages/httpx/_client.py:979: in _send_handling_redirects
    response = self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/httpx/_client.py:1014: in _send_single_request
    response = transport.handle_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/starlette/testclient.py:354: in handle_request
    raise exc
.venv/lib/python3.13/site-packages/starlette/testclient.py:351: in handle_request
    portal.call(self.app, scope, receive, send)
.venv/lib/python3.13/site-packages/anyio/from_thread.py:340: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:456: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.13/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
.venv/lib/python3.13/site-packages/anyio/from_thread.py:265: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/applications.py:1163: in __call__
    await super().__call__(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
.venv/lib/python3.13/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
.venv/lib/python3.13/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/routing.py:670: in __call__
    await self.middleware_stack(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2734: in app
    await route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1780: in handle
    await self.original_router.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:2789: in handle
    await included_router._handle_selected(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1800: in _handle_selected
    await original_route.handle(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:1279: in handle
    await app(scope, receive, send)
.venv/lib/python3.13/site-packages/fastapi/routing.py:158: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
.venv/lib/python3.13/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
.venv/lib/python3.13/site-packages/fastapi/routing.py:144: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/fastapi/routing.py:706: in app
    raw_response = await run_endpoint_function(
.venv/lib/python3.13/site-packages/fastapi/routing.py:352: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
app/api/slabs.py:263: in create_slab
    form = await request.form()
           ^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7fdd295f6490>

    async def _get_form(
        self,
        *,
        max_files: int | float = 1000,
        max_fields: int | float = 1000,
        max_part_size: int = 1024 * 1024,
    ) -> FormData:
        if self._form is None:  # pragma: no branch
>           assert parse_options_header is not None, (
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                "The `python-multipart` library must be installed to use form parsing."
            )
E           AssertionError: The `python-multipart` library must be installed to use form parsing.

.venv/lib/python3.13/site-packages/starlette/requests.py:276: AssertionError
------------------------------ Captured log call -------------------------------
INFO     root:main.py:24 SlabUploader starting up
INFO     app.db.engine:engine.py:49 Connecting to sqlite+aiosqlite:///:memory:
INFO     app.db.engine:engine.py:69 Database initialized
INFO     root:main.py:27 SlabUploader shutting down
=============================== 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
=========================== short test summary info ============================
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_returns_201 - Asser...
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_returns_status_calibrated
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_returns_server_rev
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_response_shape - As...
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_multiple_photos - A...
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_missing_data_field_returns_400
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_duplicate_sku_returns_409
FAILED tests/contract/test_slabs.py::TestCreateSlab::test_with_test_client_lifespan
=================== 8 failed, 31 passed, 2 warnings in 3.52s ===================
EXIT:1
