feat(bot): клиент SourceCraft API — прогоны, логи кубов, sha коммита, ретраи
- SourceCraftClient (httpx): list_runs, get_run, get_cube_logs (постранично), run_commit_sha; ретраи на сетевые ошибки и 5xx с экспоненциальной задержкой, 4xx — SourceCraftError без повторов - модели: RunStatus/EventType из swagger, «1970-01-01» = не задано, failed_cubes без служебных #-кубов - у прогона в API нет sha → куб check/meta в tgit-uploads печатает TGIT_COMMIT_SHA (ADR-014) - CLI: python -m app.sourcecraft.cli runs - тесты на реальных ответах API (фикстуры), порог покрытия 45 → 80 Refs #10 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"logs": "Downloading ruff (12.8MiB)\n Downloading ruff\nInstalled 1 package in 1ms\nF401 [*] `os` imported but unused\n --> services/broken/main.py:1:8\n |\n1 | import os\n | ^^\n2 |\n3 | def run():\n |\nhelp: Remove unused import: `os`\n\nF821 Undefined name `undefined_name`\n --> services/broken/main.py:4:12\n |\n3 | def run():\n4 | return undefined_name\n | ^^^^^^^^^^^^^^\n |\n\nFound 2 errors.\n[*] 1 fixable with the `--fix` option.\n",
|
||||
"page_complete": false,
|
||||
"done": true
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"logs": "TGIT_COMMIT_SHA=932f4b1a144cfed4fed8eafd94433e0995e5b12d\n",
|
||||
"page_complete": false,
|
||||
"done": true
|
||||
}
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
{
|
||||
"id": "",
|
||||
"slug": "1000001",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.273658Z",
|
||||
"started_at": "2026-10-13T20:58:53.940173Z",
|
||||
"finished_at": "2026-10-13T20:59:17.833911Z",
|
||||
"updated_at": "2026-10-13T20:59:17.867686Z"
|
||||
},
|
||||
"status": "failed",
|
||||
"workflows": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check-and-deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:53.940173Z",
|
||||
"finished_at": "2026-10-13T20:59:17.833911Z",
|
||||
"updated_at": "2026-10-13T20:59:17.862572Z"
|
||||
},
|
||||
"status": "failed",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:53.940173Z",
|
||||
"finished_at": "2026-10-13T20:59:11.150164Z",
|
||||
"updated_at": "2026-10-13T20:59:12.846332Z"
|
||||
},
|
||||
"status": "failed",
|
||||
"cubes": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#create-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:54.031424Z",
|
||||
"finished_at": "2026-10-13T20:58:54.090132Z",
|
||||
"updated_at": "2026-10-13T20:58:54.155008Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#git-clone",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:54.090481Z",
|
||||
"finished_at": "2026-10-13T20:58:55.142644Z",
|
||||
"updated_at": "2026-10-13T20:58:55.211046Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#create-environment"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-compile",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:55.143033Z",
|
||||
"finished_at": "2026-10-13T20:58:59.605194Z",
|
||||
"updated_at": "2026-10-13T20:58:59.706155Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-lint",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:59.605538Z",
|
||||
"finished_at": "2026-10-13T20:59:00.536788Z",
|
||||
"updated_at": "2026-10-13T20:59:00.604498Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-security",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:59:00.537171Z",
|
||||
"finished_at": "2026-10-13T20:59:01.447150Z",
|
||||
"updated_at": "2026-10-13T20:59:01.511994Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-tests",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:59:01.447481Z",
|
||||
"finished_at": "2026-10-13T20:59:02.624921Z",
|
||||
"updated_at": "2026-10-13T20:59:02.700313Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "compile",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:59:02.625267Z",
|
||||
"finished_at": "2026-10-13T20:59:09.366621Z",
|
||||
"updated_at": "2026-10-13T20:59:09.454706Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-compile"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "lint",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:59:09.366953Z",
|
||||
"finished_at": "2026-10-13T20:59:11.088201Z",
|
||||
"updated_at": "2026-10-13T20:59:11.173518Z"
|
||||
},
|
||||
"status": "failed",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-lint"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "security",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "2026-10-13T20:59:11.088571Z",
|
||||
"updated_at": "2026-10-13T20:59:11.206851Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-security"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "tests",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "2026-10-13T20:59:11.088804Z",
|
||||
"updated_at": "2026-10-13T20:59:11.242177Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#tear-down-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:59:11.089254Z",
|
||||
"finished_at": "2026-10-13T20:59:11.150164Z",
|
||||
"updated_at": "2026-10-13T20:59:11.346169Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "compile"
|
||||
},
|
||||
{
|
||||
"name": "lint"
|
||||
},
|
||||
{
|
||||
"name": "security"
|
||||
},
|
||||
{
|
||||
"name": "tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": {
|
||||
"needs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "2026-10-13T20:59:17.833911Z",
|
||||
"updated_at": "2026-10-13T20:59:17.840273Z"
|
||||
},
|
||||
"status": "created",
|
||||
"cubes": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#create-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-13T20:58:52.727666Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#git-clone",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-13T20:58:52.727666Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#create-environment"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-artifacts",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-13T20:58:52.727666Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-deploy",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-13T20:58:52.727666Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-artifacts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "deploy",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-13T20:58:52.727666Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-deploy"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#tear-down-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-13T20:58:52.727666Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "deploy"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "check"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"environment_id": ""
|
||||
}
|
||||
],
|
||||
"event_type": "manual",
|
||||
"error_messages": [],
|
||||
"user": {
|
||||
"id": "01a06158-1562-7db1-b6e9-018ddd4d4c29",
|
||||
"slug": "rogachevegorushka"
|
||||
}
|
||||
}
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
{
|
||||
"id": "",
|
||||
"slug": "1000002",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:56.652199Z",
|
||||
"started_at": "2026-10-14T14:57:43.223468Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:57:43.242955Z"
|
||||
},
|
||||
"status": "processing",
|
||||
"workflows": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check-and-deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:43.223468Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:57:43.234813Z"
|
||||
},
|
||||
"status": "processing",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:43.223468Z",
|
||||
"finished_at": "2026-10-14T14:58:19.375134Z",
|
||||
"updated_at": "2026-10-14T14:58:19.571691Z"
|
||||
},
|
||||
"status": "success",
|
||||
"cubes": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#create-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:43.514526Z",
|
||||
"finished_at": "2026-10-14T14:57:43.584723Z",
|
||||
"updated_at": "2026-10-14T14:57:43.657871Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#git-clone",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:43.585127Z",
|
||||
"finished_at": "2026-10-14T14:57:45.528681Z",
|
||||
"updated_at": "2026-10-14T14:57:45.606302Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#create-environment"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-compile",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:45.529064Z",
|
||||
"finished_at": "2026-10-14T14:57:55.435126Z",
|
||||
"updated_at": "2026-10-14T14:57:55.537030Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-lint",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:55.435513Z",
|
||||
"finished_at": "2026-10-14T14:57:58.411834Z",
|
||||
"updated_at": "2026-10-14T14:57:58.479777Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-meta",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:58.412252Z",
|
||||
"finished_at": "2026-10-14T14:58:03.323469Z",
|
||||
"updated_at": "2026-10-14T14:58:03.420479Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-security",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:03.323798Z",
|
||||
"finished_at": "2026-10-14T14:58:04.898405Z",
|
||||
"updated_at": "2026-10-14T14:58:04.986588Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-tests",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:04.898847Z",
|
||||
"finished_at": "2026-10-14T14:58:09.732911Z",
|
||||
"updated_at": "2026-10-14T14:58:09.807301Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "compile",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:09.733227Z",
|
||||
"finished_at": "2026-10-14T14:58:12.400619Z",
|
||||
"updated_at": "2026-10-14T14:58:12.533056Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-compile"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "lint",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:12.401008Z",
|
||||
"finished_at": "2026-10-14T14:58:14.235114Z",
|
||||
"updated_at": "2026-10-14T14:58:14.304352Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-lint"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "meta",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:14.235485Z",
|
||||
"finished_at": "2026-10-14T14:58:14.707956Z",
|
||||
"updated_at": "2026-10-14T14:58:14.778120Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-meta"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "security",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:14.708362Z",
|
||||
"finished_at": "2026-10-14T14:58:17.148819Z",
|
||||
"updated_at": "2026-10-14T14:58:17.221280Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-security"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "tests",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:17.149213Z",
|
||||
"finished_at": "2026-10-14T14:58:19.298832Z",
|
||||
"updated_at": "2026-10-14T14:58:19.405984Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#tear-down-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:58:19.299366Z",
|
||||
"finished_at": "2026-10-14T14:58:19.375117Z",
|
||||
"updated_at": "2026-10-14T14:58:19.528162Z"
|
||||
},
|
||||
"status": "success",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "compile"
|
||||
},
|
||||
{
|
||||
"name": "lint"
|
||||
},
|
||||
{
|
||||
"name": "meta"
|
||||
},
|
||||
{
|
||||
"name": "security"
|
||||
},
|
||||
{
|
||||
"name": "tests"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": {
|
||||
"needs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:58:21.737007Z"
|
||||
},
|
||||
"status": "created",
|
||||
"cubes": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#create-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:51:57.522605Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#git-clone",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:51:57.522605Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#create-environment"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-artifacts",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:51:57.522605Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#git-clone"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#prepare-image-for-deploy",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:51:57.522605Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-artifacts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "deploy",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:51:57.522605Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "#prepare-image-for-deploy"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "#tear-down-environment",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "1970-01-01T00:00:00Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:51:57.522605Z"
|
||||
},
|
||||
"status": "created",
|
||||
"artifacts": [],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "deploy"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"relations": {
|
||||
"needs": [
|
||||
{
|
||||
"name": "check"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"environment_id": ""
|
||||
}
|
||||
],
|
||||
"event_type": "push",
|
||||
"error_messages": [],
|
||||
"user": {
|
||||
"id": "01a06158-1562-7db1-b6e9-018ddd4d4c29",
|
||||
"slug": "rogachevegorushka"
|
||||
}
|
||||
}
|
||||
Vendored
+104
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"runs": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "1000002",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:56.652199Z",
|
||||
"started_at": "2026-10-14T14:57:43.223468Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:57:43.242955Z"
|
||||
},
|
||||
"status": "processing",
|
||||
"workflows": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check-and-deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-14T14:51:57.521264Z",
|
||||
"started_at": "2026-10-14T14:57:43.223468Z",
|
||||
"finished_at": "1970-01-01T00:00:00Z",
|
||||
"updated_at": "2026-10-14T14:57:43.234813Z"
|
||||
},
|
||||
"status": "processing",
|
||||
"tasks": [],
|
||||
"environment_id": ""
|
||||
}
|
||||
],
|
||||
"event_type": "push",
|
||||
"error_messages": [],
|
||||
"user": {
|
||||
"id": "01a06158-1562-7db1-b6e9-018ddd4d4c29",
|
||||
"slug": "rogachevegorushka"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "1000001",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.273658Z",
|
||||
"started_at": "2026-10-13T20:58:53.940173Z",
|
||||
"finished_at": "2026-10-13T20:59:17.833911Z",
|
||||
"updated_at": "2026-10-13T20:59:17.867686Z"
|
||||
},
|
||||
"status": "failed",
|
||||
"workflows": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check-and-deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:58:52.726601Z",
|
||||
"started_at": "2026-10-13T20:58:53.940173Z",
|
||||
"finished_at": "2026-10-13T20:59:17.833911Z",
|
||||
"updated_at": "2026-10-13T20:59:17.862572Z"
|
||||
},
|
||||
"status": "failed",
|
||||
"tasks": [],
|
||||
"environment_id": ""
|
||||
}
|
||||
],
|
||||
"event_type": "manual",
|
||||
"error_messages": [],
|
||||
"user": {
|
||||
"id": "01a06158-1562-7db1-b6e9-018ddd4d4c29",
|
||||
"slug": "rogachevegorushka"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "",
|
||||
"slug": "1000000",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:57:06.596315Z",
|
||||
"started_at": "2026-10-13T20:57:06.821003Z",
|
||||
"finished_at": "2026-10-13T20:58:26.698635Z",
|
||||
"updated_at": "2026-10-13T20:58:27.884131Z"
|
||||
},
|
||||
"status": "success",
|
||||
"workflows": [
|
||||
{
|
||||
"id": "",
|
||||
"slug": "check-and-deploy",
|
||||
"description": "",
|
||||
"dates": {
|
||||
"created_at": "2026-10-13T20:57:06.725971Z",
|
||||
"started_at": "2026-10-13T20:57:06.821003Z",
|
||||
"finished_at": "2026-10-13T20:58:26.698635Z",
|
||||
"updated_at": "2026-10-13T20:58:27.878032Z"
|
||||
},
|
||||
"status": "success",
|
||||
"tasks": [],
|
||||
"environment_id": ""
|
||||
}
|
||||
],
|
||||
"event_type": "push",
|
||||
"error_messages": [],
|
||||
"user": {
|
||||
"id": "01a06158-1562-7db1-b6e9-018ddd4d4c29",
|
||||
"slug": "rogachevegorushka"
|
||||
}
|
||||
}
|
||||
],
|
||||
"next_page_token": "147400973399132381"
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
from app.config import SourceCraftSettings
|
||||
from app.sourcecraft.client import SourceCraftClient, SourceCraftError
|
||||
from app.sourcecraft.models import EventType, RunStatus
|
||||
|
||||
FIXTURES = Path(__file__).resolve().parent.parent / "fixtures" / "sourcecraft"
|
||||
API = "https://api.example.test"
|
||||
REPO = "org/repo"
|
||||
SHA = "932f4b1a144cfed4fed8eafd94433e0995e5b12d"
|
||||
|
||||
|
||||
def fixture(name: str) -> Any:
|
||||
return json.loads((FIXTURES / name).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
class Sleeps:
|
||||
def __init__(self) -> None:
|
||||
self.delays: list[float] = []
|
||||
|
||||
async def __call__(self, delay: float) -> None:
|
||||
self.delays.append(delay)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sleeps() -> Sleeps:
|
||||
return Sleeps()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(sleeps: Sleeps) -> Any:
|
||||
settings = SourceCraftSettings(token="secret", api_url=API, max_attempts=3) # type: ignore[arg-type]
|
||||
async with SourceCraftClient(settings, sleep=sleeps) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_list_runs_parses_real_response(client: SourceCraftClient) -> None:
|
||||
route = respx.get(f"{API}/repos/{REPO}/cicd/runs").respond(json=fixture("runs.json"))
|
||||
|
||||
runs = await client.list_runs(REPO, page_size=3)
|
||||
|
||||
assert route.calls.last.request.headers["Authorization"] == "Bearer secret"
|
||||
assert route.calls.last.request.url.params["page_size"] == "3"
|
||||
assert [r.slug for r in runs] == ["1000002", "1000001", "1000000"]
|
||||
assert runs[1].event_type is EventType.MANUAL
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_failed_run_points_at_failed_cube(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs/1000001").respond(json=fixture("run_failed.json"))
|
||||
|
||||
run = await client.get_run(REPO, "1000001")
|
||||
|
||||
assert run.status is RunStatus.FAILED and run.status.finished
|
||||
assert [(t, c.slug) for _, t, c in run.failed_cubes()] == [("check", "lint")]
|
||||
assert all(not c.is_service for _, _, c in run.cubes())
|
||||
assert run.dates.finished_at is not None
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_unfinished_run_has_no_finish_date(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs/1000002").respond(json=fixture("run_processing.json"))
|
||||
|
||||
run = await client.get_run(REPO, "1000002")
|
||||
|
||||
assert not run.status.finished
|
||||
assert run.dates.finished_at is None # API sends 1970-01-01 for "not yet"
|
||||
check = run.task("check-and-deploy", "check")
|
||||
assert check is not None and check.status is RunStatus.SUCCESS
|
||||
assert run.task("check-and-deploy", "nope") is None
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_commit_sha_from_meta_cube(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs/1000002").respond(json=fixture("run_processing.json"))
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/logs/1000002/check-and-deploy/check/meta").respond(
|
||||
json=fixture("log_meta.json")
|
||||
)
|
||||
|
||||
run = await client.get_run(REPO, "1000002")
|
||||
|
||||
assert await client.run_commit_sha(REPO, run) == SHA
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_commit_sha_none_when_meta_log_missing(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs/1000002").respond(json=fixture("run_processing.json"))
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/logs/1000002/check-and-deploy/check/meta").respond(
|
||||
404, json={"error_code": "NotFound", "message": "no logs"}
|
||||
)
|
||||
run = await client.get_run(REPO, "1000002")
|
||||
|
||||
assert await client.run_commit_sha(REPO, run) is None
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_logs_are_read_page_by_page(client: SourceCraftClient) -> None:
|
||||
url = f"{API}/repos/{REPO}/cicd/logs/1/wf/task/cube"
|
||||
respx.get(url, params={"page": "1"}).respond(json={"logs": "a\n", "page_complete": True, "done": False})
|
||||
respx.get(url, params={"page": "2"}).respond(json={"logs": "b\n", "page_complete": True, "done": True})
|
||||
respx.get(url, params={"page": "3"}).respond(404, json={"error_code": "NotFound", "message": ""})
|
||||
|
||||
logs = await client.get_cube_logs(REPO, "1", "wf", "task", "cube")
|
||||
|
||||
assert logs.text == "a\nb\n" and logs.done
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_real_failed_lint_log(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/logs/1000001/check-and-deploy/check/lint").respond(
|
||||
json=fixture("log_lint_failed.json")
|
||||
)
|
||||
|
||||
logs = await client.get_cube_logs(REPO, "1000001", "check-and-deploy", "check", "lint")
|
||||
|
||||
assert "F821 Undefined name" in logs.text and logs.done
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_retries_transport_errors_and_5xx(client: SourceCraftClient, sleeps: Sleeps) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs").mock(
|
||||
side_effect=[httpx.ConnectError("tls eof"), httpx.Response(503), httpx.Response(200, json={"runs": []})]
|
||||
)
|
||||
|
||||
assert await client.list_runs(REPO) == []
|
||||
assert sleeps.delays == [0.5, 1.0]
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_gives_up_after_max_attempts(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs").mock(side_effect=httpx.ConnectError("down"))
|
||||
|
||||
with pytest.raises(httpx.ConnectError):
|
||||
await client.list_runs(REPO)
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_persistent_5xx_raises_api_error(client: SourceCraftClient) -> None:
|
||||
respx.get(f"{API}/repos/{REPO}/cicd/runs").respond(502, text="bad gateway")
|
||||
|
||||
with pytest.raises(SourceCraftError) as exc:
|
||||
await client.list_runs(REPO)
|
||||
assert exc.value.status == 502
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_4xx_is_not_retried(client: SourceCraftClient, sleeps: Sleeps) -> None:
|
||||
route = respx.get(f"{API}/repos/{REPO}/cicd/runs/9").respond(
|
||||
403, json={"error_code": "PermissionDenied", "message": "no access"}
|
||||
)
|
||||
|
||||
with pytest.raises(SourceCraftError) as exc:
|
||||
await client.get_run(REPO, "9")
|
||||
|
||||
assert (exc.value.status, exc.value.code) == (403, "PermissionDenied")
|
||||
assert route.call_count == 1 and sleeps.delays == []
|
||||
|
||||
|
||||
async def test_run_url(client: SourceCraftClient) -> None:
|
||||
assert client.run_url(REPO, "7") == "https://sourcecraft.dev/org/repo/cicd/runs/7"
|
||||
Reference in New Issue
Block a user