"""Batch API tests. List is cheap and always runs; the end-to-end test creates a 1-request batch (≈ $0.000004) and is gated by RUN_BATCH_TESTS=true (marker run_batch_tests). Batches themselves cannot be deleted; files are.""" from __future__ import annotations import json, time, uuid import pytest def test_list_batches_shape(openai): st, page, _ = openai("GET", "/v1/batches?limit=2", note="test_batch list") assert st == 200 and page["object"] == "list" and "has_more" in page for b in page["data"]: assert b["object"] == "batch" and b["status"] in {"validating", "failed", "in_progress", "finalizing", "completed", "expired", "cancelling", "cancelled"} def test_retrieve_unknown_batch_404(openai): st, body, _ = openai("GET", "/v1/batches/batch_atlasdoesnotexist", note="test_batch bogus id") assert st == 404 and body["error"]["type"] == "invalid_request_error" @pytest.mark.run_batch_tests def test_batch_end_to_end(openai, models): line = {"custom_id": "atlas-platform-agent-test-1", "method": "POST", "url": "/v1/responses", "body": {"model": models["openai"], "input": "Reply with OK.", "max_output_tokens": 16}} b = f"----atlas{uuid.uuid4().hex}" body = (f"--{b}\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nbatch\r\n--{b}\r\n" f"Content-Disposition: form-data; name=\"file\"; filename=\"in.jsonl\"\r\nContent-Type: application/jsonl\r\n\r\n").encode() \ + (json.dumps(line) + "\n").encode() + f"\r\n--{b}--\r\n".encode() st, f, _ = openai("POST", "/v1/files", data=body, content_type=f"multipart/form-data; boundary={b}", note="test_batch upload") assert st == 200 and f["purpose"] == "batch" and f["expires_at"] == f["created_at"] + 30 * 86400 files = [f["id"]] try: st, batch, _ = openai("POST", "/v1/batches", {"input_file_id": f["id"], "endpoint": "/v1/responses", "completion_window": "24h", "metadata": {"owner": "atlas-platform-agent-test"}, "output_expires_after": {"anchor": "created_at", "seconds": 3600}}, note="test_batch create", est_cost_usd=0.00001) assert st == 200 and batch["object"] == "batch" and batch["status"] == "validating" assert batch["expires_at"] == batch["created_at"] + 86400 t0 = time.time() while batch["status"] not in ("completed", "failed", "expired", "cancelled") and time.time() - t0 < 180: time.sleep(15) st, batch, _ = openai("GET", f"/v1/batches/{batch['id']}", note="test_batch poll") if batch["status"] not in ("completed", "failed", "expired", "cancelled"): st, c, _ = openai("POST", f"/v1/batches/{batch['id']}/cancel", note="test_batch cancel") assert st == 200 and c["status"] in ("cancelling", "cancelled") pytest.skip("batch did not finish within 3 minutes; cancelled") assert batch["status"] == "completed", batch assert batch["request_counts"] == {"total": 1, "completed": 1, "failed": 0} assert batch["usage"]["total_tokens"] > 0 files.append(batch["output_file_id"]) st, out, _ = openai("GET", f"/v1/files/{batch['output_file_id']}/content", content_type=None, note="test_batch output") row = json.loads(out.decode().splitlines()[0]) assert row["custom_id"] == line["custom_id"] and row["error"] is None assert row["response"]["status_code"] == 200 and row["response"]["body"]["object"] == "response" st, meta, _ = openai("GET", f"/v1/files/{batch['output_file_id']}", note="test_batch output meta") assert meta["purpose"] == "batch_output" and meta["expires_at"] == meta["created_at"] + 3600 finally: for fid in files: openai("DELETE", f"/v1/files/{fid}", note="test_batch cleanup")