Skip to content
Open
36 changes: 36 additions & 0 deletions server/cmd/api/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,42 @@ func (s *ApiService) StopRecording(ctx context.Context, req oapi.StopRecordingRe
return oapi.StopRecording200Response{}, nil
}

func (s *ApiService) MarkRecording(ctx context.Context, req oapi.MarkRecordingRequestObject) (oapi.MarkRecordingResponseObject, error) {
log := logger.FromContext(ctx)

if req.Body == nil {
return oapi.MarkRecording400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body is required"}}, nil
}

recorderID := s.defaultRecorderID
if req.Body.Id != nil && *req.Body.Id != "" {
recorderID = *req.Body.Id
}

rec, exists := s.recordManager.GetRecorder(recorderID)
if !exists {
log.Warn("attempted to mark non-existent recording session", "recorder_id", recorderID)
return oapi.MarkRecording404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "no such recording session"}}, nil
}

name, offsetMs, err := rec.Mark(req.Body.Name)
if err != nil {
switch {
case errors.Is(err, recorder.ErrNotRecording):
log.Warn("attempted to mark recording session that is not recording", "recorder_id", recorderID)
return oapi.MarkRecording409JSONResponse{ConflictErrorJSONResponse: oapi.ConflictErrorJSONResponse{Message: "recording session is not active"}}, nil
case errors.Is(err, recorder.ErrInvalidMarkerName):
log.Warn("invalid marker name", "recorder_id", recorderID)
return oapi.MarkRecording400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "marker name must be non-empty and at most 200 characters"}}, nil
default:
log.Error("failed to mark recording", "err", err, "recorder_id", recorderID)
return oapi.MarkRecording500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to mark recording"}}, nil
}
}

return oapi.MarkRecording201JSONResponse{Name: name, OffsetMs: offsetMs}, nil
}

const (
minRecordingSizeInBytes = 100
)
Expand Down
78 changes: 78 additions & 0 deletions server/cmd/api/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,71 @@ func TestApiService_StopRecording(t *testing.T) {
})
}

func TestApiService_MarkRecording(t *testing.T) {
ctx := context.Background()
name := "checkpoint"

t.Run("unknown session maps to 404", func(t *testing.T) {
mgr := recorder.NewFFmpegManager()
svc, err := newSvc(t, mgr)
require.NoError(t, err)

resp, err := svc.MarkRecording(ctx, oapi.MarkRecordingRequestObject{Body: &oapi.MarkRecordingJSONRequestBody{Name: name}})
require.NoError(t, err)
require.IsType(t, oapi.MarkRecording404JSONResponse{}, resp)
})

t.Run("missing body maps to 400", func(t *testing.T) {
mgr := recorder.NewFFmpegManager()
svc, err := newSvc(t, mgr)
require.NoError(t, err)

resp, err := svc.MarkRecording(ctx, oapi.MarkRecordingRequestObject{})
require.NoError(t, err)
require.IsType(t, oapi.MarkRecording400JSONResponse{}, resp)
})

t.Run("not recording maps to 409", func(t *testing.T) {
mgr := recorder.NewFFmpegManager()
rec := &mockRecorder{id: "default", markErr: recorder.ErrNotRecording}
require.NoError(t, mgr.RegisterRecorder(ctx, rec))

svc, err := newSvc(t, mgr)
require.NoError(t, err)
resp, err := svc.MarkRecording(ctx, oapi.MarkRecordingRequestObject{Body: &oapi.MarkRecordingJSONRequestBody{Name: name}})
require.NoError(t, err)
require.IsType(t, oapi.MarkRecording409JSONResponse{}, resp)
require.True(t, rec.markCalled)
})

t.Run("invalid name maps to 400", func(t *testing.T) {
mgr := recorder.NewFFmpegManager()
rec := &mockRecorder{id: "default", markErr: recorder.ErrInvalidMarkerName}
require.NoError(t, mgr.RegisterRecorder(ctx, rec))

svc, err := newSvc(t, mgr)
require.NoError(t, err)
resp, err := svc.MarkRecording(ctx, oapi.MarkRecordingRequestObject{Body: &oapi.MarkRecordingJSONRequestBody{Name: " "}})
require.NoError(t, err)
require.IsType(t, oapi.MarkRecording400JSONResponse{}, resp)
})

t.Run("success returns 201 with offset", func(t *testing.T) {
mgr := recorder.NewFFmpegManager()
rec := &mockRecorder{id: "default", isRecordingFlag: true, markOffsetMs: 1234}
require.NoError(t, mgr.RegisterRecorder(ctx, rec))

svc, err := newSvc(t, mgr)
require.NoError(t, err)
resp, err := svc.MarkRecording(ctx, oapi.MarkRecordingRequestObject{Body: &oapi.MarkRecordingJSONRequestBody{Name: name}})
require.NoError(t, err)
r, ok := resp.(oapi.MarkRecording201JSONResponse)
require.True(t, ok, "expected 201 response, got %T", resp)
assert.Equal(t, name, r.Name)
assert.Equal(t, int64(1234), r.OffsetMs)
})
}

func TestApiService_DownloadRecording(t *testing.T) {
ctx := context.Background()

Expand Down Expand Up @@ -226,6 +291,11 @@ type mockRecorder struct {
recordingErr error
recordingData []byte
deleted bool

markCalled bool
markName string
markOffsetMs int64
markErr error
}

func (m *mockRecorder) ID() string { return m.id }
Expand Down Expand Up @@ -257,6 +327,14 @@ func (m *mockRecorder) ForceStop(ctx context.Context) error {
return nil
}

func (m *mockRecorder) Mark(name string) (string, int64, error) {
m.markCalled = true
if m.markErr != nil {
return "", 0, m.markErr
}
return name, m.markOffsetMs, nil
}

func (m *mockRecorder) IsRecording(ctx context.Context) bool { return m.isRecordingFlag }

func (m *mockRecorder) Recording(ctx context.Context) (io.ReadCloser, *recorder.RecordingMetadata, error) {
Expand Down
Loading
Loading