FEAT: Update Status Of Video Row On Media Convert Update
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sns"
|
||||
)
|
||||
|
||||
// These scenarios stand in for EventBridge, not for a client: they publish the
|
||||
// same "MediaConvert Job State Change" event AWS would put on the topic when a
|
||||
// job changes state. That is the seam cms owns — the topic, the queue, the
|
||||
// consumer and the record it updates. The EventBridge rule that feeds the
|
||||
// topic in AWS is Pulumi configuration, and is not exercised here.
|
||||
//
|
||||
// This is the only place the suite reaches for the AWS SDK. Everything a real
|
||||
// client does still goes over plain HTTP.
|
||||
|
||||
const (
|
||||
defaultEndpointURL = "http://localhost.localstack.cloud:4566"
|
||||
defaultTopicARN = "arn:aws:sns:us-east-1:000000000000:mediaconvert-job-events"
|
||||
defaultRegion = "us-east-1"
|
||||
)
|
||||
|
||||
func envOr(name, fallback string) string {
|
||||
if v := strings.TrimSpace(os.Getenv(name)); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// jobStateChange is the EventBridge event MediaConvert emits. Only the members
|
||||
// cms reads are modelled; AWS sends a good deal more.
|
||||
type jobStateChange struct {
|
||||
Version string `json:"version"`
|
||||
ID string `json:"id"`
|
||||
DetailType string `json:"detail-type"`
|
||||
Source string `json:"source"`
|
||||
Account string `json:"account"`
|
||||
Time time.Time `json:"time"`
|
||||
Region string `json:"region"`
|
||||
Resources []string `json:"resources"`
|
||||
Detail jobStateDetail `json:"detail"`
|
||||
}
|
||||
|
||||
type jobStateDetail struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
AccountID string `json:"accountId"`
|
||||
Queue string `json:"queue"`
|
||||
JobID string `json:"jobId"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// eventPublisher puts job state changes on the topic cms's queue subscribes to.
|
||||
type eventPublisher struct {
|
||||
sns *sns.Client
|
||||
topicARN string
|
||||
}
|
||||
|
||||
func newEventPublisher(ctx context.Context) (*eventPublisher, error) {
|
||||
endpoint := envOr("AWS_ENDPOINT_URL", defaultEndpointURL)
|
||||
region := envOr("AWS_REGION", defaultRegion)
|
||||
|
||||
cfg, err := awsconfig.LoadDefaultConfig(ctx,
|
||||
awsconfig.WithRegion(region),
|
||||
// LocalStack accepts any credentials; these keep the SDK from hunting
|
||||
// for a profile that a developer's machine may not have.
|
||||
awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
|
||||
envOr("AWS_ACCESS_KEY_ID", "test"), envOr("AWS_SECRET_ACCESS_KEY", "test"), "")),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &eventPublisher{
|
||||
sns: sns.NewFromConfig(cfg, func(o *sns.Options) { o.BaseEndpoint = aws.String(endpoint) }),
|
||||
topicARN: envOr("MEDIACONVERT_EVENTS_TOPIC_ARN", defaultTopicARN),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *eventPublisher) publishJobState(ctx context.Context, jobID, state string) error {
|
||||
event := jobStateChange{
|
||||
Version: "0",
|
||||
ID: fmt.Sprintf("test-%d", time.Now().UnixNano()),
|
||||
DetailType: "MediaConvert Job State Change",
|
||||
Source: "aws.mediaconvert",
|
||||
Account: "000000000000",
|
||||
Time: time.Now().UTC(),
|
||||
Region: envOr("AWS_REGION", defaultRegion),
|
||||
Resources: []string{fmt.Sprintf("arn:aws:mediaconvert:%s:000000000000:jobs/%s", envOr("AWS_REGION", defaultRegion), jobID)},
|
||||
Detail: jobStateDetail{
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
AccountID: "000000000000",
|
||||
JobID: jobID,
|
||||
Status: state,
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.sns.Publish(ctx, &sns.PublishInput{
|
||||
TopicArn: aws.String(p.topicARN),
|
||||
Message: aws.String(string(body)),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("publishing to %s: %w", p.topicARN, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
Feature: Transcoding status
|
||||
A registered video starts life as "processing" and is handed to MediaConvert.
|
||||
MediaConvert reports every job state change to an SNS topic, which fans the
|
||||
event out to a queue cms consumes, so the catalogue record catches up with
|
||||
the transcoding job on its own — nobody polls AWS and nothing is exposed for
|
||||
AWS to call back into.
|
||||
|
||||
Background:
|
||||
Given the CMS API is available
|
||||
|
||||
Scenario: A finished job marks the video ready
|
||||
Given I have registered a video that is being transcoded
|
||||
When MediaConvert reports that the job reached "COMPLETE"
|
||||
Then the video eventually has status "ready"
|
||||
|
||||
Scenario Outline: A job that did not finish cleanly marks the video failed
|
||||
Given I have registered a video that is being transcoded
|
||||
When MediaConvert reports that the job reached "<state>"
|
||||
Then the video eventually has status "failed"
|
||||
|
||||
Examples:
|
||||
| state |
|
||||
| ERROR |
|
||||
| CANCELED |
|
||||
|
||||
Scenario Outline: A state that is not an outcome leaves the video processing
|
||||
Given I have registered a video that is being transcoded
|
||||
When MediaConvert reports that the job reached "<state>"
|
||||
Then the video keeps status "processing"
|
||||
|
||||
Examples:
|
||||
| state |
|
||||
| SUBMITTED |
|
||||
| PROGRESSING |
|
||||
| STATUS_UPDATE |
|
||||
|
||||
# A message naming a job nobody has must be consumed and dropped, not left to
|
||||
# redeliver forever. The second event is the assertion that it was: it can
|
||||
# only be handled if the first one did not wedge the consumer.
|
||||
Scenario: An event for a job no video has does not stop the consumer
|
||||
Given I have registered a video that is being transcoded
|
||||
When MediaConvert reports that the job "1700000000000-nosuchjob" reached "COMPLETE"
|
||||
And MediaConvert reports that the job reached "COMPLETE"
|
||||
Then the video eventually has status "ready"
|
||||
@@ -0,0 +1,52 @@
|
||||
Feature: Video details
|
||||
A registered video can be read back by its id. This is how a client learns
|
||||
what became of an upload once it was handed to the transcoding pipeline —
|
||||
without it, the status a video carries is written but never visible.
|
||||
|
||||
Background:
|
||||
Given the CMS API is available
|
||||
|
||||
Scenario: Reading back a video that was registered
|
||||
Given I have requested an upload slot for "detail.mp4" of type "video/mp4"
|
||||
And I have uploaded the file to the upload URL
|
||||
And I have registered the uploaded video titled "Inside the Newsroom" under categories "documentary, news"
|
||||
When I ask the CMS for that video
|
||||
Then the request succeeds with status 200
|
||||
And the video has an id
|
||||
And the video is registered with status "processing"
|
||||
And the video is stored under the key from the upload slot
|
||||
And the video is filed under categories "documentary, news"
|
||||
And the video keeps the metadata I sent
|
||||
|
||||
# A uuid has several accepted spellings, and Postgres takes only some of them
|
||||
# verbatim — the urn form it rejects outright. The handler parses the id and
|
||||
# queries with the canonical form, so every spelling names the same video
|
||||
# instead of some of them faulting.
|
||||
Scenario Outline: Reading a video by another accepted spelling of its id
|
||||
Given I have registered a video
|
||||
When I ask the CMS for that video with its id written <form>
|
||||
Then the request succeeds with status 200
|
||||
And the video has an id
|
||||
|
||||
Examples:
|
||||
| form |
|
||||
| braced |
|
||||
| unhyphenated |
|
||||
| as a urn |
|
||||
|
||||
Scenario: Asking for a video that does not exist
|
||||
When I ask the CMS for the video with id "0199f3a1-7c2e-7b21-9f0d-1a2b3c4d5e6f"
|
||||
Then the request is rejected with status 404
|
||||
And the problem title is "Video Not Found"
|
||||
|
||||
Scenario Outline: Asking for a video with an id that is not a video id
|
||||
When I ask the CMS for the video with id "<id>"
|
||||
Then the request is rejected with status 404
|
||||
And the problem title is "Video Not Found"
|
||||
|
||||
Examples:
|
||||
| id |
|
||||
| not-a-uuid |
|
||||
| 12345 |
|
||||
| 0199f3a1-7c2e-7b21-9f0d-1a2b3c4d5e6 |
|
||||
| '; DROP TABLE videos; -- |
|
||||
@@ -5,6 +5,21 @@ go 1.25.12
|
||||
require github.com/go-bdd/gobdd v1.1.4
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.44.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.39 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.43.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 // indirect
|
||||
github.com/aws/smithy-go v1.28.1 // indirect
|
||||
github.com/cucumber/gherkin/go/v33 v33.0.0 // indirect
|
||||
github.com/cucumber/messages/go/v28 v28.0.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
github.com/aws/aws-sdk-go-v2 v1.44.0 h1:4IbaHhtzy+4h37z4JQyO9a2QsiCml3CNYHtq5hIHigo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.44.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.40 h1:lAVC9gMmKusmqDRe32dPtgKl/BWvJmMJoWELKHCAObw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.40/go.mod h1:8xOJLbe/hOj1g4PVsfJYV7O2byq+UGET1onDdUgbwqc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.39 h1:XOg8LC3Kgnsa3WiPQjc7Bi8k5IBN92cPYfIV9XMFss0=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.39/go.mod h1:GonTDBQ+mTpCVNwaHjj0PagspfrYYMEqOx7FehoEP/I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40 h1:r5aGipEVgI9aT/tAGjdrPbDQvIAKdTrS3rUPQtG4Rmo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.40/go.mod h1:vOD3CnPxAdkL6MWZeROkZsTlskklMFfgVFkHzx/oZpY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40 h1:UIXlbijuB2XK1Kr57fo8iIxCuaSHJzwZ1uo+2tbEYIk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.40/go.mod h1:wcEsL6jscjZjVUinb0Q5qD/GXOG1yT3GNfmT9HuDwzU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40 h1:xLQVRDs2NddDmK9BEyh5KSlJ1Gpy5/GIJXrV6WcVGAE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.40/go.mod h1:XRXnpFVFGLaEVK+olDdFIM1vNa04ETW452oFGEPUxAo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41 h1:nv/ILuCY0yXACzMQwvtt/HbqDDjemZiI0AeDbxGQlnU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.41/go.mod h1:dzvOSpxaPqQ3j0xS6Lc1vyVuWW0RBj7s/QqYpzu3Q/0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40 h1:gr3Fw1cxZXNCdeo/lQ7isHEHzvHVM7z75qb2zW9aMjw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.40/go.mod h1:8z/9CmfnQhiuXD7Ykbcg4a/whSWsniE0ODSx9uwVzfk=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0 h1:agcr0j8YeFEzdXNo17Rg9MbbjLRjrimabwNtji4e+lU=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.6.0/go.mod h1:qU5PxgQ4JiUOOMotzfO3+5oUda5W+8JDVKyLQqlrJik=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.43.0 h1:VPYjwn0BoX34hb44OT8T+Ikgn4NzsN7fHetaHaevsDc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.43.0/go.mod h1:I1vnLPvvi9KBqxddu8nJ4vktoPJvaIG05UmjBD9sqm8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0 h1:FxaN8/sn61DTXNI6Gt678tFJUY8iUsCchm6Y/F/RjaA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.34.0/go.mod h1:vu4OY6s8LJtT8BtYG2LD6BGSZMptkYn3o5hvCPB22jc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0 h1:crWKPeGYTBTuBxQ3p73kjfJvt4brUIsr+Fuypko8FxY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.39.0/go.mod h1:HjjZVhaBz0JBR/kbWKThmNDhFKS7y6EURuk493tJk9Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0 h1:IZ63JdogSNNjex/jsODNv7jGDcO/xJYd9FsgyfCsp1g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.46.0/go.mod h1:I+rwAf3spG5dITBaAo3xXRowk8kiOhtU1kYxfvCTC44=
|
||||
github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ=
|
||||
github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/cucumber/gherkin/go/v33 v33.0.0 h1:PqQ81cHjD732/GZ7c/6k/sjlYG4LzLp51nggL6172Sg=
|
||||
github.com/cucumber/gherkin/go/v33 v33.0.0/go.mod h1:mnP4fdkoc+LmjSLi9Kq3M5D84GphRWLdgeKAlvUYp2c=
|
||||
github.com/cucumber/messages/go/v28 v28.0.0 h1:BOJmy8LKSbdKxM6Ba1v9ZmpZk7j5cyH+LTAaGxMeflc=
|
||||
|
||||
@@ -2,8 +2,11 @@ package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-bdd/gobdd"
|
||||
)
|
||||
@@ -352,3 +355,180 @@ func theVideoKeepsTheMetadataISent(t gobdd.StepTest, ctx gobdd.Context) {
|
||||
t.Errorf("expected the file name %q, got %q", w.sent.FileName, w.video.FileName)
|
||||
}
|
||||
}
|
||||
|
||||
func askForThatVideo(t gobdd.StepTest, ctx gobdd.Context) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
if w.video.ID == "" {
|
||||
t.Fatalf("no video has been registered, so there is no id to ask for")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := w.client.get("/api/videos/" + w.video.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("could not ask for the video: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.last = result
|
||||
w.video = videoBody{}
|
||||
|
||||
if result.status == 200 {
|
||||
if err := result.json(&w.video); err != nil {
|
||||
t.Fatalf("could not decode the video: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func askForVideoWithID(t gobdd.StepTest, ctx gobdd.Context, id string) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
result, err := w.client.get("/api/videos/" + url.PathEscape(id))
|
||||
if err != nil {
|
||||
t.Fatalf("could not ask for the video: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.last = result
|
||||
w.video = videoBody{}
|
||||
}
|
||||
|
||||
// --- transcoding status -----------------------------------------------------
|
||||
|
||||
// statusSettleTimeout bounds how long a scenario waits for the consumer to
|
||||
// pick an event off the queue and write the record. The consumer long-polls,
|
||||
// so in practice this resolves in well under a second; the budget is for a
|
||||
// loaded machine, not for a slow path.
|
||||
const (
|
||||
statusSettleTimeout = 20 * time.Second
|
||||
statusPollInterval = 200 * time.Millisecond
|
||||
statusHoldWindow = 3 * time.Second
|
||||
)
|
||||
|
||||
func registerVideoBeingTranscoded(t gobdd.StepTest, ctx gobdd.Context) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
requestUploadSlot(t, ctx, "transcoding.mp4", "video/mp4")
|
||||
uploadTheFile(t, ctx)
|
||||
registerUploadedVideo(t, ctx, "A Video Being Transcoded", "other")
|
||||
|
||||
if w.last.status != 201 {
|
||||
t.Fatalf("could not register a video to transcode: %s", w.last.summary())
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(w.video.MediaConvertJobID) == "" {
|
||||
t.Fatalf("the registered video carries no transcoding job id, so no event could name it")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func mediaConvertReportsState(t gobdd.StepTest, ctx gobdd.Context, state string) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
if strings.TrimSpace(w.video.MediaConvertJobID) == "" {
|
||||
t.Fatalf("no transcoding job has been started, so there is no job to report on")
|
||||
return
|
||||
}
|
||||
publishJobState(t, w.video.MediaConvertJobID, state)
|
||||
}
|
||||
|
||||
func mediaConvertReportsStateForJob(t gobdd.StepTest, ctx gobdd.Context, jobID, state string) {
|
||||
worldOf(t, ctx) // keeps the step honest about needing a scenario world
|
||||
publishJobState(t, jobID, state)
|
||||
}
|
||||
|
||||
func publishJobState(t gobdd.StepTest, jobID, state string) {
|
||||
background := context.Background()
|
||||
|
||||
publisher, err := newEventPublisher(background)
|
||||
if err != nil {
|
||||
t.Fatalf("could not reach the job events topic: %s", err)
|
||||
return
|
||||
}
|
||||
if err := publisher.publishJobState(background, jobID, state); err != nil {
|
||||
t.Fatalf("could not publish the job state change: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func theVideoEventuallyHasStatus(t gobdd.StepTest, ctx gobdd.Context, want string) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
deadline := time.Now().Add(statusSettleTimeout)
|
||||
last := ""
|
||||
for time.Now().Before(deadline) {
|
||||
last = currentStatus(t, w)
|
||||
if last == want {
|
||||
return
|
||||
}
|
||||
time.Sleep(statusPollInterval)
|
||||
}
|
||||
|
||||
t.Errorf("the video never reached status %q within %s; it is still %q", want, statusSettleTimeout, last)
|
||||
}
|
||||
|
||||
func theVideoKeepsStatus(t gobdd.StepTest, ctx gobdd.Context, want string) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
// Held rather than sampled once: the consumer is asynchronous, so a status
|
||||
// that is still "processing" the instant after publishing proves nothing.
|
||||
deadline := time.Now().Add(statusHoldWindow)
|
||||
for time.Now().Before(deadline) {
|
||||
if got := currentStatus(t, w); got != want {
|
||||
t.Errorf("the video moved to status %q, but this event should have left it %q", got, want)
|
||||
return
|
||||
}
|
||||
time.Sleep(statusPollInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// currentStatus re-reads the video over the API. It deliberately does not
|
||||
// touch w.last: these steps assert on the record, not on an exchange.
|
||||
func currentStatus(t gobdd.StepTest, w *world) string {
|
||||
result, err := w.client.get("/api/videos/" + w.video.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("could not re-read the video: %s", err)
|
||||
return ""
|
||||
}
|
||||
if result.status != 200 {
|
||||
t.Fatalf("could not re-read the video: %s", result.summary())
|
||||
return ""
|
||||
}
|
||||
|
||||
var body videoBody
|
||||
if err := result.json(&body); err != nil {
|
||||
t.Fatalf("could not decode the video: %s", err)
|
||||
return ""
|
||||
}
|
||||
return body.Status
|
||||
}
|
||||
|
||||
// askForThatVideoWrittenAs re-reads the video by a different, still-valid
|
||||
// spelling of the same uuid.
|
||||
func askForThatVideoWrittenAs(t gobdd.StepTest, ctx gobdd.Context, form string) {
|
||||
w := worldOf(t, ctx)
|
||||
|
||||
if w.video.ID == "" {
|
||||
t.Fatalf("no video has been registered, so there is no id to rewrite")
|
||||
return
|
||||
}
|
||||
|
||||
var id string
|
||||
switch form {
|
||||
case "braced":
|
||||
id = "{" + w.video.ID + "}"
|
||||
case "unhyphenated":
|
||||
id = strings.ReplaceAll(w.video.ID, "-", "")
|
||||
case "as a urn":
|
||||
id = "urn:uuid:" + w.video.ID
|
||||
default:
|
||||
t.Fatalf("no such spelling of a uuid: %q", form)
|
||||
return
|
||||
}
|
||||
|
||||
askForVideoWithID(t, ctx, id)
|
||||
if w.last.status == 200 {
|
||||
if err := w.last.json(&w.video); err != nil {
|
||||
t.Fatalf("could not decode the video: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,15 @@ func TestVideoUpload(t *testing.T) {
|
||||
suite.AddStep(`^I request an upload slot for "(.*)" of type "(.*)"$`, requestUploadSlot)
|
||||
suite.AddStep(`^I upload the file to the upload URL$`, uploadTheFile)
|
||||
suite.AddStep(`^I send malformed JSON to the upload slot endpoint$`, sendMalformedJSON)
|
||||
suite.AddStep(`^I ask the CMS for that video$`, askForThatVideo)
|
||||
suite.AddStep(`^I ask the CMS for the video with id "(.*)"$`, askForVideoWithID)
|
||||
suite.AddStep(`^I have registered a video that is being transcoded$`, registerVideoBeingTranscoded)
|
||||
suite.AddStep(`^I have registered a video$`, registerVideoBeingTranscoded)
|
||||
suite.AddStep(`^I ask the CMS for that video with its id written (.*)$`, askForThatVideoWrittenAs)
|
||||
suite.AddStep(`^MediaConvert reports that the job reached "(.*)"$`, mediaConvertReportsState)
|
||||
suite.AddStep(`^MediaConvert reports that the job "(.*)" reached "(.*)"$`, mediaConvertReportsStateForJob)
|
||||
suite.AddStep(`^the video eventually has status "(.*)"$`, theVideoEventuallyHasStatus)
|
||||
suite.AddStep(`^the video keeps status "(.*)"$`, theVideoKeepsStatus)
|
||||
suite.AddStep(`^I register the uploaded video titled "(.*)" under categories "(.*)"$`, registerUploadedVideo)
|
||||
suite.AddStep(`^I register the uploaded video titled "(.*)" under category id (\d+)$`, registerUnderCategoryID)
|
||||
suite.AddStep(`^I register a video titled "(.*)" with the key "(.*)" under categories "(.*)"$`, registerWithKey)
|
||||
|
||||
Reference in New Issue
Block a user