Handle ENLYZE timeseries pagination

This commit is contained in:
2026-09-05 08:19:17 +02:00
parent 6ddb76fd03
commit 7c029759f5
2 changed files with 150 additions and 46 deletions
+88
View File
@@ -202,3 +202,91 @@ def test_malformed_run_fails_clearly(item) -> None:
client.get.return_value.body = {"data": [item]}
with pytest.raises(ValueError, match="Invalid production-run response"):
EnlyzeApiGateway(client).get_open_production_run("m")
def test_timeseries_with_null_cursor(timeseries) -> None:
gateway, client, args = timeseries
client.post_json.return_value.body["metadata"] = {"next_cursor": None}
client.post_json.return_value.body["data"]["records"] = [
["2026-09-03T00:00:00Z", 10, 2],
]
samples = gateway.get_material_samples(**args)
assert len(samples) == 1
assert samples[0].material_rate_kg_per_hour == 10
client.post_json.assert_called_once()
def test_timeseries_pagination_resolves_columns_per_page(timeseries) -> None:
gateway, client, args = timeseries
client.post_json.side_effect = [
Mock(body={
"data": {"columns": ["time", "rate", "gate"],
"records": [["2026-09-03T00:00:00Z", 10, 2]]},
"metadata": {"next_cursor": "continuation"},
}),
Mock(body={
"data": {"columns": ["gate", "time", "rate"],
"records": [[3, "2026-09-03T01:00:00Z", 20]]},
"metadata": {"next_cursor": None},
}),
]
samples = gateway.get_material_samples(**args)
assert [(s.timestamp, s.material_rate_kg_per_hour, s.gate_value) for s in samples] == [
(datetime(2026, 9, 3, tzinfo=UTC), 10, 2),
(datetime(2026, 9, 3, 1, tzinfo=UTC), 20, 3),
]
assert client.post_json.call_count == 2
first, second = client.post_json.call_args_list
assert first.args == ("/v2/timeseries", {
"machine": "m", "start": args["start"].isoformat(), "end": args["end"].isoformat(),
"variables": [{"uuid": "rate"}, {"uuid": "gate"}],
})
assert second.args == ("/v2/timeseries", {**first.args[1], "cursor": "continuation"})
@pytest.mark.parametrize("metadata", [None, [], "bad", {},
{"next_cursor": ""}, {"next_cursor": 1}, {"next_cursor": False},
{"next_cursor": []}, {"next_cursor": {}}])
def test_invalid_pagination_metadata(timeseries, metadata) -> None:
gateway, client, args = timeseries
client.post_json.return_value.body["metadata"] = metadata
with pytest.raises(ValueError, match="Invalid timeseries response.*next_cursor"):
gateway.get_material_samples(**args)
client.post_json.assert_called_once()
@pytest.mark.parametrize("cursors", [["a", "a"], ["a", "b", "a"]])
def test_repeated_pagination_cursor(timeseries, cursors) -> None:
gateway, client, args = timeseries
data = client.post_json.return_value.body["data"]
client.post_json.side_effect = [
Mock(body={"data": data, "metadata": {"next_cursor": cursor}}) for cursor in cursors
]
with pytest.raises(ValueError, match="next_cursor has already been followed"):
gateway.get_material_samples(**args)
assert client.post_json.call_count == len(cursors)
@pytest.mark.parametrize("data, error", [
({"columns": ["time", "rate", "gate"], "records": [[]]}, "malformed record 0"),
({"columns": ["time", "rate", "rate", "gate"], "records": []}, "required column"),
({"columns": ["time", "rate"], "records": []}, "required column"),
({"columns": None, "records": []}, "columns must be a list"),
({"columns": ["time", "rate", "gate"], "records": None}, "records must be a list"),
({"columns": ["time", "rate", "gate"],
"records": [["2026-09-03T01:00:00", 10, 2]]}, "timezone-aware"),
({"columns": ["time", "rate", "gate"],
"records": [["2026-09-03T01:00:00Z", float("nan"), 2]]}, "finite"),
({"columns": ["time", "rate", "gate"],
"records": [["2026-09-03T01:00:00Z", 10, True]]}, "numeric"),
])
def test_malformed_later_page(timeseries, data, error) -> None:
gateway, client, args = timeseries
first_body = client.post_json.return_value.body
first_body["metadata"] = {"next_cursor": "continuation"}
client.post_json.side_effect = [
Mock(body=first_body), Mock(body={"data": data, "metadata": {"next_cursor": None}}),
]
with pytest.raises(ValueError, match=f"Invalid timeseries response on page 2:.*{error}"):
gateway.get_material_samples(**args)
assert client.post_json.call_count == 2