Add create show endpoint with validation and error handling
This commit is contained in:
parent
9ef61fe8a6
commit
338403d80d
@ -171,6 +171,50 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Insert a new show into ` + "`" + `current` + "`" + `.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"shows"
|
||||||
|
],
|
||||||
|
"summary": "Create show",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "New show payload",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.CreateShowReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.CurrentResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid payload or invalid time/duration",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.HTTPError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "create failed",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.HTTPError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "Delete a row from ` + "`" + `current` + "`" + ` by ID.",
|
"description": "Delete a row from ` + "`" + `current` + "`" + ` by ID.",
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -217,14 +261,72 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"httpapi.CreateShowReq": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ep_num",
|
||||||
|
"ep_title",
|
||||||
|
"playback_length",
|
||||||
|
"season_name",
|
||||||
|
"start_time"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ep_num": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"ep_title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Pilot"
|
||||||
|
},
|
||||||
|
"playback_length": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "00:24:00"
|
||||||
|
},
|
||||||
|
"season_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Season 1"
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "10:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"httpapi.CurrentResponse": {
|
"httpapi.CurrentResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"current_ep": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"date_created": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "2024-02-01T15:04:05Z"
|
||||||
|
},
|
||||||
|
"ep_num": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"ep_title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Pilot"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"playback_length": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "00:24:00"
|
||||||
|
},
|
||||||
|
"season_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Season 1"
|
||||||
},
|
},
|
||||||
"start_time": {
|
"start_time": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"example": "10:00:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -238,7 +340,19 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpapi.MoveReq": {
|
"httpapi.MoveReq": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"httpapi.MoveRes": {
|
"httpapi.MoveRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -169,6 +169,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Insert a new show into `current`.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"shows"
|
||||||
|
],
|
||||||
|
"summary": "Create show",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "New show payload",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.CreateShowReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.CurrentResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid payload or invalid time/duration",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.HTTPError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "create failed",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/httpapi.HTTPError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"delete": {
|
"delete": {
|
||||||
"description": "Delete a row from `current` by ID.",
|
"description": "Delete a row from `current` by ID.",
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -215,14 +259,72 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"httpapi.CreateShowReq": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"ep_num",
|
||||||
|
"ep_title",
|
||||||
|
"playback_length",
|
||||||
|
"season_name",
|
||||||
|
"start_time"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"ep_num": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"ep_title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Pilot"
|
||||||
|
},
|
||||||
|
"playback_length": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "00:24:00"
|
||||||
|
},
|
||||||
|
"season_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Season 1"
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "10:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"httpapi.CurrentResponse": {
|
"httpapi.CurrentResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"current_ep": {
|
||||||
|
"type": "boolean",
|
||||||
|
"example": false
|
||||||
|
},
|
||||||
|
"date_created": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "2024-02-01T15:04:05Z"
|
||||||
|
},
|
||||||
|
"ep_num": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"ep_title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Pilot"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"playback_length": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "00:24:00"
|
||||||
|
},
|
||||||
|
"season_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Season 1"
|
||||||
},
|
},
|
||||||
"start_time": {
|
"start_time": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"example": "10:00:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -236,7 +338,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpapi.MoveReq": {
|
"httpapi.MoveReq": {
|
||||||
"type": "object"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 123
|
||||||
|
},
|
||||||
|
"ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"httpapi.MoveRes": {
|
"httpapi.MoveRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -1,10 +1,54 @@
|
|||||||
basePath: /
|
basePath: /
|
||||||
definitions:
|
definitions:
|
||||||
|
httpapi.CreateShowReq:
|
||||||
|
properties:
|
||||||
|
ep_num:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
ep_title:
|
||||||
|
example: Pilot
|
||||||
|
type: string
|
||||||
|
playback_length:
|
||||||
|
example: "00:24:00"
|
||||||
|
type: string
|
||||||
|
season_name:
|
||||||
|
example: Season 1
|
||||||
|
type: string
|
||||||
|
start_time:
|
||||||
|
example: "10:00:00"
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ep_num
|
||||||
|
- ep_title
|
||||||
|
- playback_length
|
||||||
|
- season_name
|
||||||
|
- start_time
|
||||||
|
type: object
|
||||||
httpapi.CurrentResponse:
|
httpapi.CurrentResponse:
|
||||||
properties:
|
properties:
|
||||||
id:
|
current_ep:
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
|
date_created:
|
||||||
|
example: "2024-02-01T15:04:05Z"
|
||||||
|
type: string
|
||||||
|
ep_num:
|
||||||
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
ep_title:
|
||||||
|
example: Pilot
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
example: 123
|
||||||
|
type: integer
|
||||||
|
playback_length:
|
||||||
|
example: "00:24:00"
|
||||||
|
type: string
|
||||||
|
season_name:
|
||||||
|
example: Season 1
|
||||||
|
type: string
|
||||||
start_time:
|
start_time:
|
||||||
|
example: "10:00:00"
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
httpapi.HTTPError:
|
httpapi.HTTPError:
|
||||||
@ -14,6 +58,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
httpapi.MoveReq:
|
httpapi.MoveReq:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
example: 123
|
||||||
|
type: integer
|
||||||
|
ids:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
httpapi.MoveRes:
|
httpapi.MoveRes:
|
||||||
properties:
|
properties:
|
||||||
@ -187,6 +239,35 @@ paths:
|
|||||||
summary: List current shows
|
summary: List current shows
|
||||||
tags:
|
tags:
|
||||||
- shows
|
- shows
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Insert a new show into `current`.
|
||||||
|
parameters:
|
||||||
|
- description: New show payload
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.CreateShowReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.CurrentResponse'
|
||||||
|
"400":
|
||||||
|
description: invalid payload or invalid time/duration
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.HTTPError'
|
||||||
|
"500":
|
||||||
|
description: create failed
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/httpapi.HTTPError'
|
||||||
|
summary: Create show
|
||||||
|
tags:
|
||||||
|
- shows
|
||||||
schemes:
|
schemes:
|
||||||
- http
|
- http
|
||||||
- https
|
- https
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("episode not found")
|
ErrNotFound = errors.New("episode not found")
|
||||||
ErrInvalidStartTime = errors.New("invalid start_time (expected HH:MM:SS)")
|
ErrInvalidStartTime = errors.New("invalid start_time (expected HH:MM:SS)")
|
||||||
|
ErrInvalidPlayback = errors.New("invalid playback_length (expected HH:MM:SS and > 00:00:00)")
|
||||||
|
ErrInvalidShowInput = errors.New("invalid show payload")
|
||||||
ErrEmptyIDs = errors.New("ids must not be empty")
|
ErrEmptyIDs = errors.New("ids must not be empty")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,9 +22,19 @@ type Episode struct {
|
|||||||
SeasonName string `json:"season_name"`
|
SeasonName string `json:"season_name"`
|
||||||
StartTime string `json:"start_time"`
|
StartTime string `json:"start_time"`
|
||||||
PlaybackLength string `json:"playback_length"`
|
PlaybackLength string `json:"playback_length"`
|
||||||
|
CurrentEp bool `json:"current_ep"`
|
||||||
DateCreated time.Time `json:"date_created"`
|
DateCreated time.Time `json:"date_created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewShowInput is the payload needed to create a new show/episode.
|
||||||
|
type NewShowInput struct {
|
||||||
|
EpNum int `json:"ep_num"`
|
||||||
|
EpTitle string `json:"ep_title"`
|
||||||
|
SeasonName string `json:"season_name"`
|
||||||
|
StartTime string `json:"start_time"`
|
||||||
|
PlaybackLength string `json:"playback_length"`
|
||||||
|
}
|
||||||
|
|
||||||
// MoveResult describes what happened during an archive move operation.
|
// MoveResult describes what happened during an archive move operation.
|
||||||
type MoveResult struct {
|
type MoveResult struct {
|
||||||
MovedIDs []int64
|
MovedIDs []int64
|
||||||
@ -34,6 +46,7 @@ type MoveResult struct {
|
|||||||
type Repository interface {
|
type Repository interface {
|
||||||
GetCurrent(ctx context.Context) (Episode, error)
|
GetCurrent(ctx context.Context) (Episode, error)
|
||||||
SetCurrent(ctx context.Context, id int64, startHHMMSS string) error
|
SetCurrent(ctx context.Context, id int64, startHHMMSS string) error
|
||||||
|
Create(ctx context.Context, in NewShowInput) (Episode, error)
|
||||||
MoveToArchive(ctx context.Context, ids []int64) (MoveResult, error)
|
MoveToArchive(ctx context.Context, ids []int64) (MoveResult, error)
|
||||||
ListAll(ctx context.Context) ([]Episode, error)
|
ListAll(ctx context.Context) ([]Episode, error)
|
||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import "context"
|
|||||||
type UseCases interface {
|
type UseCases interface {
|
||||||
GetCurrent(ctx context.Context) (Episode, error)
|
GetCurrent(ctx context.Context) (Episode, error)
|
||||||
SetCurrent(ctx context.Context, id int64, start string) (Episode, error)
|
SetCurrent(ctx context.Context, id int64, start string) (Episode, error)
|
||||||
|
Create(ctx context.Context, in NewShowInput) (Episode, error)
|
||||||
MoveToArchive(ctx context.Context, ids []int64) (MoveResult, error)
|
MoveToArchive(ctx context.Context, ids []int64) (MoveResult, error)
|
||||||
ListAll(ctx context.Context) ([]Episode, error)
|
ListAll(ctx context.Context) ([]Episode, error)
|
||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
|||||||
@ -24,6 +24,7 @@ func NewRouter(svc episode.UseCases) *gin.Engine {
|
|||||||
v1.GET("/current", getCurrentHandler(svc))
|
v1.GET("/current", getCurrentHandler(svc))
|
||||||
v1.POST("/current", setCurrentHandler(svc))
|
v1.POST("/current", setCurrentHandler(svc))
|
||||||
v1.POST("/archive", moveToArchiveHandler(svc))
|
v1.POST("/archive", moveToArchiveHandler(svc))
|
||||||
|
v1.POST("/shows", createShowHandler(svc))
|
||||||
v1.GET("/shows", listShowsHandler(svc))
|
v1.GET("/shows", listShowsHandler(svc))
|
||||||
v1.DELETE("/shows", deleteShowsHandler(svc))
|
v1.DELETE("/shows", deleteShowsHandler(svc))
|
||||||
|
|
||||||
@ -105,6 +106,51 @@ func setCurrentHandler(svc episode.UseCases) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createShowHandler godoc
|
||||||
|
// @Summary Create show
|
||||||
|
// @Description Insert a new show into `current`.
|
||||||
|
// @Tags shows
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body CreateShowReq true "New show payload"
|
||||||
|
// @Success 201 {object} CurrentResponse
|
||||||
|
// @Failure 400 {object} HTTPError "invalid payload or invalid time/duration"
|
||||||
|
// @Failure 500 {object} HTTPError "create failed"
|
||||||
|
// @Router /api/v1/shows [post]
|
||||||
|
func createShowHandler(svc episode.UseCases) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req CreateShowReq
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
in := episode.NewShowInput{
|
||||||
|
EpNum: req.EpNum,
|
||||||
|
EpTitle: req.EpTitle,
|
||||||
|
SeasonName: req.SeasonName,
|
||||||
|
StartTime: req.StartTime,
|
||||||
|
PlaybackLength: req.PlaybackLength,
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := svc.Create(c.Request.Context(), in)
|
||||||
|
if err != nil {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, episode.ErrInvalidStartTime),
|
||||||
|
errors.Is(err, episode.ErrInvalidPlayback),
|
||||||
|
errors.Is(err, episode.ErrInvalidShowInput):
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// moveToArchiveHandler godoc
|
// moveToArchiveHandler godoc
|
||||||
// @Summary Move shows to archive
|
// @Summary Move shows to archive
|
||||||
// @Description Move one or many IDs from `current` to `current_archive`.
|
// @Description Move one or many IDs from `current` to `current_archive`.
|
||||||
|
|||||||
@ -28,10 +28,13 @@ type fakeSvc struct {
|
|||||||
listRes []episode.Episode
|
listRes []episode.Episode
|
||||||
listErr error
|
listErr error
|
||||||
deleteErr error
|
deleteErr error
|
||||||
|
createRes episode.Episode
|
||||||
|
createErr error
|
||||||
lastSetID int64
|
lastSetID int64
|
||||||
lastTime string
|
lastTime string
|
||||||
lastMove []int64
|
lastMove []int64
|
||||||
lastDelID int64
|
lastDelID int64
|
||||||
|
lastCreate episode.NewShowInput
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeSvc) GetCurrent(ctx context.Context) (episode.Episode, error) {
|
func (f *fakeSvc) GetCurrent(ctx context.Context) (episode.Episode, error) {
|
||||||
@ -55,6 +58,10 @@ func (f *fakeSvc) Delete(ctx context.Context, id int64) error {
|
|||||||
f.lastDelID = id
|
f.lastDelID = id
|
||||||
return f.deleteErr
|
return f.deleteErr
|
||||||
}
|
}
|
||||||
|
func (f *fakeSvc) Create(ctx context.Context, in episode.NewShowInput) (episode.Episode, error) {
|
||||||
|
f.lastCreate = in
|
||||||
|
return f.createRes, f.createErr
|
||||||
|
}
|
||||||
|
|
||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|
||||||
@ -204,6 +211,91 @@ func TestPostArchive_SingleAndMultiple_OK(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPostShows_BadPayload(t *testing.T) {
|
||||||
|
r := newRouterWithSvc(&fakeSvc{})
|
||||||
|
w := doJSON(t, r, http.MethodPost, "/api/v1/shows", map[string]any{"ep_title": "Pilot"})
|
||||||
|
if w.Code != http.StatusBadRequest {
|
||||||
|
t.Fatalf("expected 400, got %d", w.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostShows_ValidationErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
code int
|
||||||
|
payload any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bad start",
|
||||||
|
err: episode.ErrInvalidStartTime,
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
payload: map[string]any{
|
||||||
|
"ep_num": 1, "ep_title": "Pilot", "season_name": "S1", "start_time": "1:0:0", "playback_length": "00:24:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad playback",
|
||||||
|
err: episode.ErrInvalidPlayback,
|
||||||
|
code: http.StatusBadRequest,
|
||||||
|
payload: map[string]any{
|
||||||
|
"ep_num": 1, "ep_title": "Pilot", "season_name": "S1", "start_time": "10:00:00", "playback_length": "0:0:0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service error",
|
||||||
|
err: errors.New("db down"),
|
||||||
|
code: http.StatusInternalServerError,
|
||||||
|
payload: map[string]any{
|
||||||
|
"ep_num": 1, "ep_title": "Pilot", "season_name": "S1", "start_time": "10:00:00", "playback_length": "00:24:00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
svc := &fakeSvc{createErr: tt.err}
|
||||||
|
r := newRouterWithSvc(svc)
|
||||||
|
w := doJSON(t, r, http.MethodPost, "/api/v1/shows", tt.payload)
|
||||||
|
if w.Code != tt.code {
|
||||||
|
t.Fatalf("expected %d, got %d body=%s", tt.code, w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPostShows_OK(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
want := episode.Episode{
|
||||||
|
Id: 9,
|
||||||
|
EpNum: 1,
|
||||||
|
EpTitle: "Pilot",
|
||||||
|
SeasonName: "S1",
|
||||||
|
StartTime: "10:00:00",
|
||||||
|
PlaybackLength: "00:24:00",
|
||||||
|
CurrentEp: false,
|
||||||
|
DateCreated: now,
|
||||||
|
}
|
||||||
|
svc := &fakeSvc{createRes: want}
|
||||||
|
r := newRouterWithSvc(svc)
|
||||||
|
w := doJSON(t, r, http.MethodPost, "/api/v1/shows", map[string]any{
|
||||||
|
"ep_num": 1, "ep_title": "Pilot", "season_name": "S1", "start_time": "10:00:00", "playback_length": "00:24:00",
|
||||||
|
})
|
||||||
|
if w.Code != http.StatusCreated {
|
||||||
|
t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
var got episode.Episode
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
|
||||||
|
t.Fatalf("json: %v", err)
|
||||||
|
}
|
||||||
|
if got.Id != want.Id || got.CurrentEp {
|
||||||
|
t.Fatalf("unexpected body: %+v", got)
|
||||||
|
}
|
||||||
|
if svc.lastCreate.EpTitle != "Pilot" || svc.lastCreate.StartTime != "10:00:00" {
|
||||||
|
t.Fatalf("service called with wrong input: %+v", svc.lastCreate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestListShows_OK(t *testing.T) {
|
func TestListShows_OK(t *testing.T) {
|
||||||
svc := &fakeSvc{
|
svc := &fakeSvc{
|
||||||
listRes: []episode.Episode{
|
listRes: []episode.Episode{
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package httpapi
|
package httpapi
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
// HTTPError is the standard error response.
|
// HTTPError is the standard error response.
|
||||||
type HTTPError struct {
|
type HTTPError struct {
|
||||||
Error string `json:"error" example:"invalid payload"`
|
Error string `json:"error" example:"invalid payload"`
|
||||||
@ -11,10 +13,19 @@ type SetCurrentReq struct {
|
|||||||
StartTime string `json:"start_time" binding:"required" example:"21:00:00"`
|
StartTime string `json:"start_time" binding:"required" example:"21:00:00"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateShowReq is the request body for POST /api/v1/shows.
|
||||||
|
type CreateShowReq struct {
|
||||||
|
EpNum int `json:"ep_num" binding:"required" example:"1"`
|
||||||
|
EpTitle string `json:"ep_title" binding:"required" example:"Pilot"`
|
||||||
|
SeasonName string `json:"season_name" binding:"required" example:"Season 1"`
|
||||||
|
StartTime string `json:"start_time" binding:"required" example:"10:00:00"`
|
||||||
|
PlaybackLength string `json:"playback_length" binding:"required" example:"00:24:00"`
|
||||||
|
}
|
||||||
|
|
||||||
// MoveReq is the request body for POST /api/v1/archive.
|
// MoveReq is the request body for POST /api/v1/archive.
|
||||||
type MoveReq struct {
|
type MoveReq struct {
|
||||||
ID *int64 `json:"id,omitempty" example:"123"`
|
ID *int64 `json:"id,omitempty" example:"123"`
|
||||||
IDs []int64 `json:"ids,omitempty" example:"[123,124]"`
|
IDs []int64 `json:"ids,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveRes is the response body for POST /api/v1/archive.
|
// MoveRes is the response body for POST /api/v1/archive.
|
||||||
@ -28,10 +39,13 @@ type MoveRes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CurrentResponse represents a row from the `current` table.
|
// CurrentResponse represents a row from the `current` table.
|
||||||
//
|
|
||||||
// TODO: fill these fields to match repo.Current exactly.
|
|
||||||
type CurrentResponse struct {
|
type CurrentResponse struct {
|
||||||
ID int64 `json:"id"`
|
ID int `json:"id" example:"123"`
|
||||||
StartTime string `json:"start_time"`
|
EpNum int `json:"ep_num" example:"1"`
|
||||||
// Add other fields here to mirror repo.Current
|
EpTitle string `json:"ep_title" example:"Pilot"`
|
||||||
|
SeasonName string `json:"season_name" example:"Season 1"`
|
||||||
|
StartTime string `json:"start_time" example:"10:00:00"`
|
||||||
|
PlaybackLength string `json:"playback_length" example:"00:24:00"`
|
||||||
|
CurrentEp bool `json:"current_ep" example:"false"`
|
||||||
|
DateCreated time.Time `json:"date_created" example:"2024-02-01T15:04:05Z"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ SELECT
|
|||||||
season_name,
|
season_name,
|
||||||
to_char(start_time, 'HH24:MI:SS') AS start_time,
|
to_char(start_time, 'HH24:MI:SS') AS start_time,
|
||||||
to_char(playback_length, 'HH24:MI:SS') AS playback_length,
|
to_char(playback_length, 'HH24:MI:SS') AS playback_length,
|
||||||
|
current_ep,
|
||||||
date_created
|
date_created
|
||||||
FROM current
|
FROM current
|
||||||
ORDER BY id DESC;
|
ORDER BY id DESC;
|
||||||
@ -48,7 +49,7 @@ ORDER BY id DESC;
|
|||||||
var out []episode.Episode
|
var out []episode.Episode
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var e episode.Episode
|
var e episode.Episode
|
||||||
if err := rows.Scan(&e.Id, &e.EpNum, &e.EpTitle, &e.SeasonName, &e.StartTime, &e.PlaybackLength, &e.DateCreated); err != nil {
|
if err := rows.Scan(&e.Id, &e.EpNum, &e.EpTitle, &e.SeasonName, &e.StartTime, &e.PlaybackLength, &e.CurrentEp, &e.DateCreated); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, e)
|
out = append(out, e)
|
||||||
@ -65,6 +66,7 @@ SELECT
|
|||||||
season_name,
|
season_name,
|
||||||
to_char(start_time, 'HH24:MI:SS') AS start_time,
|
to_char(start_time, 'HH24:MI:SS') AS start_time,
|
||||||
to_char(playback_length, 'HH24:MI:SS') AS playback_length,
|
to_char(playback_length, 'HH24:MI:SS') AS playback_length,
|
||||||
|
current_ep,
|
||||||
date_created
|
date_created
|
||||||
FROM current
|
FROM current
|
||||||
WHERE current_ep = true
|
WHERE current_ep = true
|
||||||
@ -73,7 +75,7 @@ LIMIT 1;
|
|||||||
`
|
`
|
||||||
var e episode.Episode
|
var e episode.Episode
|
||||||
err := r.pool.QueryRow(ctx, q).Scan(
|
err := r.pool.QueryRow(ctx, q).Scan(
|
||||||
&e.Id, &e.EpNum, &e.EpTitle, &e.SeasonName, &e.StartTime, &e.PlaybackLength, &e.DateCreated,
|
&e.Id, &e.EpNum, &e.EpTitle, &e.SeasonName, &e.StartTime, &e.PlaybackLength, &e.CurrentEp, &e.DateCreated,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
@ -84,6 +86,30 @@ LIMIT 1;
|
|||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *pgxEpisodeRepo) Create(ctx context.Context, in episode.NewShowInput) (episode.Episode, error) {
|
||||||
|
const q = `
|
||||||
|
INSERT INTO current (ep_num, ep_title, season_name, start_time, playback_length)
|
||||||
|
VALUES ($1, $2, $3, $4::time, $5::interval)
|
||||||
|
RETURNING
|
||||||
|
id,
|
||||||
|
ep_num,
|
||||||
|
ep_title,
|
||||||
|
season_name,
|
||||||
|
to_char(start_time, 'HH24:MI:SS') AS start_time,
|
||||||
|
to_char(playback_length, 'HH24:MI:SS') AS playback_length,
|
||||||
|
current_ep,
|
||||||
|
date_created;
|
||||||
|
`
|
||||||
|
var e episode.Episode
|
||||||
|
err := r.pool.QueryRow(ctx, q, in.EpNum, in.EpTitle, in.SeasonName, in.StartTime, in.PlaybackLength).Scan(
|
||||||
|
&e.Id, &e.EpNum, &e.EpTitle, &e.SeasonName, &e.StartTime, &e.PlaybackLength, &e.CurrentEp, &e.DateCreated,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return episode.Episode{}, err
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *pgxEpisodeRepo) SetCurrent(ctx context.Context, id int64, startHHMMSS string) error {
|
func (r *pgxEpisodeRepo) SetCurrent(ctx context.Context, id int64, startHHMMSS string) error {
|
||||||
tx, err := r.pool.Begin(ctx)
|
tx, err := r.pool.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -73,6 +74,7 @@ func TestPGXEpisodeRepo_GetCurrent(t *testing.T) {
|
|||||||
"S1",
|
"S1",
|
||||||
"12:00:00",
|
"12:00:00",
|
||||||
"00:24:00",
|
"00:24:00",
|
||||||
|
true,
|
||||||
now,
|
now,
|
||||||
}}
|
}}
|
||||||
},
|
},
|
||||||
@ -94,8 +96,8 @@ func TestPGXEpisodeRepo_ListAll(t *testing.T) {
|
|||||||
queryFn: func(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
|
queryFn: func(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
|
||||||
return &fakeRows{
|
return &fakeRows{
|
||||||
rows: [][]any{
|
rows: [][]any{
|
||||||
{1, 1, "Pilot", "S1", "10:00:00", "00:24:00", now},
|
{1, 1, "Pilot", "S1", "10:00:00", "00:24:00", true, now},
|
||||||
{2, 2, "Next", "S1", "10:30:00", "00:24:00", now},
|
{2, 2, "Next", "S1", "10:30:00", "00:24:00", false, now},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
@ -110,6 +112,48 @@ func TestPGXEpisodeRepo_ListAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPGXEpisodeRepo_Create(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
var gotSQL string
|
||||||
|
var gotArgs []any
|
||||||
|
fp := &fakePool{
|
||||||
|
queryRowFn: func(ctx context.Context, sql string, args ...any) pgx.Row {
|
||||||
|
gotSQL = sql
|
||||||
|
gotArgs = append([]any(nil), args...)
|
||||||
|
return fakeRow{values: []any{
|
||||||
|
5,
|
||||||
|
10,
|
||||||
|
"Title",
|
||||||
|
"S1",
|
||||||
|
"12:00:00",
|
||||||
|
"00:24:00",
|
||||||
|
false,
|
||||||
|
now,
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
repo := &pgxEpisodeRepo{pool: fp}
|
||||||
|
row, err := repo.Create(context.Background(), episode.NewShowInput{
|
||||||
|
EpNum: 10,
|
||||||
|
EpTitle: "Title",
|
||||||
|
SeasonName: "S1",
|
||||||
|
StartTime: "12:00:00",
|
||||||
|
PlaybackLength: "00:24:00",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
if row.Id != 5 || !strings.Contains(gotSQL, "INSERT INTO current") {
|
||||||
|
t.Fatalf("bad insert: id=%d sql=%s", row.Id, gotSQL)
|
||||||
|
}
|
||||||
|
if len(gotArgs) != 5 || gotArgs[0] != 10 || gotArgs[4] != "00:24:00" {
|
||||||
|
t.Fatalf("bad args: %+v", gotArgs)
|
||||||
|
}
|
||||||
|
if row.CurrentEp {
|
||||||
|
t.Fatalf("expected current_ep false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- fakes ---
|
// --- fakes ---
|
||||||
|
|
||||||
type fakePool struct {
|
type fakePool struct {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"watch-party-backend/internal/core/episode"
|
"watch-party-backend/internal/core/episode"
|
||||||
@ -31,6 +32,26 @@ func (s *Service) GetCurrent(ctx context.Context) (episode.Episode, error) {
|
|||||||
|
|
||||||
var hhmmss = regexp.MustCompile(`^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$`)
|
var hhmmss = regexp.MustCompile(`^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$`)
|
||||||
|
|
||||||
|
func parseHHMMSSToDuration(hhmmss string) (time.Duration, error) {
|
||||||
|
parts := strings.Split(hhmmss, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return 0, episode.ErrInvalidPlayback
|
||||||
|
}
|
||||||
|
hours, err := time.ParseDuration(parts[0] + "h")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
mins, err := time.ParseDuration(parts[1] + "m")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
secs, err := time.ParseDuration(parts[2] + "s")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return hours + mins + secs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) SetCurrent(ctx context.Context, id int64, start string) (episode.Episode, error) {
|
func (s *Service) SetCurrent(ctx context.Context, id int64, start string) (episode.Episode, error) {
|
||||||
if !hhmmss.MatchString(start) {
|
if !hhmmss.MatchString(start) {
|
||||||
return episode.Episode{}, episode.ErrInvalidStartTime
|
return episode.Episode{}, episode.ErrInvalidStartTime
|
||||||
@ -43,6 +64,26 @@ func (s *Service) SetCurrent(ctx context.Context, id int64, start string) (episo
|
|||||||
return s.repo.GetCurrent(c)
|
return s.repo.GetCurrent(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) Create(ctx context.Context, in episode.NewShowInput) (episode.Episode, error) {
|
||||||
|
if in.EpNum <= 0 || strings.TrimSpace(in.EpTitle) == "" || strings.TrimSpace(in.SeasonName) == "" {
|
||||||
|
return episode.Episode{}, episode.ErrInvalidShowInput
|
||||||
|
}
|
||||||
|
if !hhmmss.MatchString(in.StartTime) {
|
||||||
|
return episode.Episode{}, episode.ErrInvalidStartTime
|
||||||
|
}
|
||||||
|
if !hhmmss.MatchString(in.PlaybackLength) {
|
||||||
|
return episode.Episode{}, episode.ErrInvalidPlayback
|
||||||
|
}
|
||||||
|
dur, err := parseHHMMSSToDuration(in.PlaybackLength)
|
||||||
|
if err != nil || dur <= 0 {
|
||||||
|
return episode.Episode{}, episode.ErrInvalidPlayback
|
||||||
|
}
|
||||||
|
|
||||||
|
c, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
return s.repo.Create(c, in)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) MoveToArchive(ctx context.Context, ids []int64) (episode.MoveResult, error) {
|
func (s *Service) MoveToArchive(ctx context.Context, ids []int64) (episode.MoveResult, error) {
|
||||||
uniq := make([]int64, 0, len(ids))
|
uniq := make([]int64, 0, len(ids))
|
||||||
seen := make(map[int64]struct{}, len(ids))
|
seen := make(map[int64]struct{}, len(ids))
|
||||||
|
|||||||
@ -25,6 +25,9 @@ type fakeRepo struct {
|
|||||||
moveCalls [][]int64
|
moveCalls [][]int64
|
||||||
listRes []episode.Episode
|
listRes []episode.Episode
|
||||||
listErr error
|
listErr error
|
||||||
|
createRes episode.Episode
|
||||||
|
createErr error
|
||||||
|
createIn []episode.NewShowInput
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeRepo) GetCurrent(ctx context.Context) (episode.Episode, error) {
|
func (f *fakeRepo) GetCurrent(ctx context.Context) (episode.Episode, error) {
|
||||||
@ -47,6 +50,10 @@ func (f *fakeRepo) ListAll(ctx context.Context) ([]episode.Episode, error) {
|
|||||||
func (f *fakeRepo) Delete(ctx context.Context, id int64) error {
|
func (f *fakeRepo) Delete(ctx context.Context, id int64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (f *fakeRepo) Create(ctx context.Context, in episode.NewShowInput) (episode.Episode, error) {
|
||||||
|
f.createIn = append(f.createIn, in)
|
||||||
|
return f.createRes, f.createErr
|
||||||
|
}
|
||||||
|
|
||||||
// ---- tests ----
|
// ---- tests ----
|
||||||
|
|
||||||
@ -169,3 +176,69 @@ func TestEpisodeService_ListAll_Error(t *testing.T) {
|
|||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEpisodeService_Create_Validation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in episode.NewShowInput
|
||||||
|
want error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bad ep num",
|
||||||
|
in: episode.NewShowInput{EpNum: 0, EpTitle: "x", SeasonName: "S1", StartTime: "10:00:00", PlaybackLength: "00:24:00"},
|
||||||
|
want: episode.ErrInvalidShowInput,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "blank title",
|
||||||
|
in: episode.NewShowInput{EpNum: 1, EpTitle: " ", SeasonName: "S1", StartTime: "10:00:00", PlaybackLength: "00:24:00"},
|
||||||
|
want: episode.ErrInvalidShowInput,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad start",
|
||||||
|
in: episode.NewShowInput{EpNum: 1, EpTitle: "x", SeasonName: "S1", StartTime: "1:0:0", PlaybackLength: "00:24:00"},
|
||||||
|
want: episode.ErrInvalidStartTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad playback format",
|
||||||
|
in: episode.NewShowInput{EpNum: 1, EpTitle: "x", SeasonName: "S1", StartTime: "10:00:00", PlaybackLength: "24:00"},
|
||||||
|
want: episode.ErrInvalidPlayback,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero playback",
|
||||||
|
in: episode.NewShowInput{EpNum: 1, EpTitle: "x", SeasonName: "S1", StartTime: "10:00:00", PlaybackLength: "00:00:00"},
|
||||||
|
want: episode.ErrInvalidPlayback,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
svc := service.NewEpisodeService(&fakeRepo{})
|
||||||
|
if _, err := svc.Create(context.Background(), tt.in); !errors.Is(err, tt.want) {
|
||||||
|
t.Fatalf("expected %v, got %v", tt.want, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEpisodeService_Create_OK(t *testing.T) {
|
||||||
|
want := episode.Episode{Id: 10, EpNum: 1, EpTitle: "Pilot", StartTime: "10:00:00", PlaybackLength: "00:24:00"}
|
||||||
|
fr := &fakeRepo{createRes: want}
|
||||||
|
svc := service.NewEpisodeService(fr)
|
||||||
|
|
||||||
|
got, err := svc.Create(context.Background(), episode.NewShowInput{
|
||||||
|
EpNum: 1,
|
||||||
|
EpTitle: "Pilot",
|
||||||
|
SeasonName: "S1",
|
||||||
|
StartTime: "10:00:00",
|
||||||
|
PlaybackLength: "00:24:00",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected: %v", err)
|
||||||
|
}
|
||||||
|
if got.Id != want.Id || len(fr.createIn) != 1 {
|
||||||
|
t.Fatalf("repo not called or bad id: %+v, calls=%d", got, len(fr.createIn))
|
||||||
|
}
|
||||||
|
if fr.createIn[0].EpTitle != "Pilot" || fr.createIn[0].EpNum != 1 {
|
||||||
|
t.Fatalf("bad input passed: %+v", fr.createIn[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user