File size: 4,467 Bytes
67af2df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Write-path route tests."""

from __future__ import annotations

import pytest

from app.schemas.emotion_log import EMOTION_NAMES


class TestCreate:
    @pytest.mark.parametrize("emotion", EMOTION_NAMES)
    def test_accepts_every_valid_emotion(self, client, ingest_headers, emotion):
        response = client.post(
            "/api/logs",
            json={"dominant_emotion": emotion, "duration_seconds": 10},
            headers=ingest_headers,
        )
        assert response.status_code == 201
        assert response.json()["dominant_emotion"] == emotion

    def test_rejects_unknown_emotion_with_422(self, client, ingest_headers):
        """Previously a hand-rolled validator; now a Literal, so the error is typed."""
        response = client.post(
            "/api/logs",
            json={"dominant_emotion": "Ecstatic", "duration_seconds": 10},
            headers=ingest_headers,
        )
        assert response.status_code == 422

    @pytest.mark.parametrize("duration", [0.5, 0, -1, 3601])
    def test_rejects_out_of_range_duration(self, client, ingest_headers, duration):
        response = client.post(
            "/api/logs",
            json={"dominant_emotion": "Happy", "duration_seconds": duration},
            headers=ingest_headers,
        )
        assert response.status_code == 422

    def test_response_includes_server_assigned_fields(self, client, ingest_headers):
        body = client.post(
            "/api/logs",
            json={"dominant_emotion": "Happy", "duration_seconds": 10},
            headers=ingest_headers,
        ).json()
        assert isinstance(body["id"], int)
        assert body["timestamp"]

    def test_timestamp_carries_an_explicit_utc_offset(self, client, ingest_headers):
        """Without an offset, browsers parse the value as local time.



        SQLite returns a naive datetime even for a column declared timezone-aware,

        so the serialiser has to reattach UTC. A bare "2026-08-13T22:01:45" would

        shift every displayed record by the viewer's offset.

        """
        body = client.post(
            "/api/logs",
            json={"dominant_emotion": "Happy", "duration_seconds": 10},
            headers=ingest_headers,
        ).json()
        assert body["timestamp"].endswith(("Z", "+00:00"))

    def test_listed_timestamps_carry_an_offset(self, client, ingest_headers, session_headers):
        client.post(
            "/api/logs",
            json={"dominant_emotion": "Sad", "duration_seconds": 10},
            headers=ingest_headers,
        )
        items = client.get("/api/logs", headers=session_headers).json()["items"]
        assert items
        assert all(item["timestamp"].endswith(("Z", "+00:00")) for item in items)


class TestBulkCreate:
    def test_creates_all_items(self, client, ingest_headers):
        items = [{"dominant_emotion": "Happy", "duration_seconds": 10} for _ in range(5)]
        response = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers)
        assert response.status_code == 201
        body = response.json()
        assert body["created"] == 5
        assert body["rejected"] == 0

    def test_reports_per_item_indexes(self, client, ingest_headers):
        items = [{"dominant_emotion": "Sad", "duration_seconds": 10} for _ in range(3)]
        body = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers).json()
        assert [entry["index"] for entry in body["results"]] == [0, 1, 2]

    def test_rejects_empty_batch(self, client, ingest_headers):
        response = client.post("/api/logs/bulk", json={"items": []}, headers=ingest_headers)
        assert response.status_code == 422

    def test_rejects_oversized_batch(self, client, ingest_headers):
        items = [{"dominant_emotion": "Happy", "duration_seconds": 10}] * 501
        response = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers)
        assert response.status_code == 422

    def test_invalid_item_fails_the_batch_validation(self, client, ingest_headers):
        items = [
            {"dominant_emotion": "Happy", "duration_seconds": 10},
            {"dominant_emotion": "Nonsense", "duration_seconds": 10},
        ]
        response = client.post("/api/logs/bulk", json={"items": items}, headers=ingest_headers)
        assert response.status_code == 422