feat(recov. job): add drive consistency service
This commit is contained in:
@@ -89,7 +89,12 @@ jsonpath "$..interval_ms" contains 600000
|
||||
jsonpath "$..interval_ms" count == 3
|
||||
|
||||
# Every entry carries a `running` bool — same aggregate primitive.
|
||||
jsonpath "$..running" count == 4
|
||||
# Count matches the registered-tenant count: 4 Part 1 periodics
|
||||
# (trash_cleanup, storage_reconcile, dedup_gc, grant_cleanup) + 1
|
||||
# Part 2 recoverable (drives_consistency, wrapped by RecoverableAdapter
|
||||
# so it appears here alongside the periodics). Bump when a new
|
||||
# tenant registers.
|
||||
jsonpath "$..running" count == 5
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# =============================================================
|
||||
# OxiCloud — Recoverable-run admin surface
|
||||
# =============================================================
|
||||
# Pins the Part 2 (recoverable-run engine) admin endpoints:
|
||||
# * POST /api/admin/jobs/{name}/trigger (RecoverableJobHandler
|
||||
# path via RecoverableAdapter)
|
||||
# * POST /api/admin/jobs/{name}/cancel
|
||||
# * GET /api/admin/jobs/{name}/runs
|
||||
# * GET /api/admin/jobs/{name}/runs/{id}
|
||||
#
|
||||
# Uses `drives_consistency` — the first recoverable tenant, on-demand
|
||||
# only. Verifies:
|
||||
# 1. Registered job appears in `GET /api/admin/jobs` with no
|
||||
# interval (on-demand only).
|
||||
# 2. Triggering creates a fresh row in `jobs.recoverable_runs`,
|
||||
# handler completes, run terminates as Completed.
|
||||
# 3. History endpoint returns the just-completed run.
|
||||
# 4. Single-run detail endpoint returns the same row.
|
||||
# 5. Cancel-on-idle is a no-op with `cancelled: false` (nothing
|
||||
# running to cancel).
|
||||
# 6. Unknown run id → 404 on the single-run endpoint.
|
||||
# 7. Non-admin caller → 403 from the admin middleware on every
|
||||
# recoverable endpoint (no bespoke role check in the handlers).
|
||||
#
|
||||
# Drift-finding assertions land alongside the `jobs.run_findings`
|
||||
# migration — the current build LOGS findings to
|
||||
# `oxicloud::consistency` without persisting them. Log-tail
|
||||
# assertions from Hurl are fragile so we defer them.
|
||||
# =============================================================
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Setup — admin login + rjobs_bob (non-admin) provisioning
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "{{username}}", "password": "{{password}}" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
admin_token: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# Anti-enum registration.
|
||||
POST {{base_url}}/api/auth/register
|
||||
Content-Type: application/json
|
||||
{
|
||||
"username": "rjobs_bob",
|
||||
"email": "rjobs_bob@example.com",
|
||||
"password": "RjobsBobPassword1!"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
|
||||
|
||||
POST {{base_url}}/api/auth/login
|
||||
Content-Type: application/json
|
||||
{ "username": "rjobs_bob", "password": "RjobsBobPassword1!" }
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
bob_token: jsonpath "$.access_token"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 1 — `drives_consistency` is registered on-demand only.
|
||||
# Appears in the listing without an `interval_ms`.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/admin/jobs
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$[*].name" contains "drives_consistency"
|
||||
# On-demand → no interval_ms (`skip_serializing_if = Option::is_none`).
|
||||
jsonpath "$[?(@.name=='drives_consistency')].interval_ms" not exists
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 2 — Trigger the check. Handler dispatches through
|
||||
# `RecoverableAdapter` → `run_or_resume`, which INSERTs
|
||||
# a fresh `jobs.recoverable_runs` row, runs the scan,
|
||||
# marks it Completed. Response envelope:
|
||||
# { ok, outcome: { outcome: "ok",
|
||||
# extra: { completed: true, run_id: "..." } } }
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/admin/jobs/drives_consistency/trigger
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.ok" == true
|
||||
jsonpath "$.outcome.outcome" == "ok"
|
||||
jsonpath "$.outcome.extra.completed" == true
|
||||
[Captures]
|
||||
run_id: jsonpath "$.outcome.extra.run_id"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 3 — Run history returns at least the just-triggered
|
||||
# run, newest first. Response is a JSON array of
|
||||
# RunSummary; the top entry must be the run_id we
|
||||
# captured above with status='Completed'.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/admin/jobs/drives_consistency/runs
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$" isCollection
|
||||
jsonpath "$[0].id" == "{{run_id}}"
|
||||
jsonpath "$[0].job_name" == "drives_consistency"
|
||||
jsonpath "$[0].status" == "Completed"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 4 — Single-run detail. Returns the same row shape as
|
||||
# the listing but for one id.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/admin/jobs/drives_consistency/runs/{{run_id}}
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{run_id}}"
|
||||
jsonpath "$.job_name" == "drives_consistency"
|
||||
jsonpath "$.status" == "Completed"
|
||||
# scanned_count is bumped by the handler's checkpoint call — at
|
||||
# least 0 (empty drives table) but ordinarily > 0 for any real
|
||||
# fixture data. Present-ness of the field is what we pin.
|
||||
jsonpath "$.stats.scanned_count" isNumber
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 5 — Cancel-on-idle is a no-op. No Running row means no
|
||||
# Running→CancelRequested flip. Response is 200 with
|
||||
# `cancelled: false` (NOT a 404 — the job name is
|
||||
# registered, cancel just found nothing to cancel).
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/admin/jobs/drives_consistency/cancel
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.cancelled" == false
|
||||
jsonpath "$.reason" == "no running run for this job"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 6 — Unknown run id → 404. UUID shape is valid; the id
|
||||
# just isn't in `jobs.recoverable_runs`.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/admin/jobs/drives_consistency/runs/00000000-0000-0000-0000-000000000000
|
||||
Authorization: Bearer {{admin_token}}
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" == "run not found"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 7 — Non-admin caller is denied on every endpoint by
|
||||
# the `/api/admin/*` middleware layer. Handlers have
|
||||
# no bespoke role check — reaching them at all means
|
||||
# the caller is admin.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
POST {{base_url}}/api/admin/jobs/drives_consistency/trigger
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 403
|
||||
|
||||
|
||||
POST {{base_url}}/api/admin/jobs/drives_consistency/cancel
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 403
|
||||
|
||||
|
||||
GET {{base_url}}/api/admin/jobs/drives_consistency/runs
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 403
|
||||
|
||||
|
||||
GET {{base_url}}/api/admin/jobs/drives_consistency/runs/{{run_id}}
|
||||
Authorization: Bearer {{bob_token}}
|
||||
|
||||
HTTP 403
|
||||
@@ -167,6 +167,7 @@ hurl --variables-file "$API_DIR/test.env" --file-root "$REPO_ROOT/tests" --test
|
||||
"$API_DIR/dedup_blob_cleanup.hurl" \
|
||||
"$API_DIR/dedup_admin_gate.hurl" \
|
||||
"$API_DIR/admin_jobs.hurl" \
|
||||
"$API_DIR/recoverable_jobs.hurl" \
|
||||
"$API_DIR/default_caldav_carddav.hurl" \
|
||||
"$API_DIR/dav_error_mapping.hurl" \
|
||||
"$API_DIR/carddav_vcard_properties.hurl" \
|
||||
|
||||
Reference in New Issue
Block a user