121 lines
5.6 KiB
Markdown
121 lines
5.6 KiB
Markdown
# tests
|
|
|
|
BDD scenarios for the video upload feature, written in Gherkin and run by
|
|
[gobdd](https://github.com/go-bdd/gobdd).
|
|
|
|
The suite is **black box**: it speaks nothing but HTTP to a running `cms`, so
|
|
the same scenarios run against the local docker-compose stack and against a
|
|
deployed environment. It is its own Go module (`thamanyah/tests`) and imports
|
|
nothing from `cms/` — the API is the contract under test, not the code.
|
|
|
|
The upload to storage is a plain `PUT` to the presigned URL the API hands out,
|
|
with no AWS SDK involved, because that is exactly what a real client does.
|
|
|
|
```
|
|
features/*.feature the scenarios, in Gherkin
|
|
suite_test.go the gobdd suite: step registration + per-scenario world
|
|
steps_test.go what each step does
|
|
client_test.go HTTP plumbing and the wire types
|
|
```
|
|
|
|
## Running
|
|
|
|
The suite needs a live `cms` with its database migrated and its buckets
|
|
provisioned. From the repo root:
|
|
|
|
```bash
|
|
docker compose up -d # localstack + infra + cms
|
|
docker exec cms ./cms migrate # see "Migrations" below
|
|
cd tests && go test ./...
|
|
```
|
|
|
|
If nothing is serving, the suite **skips** rather than fails, so `go test ./...`
|
|
in a fresh checkout stays green.
|
|
|
|
Point it at another environment with `CMS_BASE_URL` (default
|
|
`http://localhost:8081`):
|
|
|
|
```bash
|
|
CMS_BASE_URL=https://cms.example.com go test -v ./...
|
|
```
|
|
|
|
Use `-v` to see the Gherkin: gobdd nests a Go subtest per feature, scenario and
|
|
step, so the step text shows up in the test output.
|
|
|
|
### Migrations
|
|
|
|
`docker-compose.yml` starts `cms` in server mode only. The migrate container
|
|
that runs `./cms migrate` to completion before the service starts exists in the
|
|
ECS task definition (`runMigrations` in `infrastructure/main.go`), not in
|
|
compose — so a freshly created local database has no schema and every scenario
|
|
fails with `relation "categories" does not exist`. Run `docker exec cms ./cms
|
|
migrate` once after the stack first comes up.
|
|
|
|
## What the scenarios cover
|
|
|
|
Both steps of the upload flow, end to end:
|
|
|
|
- listing the categories a video can be filed under, and the shape of that list
|
|
- issuing an upload slot for MP4 and QuickTime, including that the storage key
|
|
is generated rather than taken from the client's file name
|
|
- refusing a slot for every other media type, and for a body that is not JSON
|
|
- uploading the file to the presigned URL and registering it: the record comes
|
|
back `processing`, under the key from the slot, with a transcoding job id and
|
|
the categories and metadata that were sent
|
|
- refusing to register: a file that was never uploaded, a missing title, no
|
|
category, and a storage key this API never issued
|
|
- answering 404 for a category id no category has, kept distinct from the 500
|
|
the category lookup itself failing produces
|
|
- answering 409 for a key that was already registered, rather than queueing a
|
|
second transcoding job for the same file
|
|
- announcing a video on the catalogue topic once its job comes back COMPLETE,
|
|
carrying its id, title, playback URL and the **names** of its categories —
|
|
and announcing nothing for a job that ended in an error
|
|
|
|
- the read side building its catalogue from those announcements: the video
|
|
turns up in discovery under the id cms issued, a video still transcoding is
|
|
not there at all, and a video announced a second time is updated rather than
|
|
colliding with its own row
|
|
|
|
- searching that catalogue over `GET /api/videos`: finding a video by a word
|
|
in its title and by a part-typed one, narrowing to a category and being left
|
|
out of another, several categories meaning "any of", paging by cursor so the
|
|
pages tile the results exactly once, refusing a cursor no search issued, and
|
|
capping a page size that asks for the whole catalogue
|
|
|
|
Search scenarios give every video they catalogue a nonce in its title, and
|
|
assert relative to the video they created ("returns that video") rather than on
|
|
absolute counts. Nothing cleans up between runs, so a fixed title accumulates a
|
|
copy per run and a count would stop meaning anything. The paging scenario shares
|
|
one nonce across its videos so that searching for it matches that run's cohort
|
|
and nothing else.
|
|
|
|
The publication scenarios read `tests-catalogue-events`, the suite's **own**
|
|
queue on the catalogue topic — not discovery's. A consumer destroys what it
|
|
reads, so sharing discovery's queue would have the suite and the running
|
|
service race for every announcement and each see about half. Delivery is raw,
|
|
so a message body is the announcement with no SNS envelope.
|
|
|
|
The ingestion scenarios need `discovery` up as well as `cms`; they read it over
|
|
HTTP at `DISCOVERY_BASE_URL` (default `http://localhost:8080`).
|
|
Like the job-state publisher they stand in for AWS, so they are the second
|
|
place the suite reaches for the AWS SDK; everything a real client does still
|
|
goes over plain HTTP.
|
|
|
|
## Adding a scenario
|
|
|
|
Write it in `features/`. If it needs a step that does not exist yet, add the
|
|
function to `steps_test.go` and register it in `TestVideoUpload`. Two things to
|
|
know about gobdd:
|
|
|
|
- Step patterns are matched **unanchored**, and the first registered pattern
|
|
that matches wins. Anchor every pattern with `^…$`, as the existing ones do,
|
|
or a new step can silently shadow an old one.
|
|
- State moves between steps through the `*world` in the gobdd context, not
|
|
package variables — gobdd clones the context between the `Background` and the
|
|
scenario, so the world is held by pointer.
|
|
|
|
Scenarios write real rows and real objects, and nothing cleans up after them.
|
|
That is fine against LocalStack, which is disposable; think twice before
|
|
pointing the suite at anything that is not.
|