149 lines
7.3 KiB
Plaintext
149 lines
7.3 KiB
Plaintext
|
|
# =============================================================
|
|||
|
|
# OxiCloud – transcode_import: registration, contract, empty-tree run
|
|||
|
|
#
|
|||
|
|
# ## What this covers, and what it deliberately does not
|
|||
|
|
#
|
|||
|
|
# It covers the job's SURFACE: that it is registered, that it declares
|
|||
|
|
# the metadata the admin panel switches on, and that a run against an
|
|||
|
|
# already-drained tree completes cleanly with zeroed counters rather
|
|||
|
|
# than erroring or reporting phantom work.
|
|||
|
|
#
|
|||
|
|
# It does NOT cover the re-keying — the part that matters most, where
|
|||
|
|
# `{file_id}.webp` is resolved through `storage.files` to a content
|
|||
|
|
# hash, and several entries naming the same content collapse into one
|
|||
|
|
# row. That needs `.transcoded/webp/` entries on disk before the run,
|
|||
|
|
# and nothing reachable over HTTP can create them: since the write path
|
|||
|
|
# moved to the derived tier, only callers with no content hash
|
|||
|
|
# (external mounts) still write there, and hurl cannot place files in
|
|||
|
|
# the server's storage directory either.
|
|||
|
|
#
|
|||
|
|
# So the migration itself is validated by a snapshot restore against a
|
|||
|
|
# populated `.transcoded/`, the same way the thumbnail migration was —
|
|||
|
|
# an exercise that found three bugs the test suite did not. This file
|
|||
|
|
# guards the contract that surrounds it; it is not a substitute.
|
|||
|
|
#
|
|||
|
|
# The empty-tree assertions are still worth having. A drained tree is
|
|||
|
|
# the steady state of every install after the first boot, so this is
|
|||
|
|
# what the job does on the overwhelming majority of its runs, and
|
|||
|
|
# "does nothing, quietly, forever" is a real property — the thumbnail
|
|||
|
|
# equivalent warned on every boot for exactly this case until it was
|
|||
|
|
# caught by eye.
|
|||
|
|
#
|
|||
|
|
# Prerequisites: setup.hurl must have run (admin user exists).
|
|||
|
|
#
|
|||
|
|
# Run:
|
|||
|
|
# hurl --variables-file tests/api/test.env --file-root tests \
|
|||
|
|
# --test tests/api/transcode_import.hurl
|
|||
|
|
# =============================================================
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
# Step 1 – Login
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
POST {{base_url}}/api/auth/login
|
|||
|
|
Content-Type: application/json
|
|||
|
|
{
|
|||
|
|
"username": "{{username}}",
|
|||
|
|
"password": "{{password}}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HTTP 200
|
|||
|
|
[Captures]
|
|||
|
|
token: jsonpath "$.access_token"
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
# Step 2 – The job is registered, and declares itself correctly.
|
|||
|
|
#
|
|||
|
|
# The admin panel keys its read-only badge and its repair toggle off
|
|||
|
|
# these three fields, so a job that stops declaring them degrades the
|
|||
|
|
# UI silently: `mutates` wrong means a destructive job renders as safe,
|
|||
|
|
# and a missing `repair_description` removes the only way to reach the
|
|||
|
|
# deletion from the interface.
|
|||
|
|
#
|
|||
|
|
# `always` because a plain run inserts rows and writes blobs — the
|
|||
|
|
# repair flag adds deletion on top, which is why the two are
|
|||
|
|
# independent rather than one implying the other.
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
GET {{base_url}}/api/admin/jobs
|
|||
|
|
Authorization: Bearer {{token}}
|
|||
|
|
|
|||
|
|
HTTP 200
|
|||
|
|
[Asserts]
|
|||
|
|
jsonpath "$[*].name" contains "transcode_import"
|
|||
|
|
jsonpath "$[?(@.name=='transcode_import')].mutates" == "always"
|
|||
|
|
jsonpath "$[?(@.name=='transcode_import')].recoverable" == true
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
# Step 3 – A discovery-only run over a drained tree.
|
|||
|
|
#
|
|||
|
|
# No `?repair=true`: the default must import without deleting, which is
|
|||
|
|
# what the daily-tick and the no-silent-auto-repair rule both depend
|
|||
|
|
# on. Counters are asserted to exact zeros rather than "exists" — a
|
|||
|
|
# non-zero here would mean the job found work in a directory the API
|
|||
|
|
# cannot have put anything into, which is the shape a re-keying bug
|
|||
|
|
# would take.
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
POST {{base_url}}/api/admin/jobs/transcode_import/trigger
|
|||
|
|
Authorization: Bearer {{token}}
|
|||
|
|
|
|||
|
|
HTTP 200
|
|||
|
|
[Asserts]
|
|||
|
|
jsonpath "$.ok" == true
|
|||
|
|
jsonpath "$.outcome.outcome" == "ok"
|
|||
|
|
jsonpath "$.outcome.extra.completed" == true
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.imported" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.negatives" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.file_gone" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.deleted" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.failed" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.unverified" == 0
|
|||
|
|
# A clean run raises no findings. `file_gone` entries are recorded as
|
|||
|
|
# anomalies, so a non-zero count here would contradict the zero above.
|
|||
|
|
jsonpath "$.outcome.extra.finding_count" == 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
# Step 4 – The same run under `?repair=true`.
|
|||
|
|
#
|
|||
|
|
# On a drained tree this must be indistinguishable from the run above:
|
|||
|
|
# nothing to import, nothing to delete, and — the part worth pinning —
|
|||
|
|
# no complaint about the directory being absent. The thumbnail teardown
|
|||
|
|
# warned `could not be removed / No such file or directory` on every
|
|||
|
|
# boot after its migration finished, warning about success forever.
|
|||
|
|
# Absence is the end state, not a failure, and the job has to treat it
|
|||
|
|
# that way because this is what every run after the first looks like.
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
POST {{base_url}}/api/admin/jobs/transcode_import/trigger?repair=true
|
|||
|
|
Authorization: Bearer {{token}}
|
|||
|
|
|
|||
|
|
HTTP 200
|
|||
|
|
[Asserts]
|
|||
|
|
jsonpath "$.ok" == true
|
|||
|
|
jsonpath "$.outcome.outcome" == "ok"
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.imported" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.negatives" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.deleted" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.failed" == 0
|
|||
|
|
jsonpath "$.outcome.extra.finding_count" == 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
# Step 5 – Idempotence.
|
|||
|
|
#
|
|||
|
|
# The job is registered as a daily tick AND named in
|
|||
|
|
# OXICLOUD_STARTUP_JOBS, so on a long-lived install it runs
|
|||
|
|
# unattended, repeatedly, forever. Re-running must stay a no-op —
|
|||
|
|
# anything that accumulated across runs would accumulate unattended.
|
|||
|
|
# ─────────────────────────────────────────────────────────────
|
|||
|
|
POST {{base_url}}/api/admin/jobs/transcode_import/trigger
|
|||
|
|
Authorization: Bearer {{token}}
|
|||
|
|
|
|||
|
|
HTTP 200
|
|||
|
|
[Asserts]
|
|||
|
|
jsonpath "$.outcome.outcome" == "ok"
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.imported" == 0
|
|||
|
|
jsonpath "$.outcome.extra.extra_stats.negatives" == 0
|
|||
|
|
jsonpath "$.outcome.extra.finding_count" == 0
|