2026-07-24 14:22:12 -04:00
package playback
import (
2026-08-11 10:31:39 -04:00
"bytes"
"encoding/json"
2026-07-24 14:22:12 -04:00
"fmt"
"io"
2026-08-11 10:31:39 -04:00
"log/slog"
2026-07-24 14:22:12 -04:00
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/Silo-Server/silo-server/internal/httpstream"
"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
)
const (
directPlayDarwinGOOS = "darwin"
directPlayLinuxGOOS = "linux"
directPlayWindowsGOOS = "windows"
)
func TestServeDirectPlayHTTPContract ( t * testing . T ) {
const content = "0123456789abcdefghijklmnopqrstuvwxyz"
filePath := filepath . Join ( t . TempDir (), "fixture.mp4" )
if err := os . WriteFile ( filePath , [] byte ( content ), 0 o600 ); err != nil {
t . Fatal ( err )
}
2026-08-11 10:31:39 -04:00
serve := func ( method , rangeHeader , ifMatch , ifRange , ifNoneMatch string ) * httptest . ResponseRecorder {
2026-07-24 14:22:12 -04:00
t . Helper ()
req := httptest . NewRequest ( method , "/stream" , nil )
if rangeHeader != "" {
req . Header . Set ( "Range" , rangeHeader )
}
2026-08-11 10:31:39 -04:00
if ifMatch != "" {
req . Header . Set ( "If-Match" , ifMatch )
}
2026-07-24 14:22:12 -04:00
if ifRange != "" {
req . Header . Set ( "If-Range" , ifRange )
}
if ifNoneMatch != "" {
req . Header . Set ( "If-None-Match" , ifNoneMatch )
}
rr := httptest . NewRecorder ()
if err := ServeDirectPlay ( rr , req , filePath ); err != nil {
t . Fatalf ( "ServeDirectPlay: %v" , err )
}
return rr
}
2026-08-11 10:31:39 -04:00
full := serve ( http . MethodGet , "" , "" , "" , "" )
2026-07-24 14:22:12 -04:00
if full . Code != http . StatusOK {
t . Fatalf ( "full status = %d, want 200" , full . Code )
}
if body := full . Body . String (); body != content {
t . Fatalf ( "full body = %q, want %q" , body , content )
}
if got := full . Header (). Get ( "Accept-Ranges" ); got != "bytes" {
t . Fatalf ( "Accept-Ranges = %q, want bytes" , got )
}
etag := full . Header (). Get ( "ETag" )
validatorRequired := platformRequiresDirectPlayValidator ()
if validatorRequired && etag == "" {
t . Fatalf ( "ETag omitted on supported platform %s" , runtime . GOOS )
}
if ! validatorRequired && etag != "" {
t . Fatalf ( "ETag = %q on unsupported platform %s, want omitted validator" , etag , runtime . GOOS )
}
if etag != "" && ( strings . HasPrefix ( etag , "W/" ) || ! strings . HasPrefix ( etag , "\"" ) || ! strings . HasSuffix ( etag , "\"" )) {
t . Fatalf ( "ETag = %q, want a strong quoted validator" , etag )
}
t . Run ( "HEAD" , func ( t * testing . T ) {
2026-08-11 10:31:39 -04:00
rr := serve ( http . MethodHead , "" , "" , "" , "" )
2026-07-24 14:22:12 -04:00
if rr . Code != http . StatusOK {
t . Fatalf ( "status = %d, want 200" , rr . Code )
}
if rr . Body . Len () != 0 {
t . Fatalf ( "body length = %d, want 0" , rr . Body . Len ())
}
if rr . Header (). Get ( "ETag" ) != etag {
t . Fatalf ( "ETag = %q, want %q" , rr . Header (). Get ( "ETag" ), etag )
}
if rr . Header (). Get ( "Accept-Ranges" ) != "bytes" {
t . Fatalf ( "Accept-Ranges = %q, want bytes" , rr . Header (). Get ( "Accept-Ranges" ))
}
if rr . Header (). Get ( "Content-Length" ) != fmt . Sprint ( len ( content )) {
t . Fatalf ( "Content-Length = %q, want %d" , rr . Header (). Get ( "Content-Length" ), len ( content ))
}
})
tests := [] struct {
name string
rangeHeader string
wantStatus int
wantRange string
wantBody string
wantStart int64
}{
{
name : "bounded range" ,
rangeHeader : "bytes=5-9" ,
wantStatus : http . StatusPartialContent ,
wantRange : fmt . Sprintf ( "bytes 5-9/%d" , len ( content )),
wantBody : content [ 5 : 10 ],
wantStart : 5 ,
},
{
name : "suffix range" ,
rangeHeader : "bytes=-4" ,
wantStatus : http . StatusPartialContent ,
wantRange : fmt . Sprintf ( "bytes %d-%d/%d" , len ( content ) - 4 , len ( content ) - 1 , len ( content )),
wantBody : content [ len ( content ) - 4 :],
wantStart : int64 ( len ( content ) - 4 ),
},
{
name : "open ended range" ,
rangeHeader : "bytes=10-" ,
wantStatus : http . StatusPartialContent ,
wantRange : fmt . Sprintf ( "bytes 10-%d/%d" , len ( content ) - 1 , len ( content )),
wantBody : content [ 10 :],
wantStart : 10 ,
},
{
name : "syntactically invalid range" ,
rangeHeader : "bytes=invalid" ,
wantStatus : http . StatusRequestedRangeNotSatisfiable ,
wantRange : fmt . Sprintf ( "bytes */%d" , len ( content )),
},
{
name : "unsatisfiable range" ,
rangeHeader : "bytes=999-1000" ,
wantStatus : http . StatusRequestedRangeNotSatisfiable ,
wantRange : fmt . Sprintf ( "bytes */%d" , len ( content )),
},
{
name : "range starts at EOF" ,
rangeHeader : fmt . Sprintf ( "bytes=%d-" , len ( content )),
wantStatus : http . StatusRequestedRangeNotSatisfiable ,
wantRange : fmt . Sprintf ( "bytes */%d" , len ( content )),
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
resumesBefore := counterValue ( t , directStreamRangeResumes )
2026-08-11 10:31:39 -04:00
rr := serve ( http . MethodGet , tt . rangeHeader , "" , "" , "" )
2026-07-24 14:22:12 -04:00
if rr . Code != tt . wantStatus {
t . Fatalf ( "status = %d, want %d; body = %q" , rr . Code , tt . wantStatus , rr . Body . String ())
}
if got := rr . Header (). Get ( "Content-Range" ); got != tt . wantRange {
t . Fatalf ( "Content-Range = %q, want %q" , got , tt . wantRange )
}
if tt . wantStatus == http . StatusPartialContent && rr . Body . String () != tt . wantBody {
t . Fatalf ( "body = %q, want %q" , rr . Body . String (), tt . wantBody )
}
if tt . wantStatus == http . StatusPartialContent {
if got := directStreamRangeStart ( rr . Code , rr . Header (). Get ( "Content-Range" )); got != tt . wantStart {
t . Fatalf ( "range start = %d, want %d" , got , tt . wantStart )
}
if got := counterValue ( t , directStreamRangeResumes ); got != resumesBefore + 1 {
t . Fatalf ( "resume counter = %v, want %v" , got , resumesBefore + 1 )
}
}
})
}
t . Run ( "matching If-Range" , func ( t * testing . T ) {
if ! validatorRequired {
t . Skip ( "platform does not expose a durable file revision" )
}
2026-08-11 10:31:39 -04:00
rr := serve ( http . MethodGet , "bytes=7-" , "" , etag , "" )
2026-07-24 14:22:12 -04:00
if rr . Code != http . StatusPartialContent {
t . Fatalf ( "status = %d, want 206" , rr . Code )
}
if body := rr . Body . String (); body != content [ 7 :] {
t . Fatalf ( "body = %q, want %q" , body , content [ 7 :])
}
})
t . Run ( "stale If-Range" , func ( t * testing . T ) {
2026-08-11 10:31:39 -04:00
rr := serve ( http . MethodGet , "bytes=7-" , "" , "\"stale\"" , "" )
2026-07-24 14:22:12 -04:00
if rr . Code != http . StatusOK {
t . Fatalf ( "status = %d, want 200" , rr . Code )
}
if body := rr . Body . String (); body != content {
t . Fatalf ( "body = %q, want full entity %q" , body , content )
}
})
2026-08-11 10:31:39 -04:00
t . Run ( "bounded range with matching If-Match" , func ( t * testing . T ) {
if ! validatorRequired {
t . Skip ( "platform does not expose a durable file revision" )
}
rr := serve ( http . MethodGet , "bytes=7-11" , etag , "" , "" )
if rr . Code != http . StatusPartialContent {
t . Fatalf ( "status = %d, want 206" , rr . Code )
}
if body := rr . Body . String (); body != content [ 7 : 12 ] {
t . Fatalf ( "body = %q, want %q" , body , content [ 7 : 12 ])
}
})
t . Run ( "bounded range with stale If-Match" , func ( t * testing . T ) {
rr := serve ( http . MethodGet , "bytes=7-11" , "\"stale\"" , "" , "" )
if rr . Code != http . StatusPreconditionFailed {
t . Fatalf ( "status = %d, want 412" , rr . Code )
}
if rr . Body . Len () != 0 {
t . Fatalf ( "body length = %d, want 0" , rr . Body . Len ())
}
})
2026-07-24 14:22:12 -04:00
t . Run ( "If-None-Match" , func ( t * testing . T ) {
if ! validatorRequired {
t . Skip ( "platform does not expose a durable file revision" )
}
2026-08-11 10:31:39 -04:00
rr := serve ( http . MethodGet , "" , "" , "" , etag )
2026-07-24 14:22:12 -04:00
if rr . Code != http . StatusNotModified {
t . Fatalf ( "status = %d, want 304" , rr . Code )
}
if rr . Body . Len () != 0 {
t . Fatalf ( "body length = %d, want 0" , rr . Body . Len ())
}
})
}
2026-08-11 10:31:39 -04:00
func TestDirectStreamConditionalResult ( t * testing . T ) {
tests := [] struct {
name string
status int
hadIfMatch bool
hadIfRange bool
ifRangeResult string
want string
}{
{ name : "unconditional" , status : http . StatusOK , want : directStreamConditionalNone },
{ name : "If-Match passed" , status : http . StatusPartialContent , hadIfMatch : true , want : directStreamConditionalIfMatchPassed },
{ name : "If-Match failed" , status : http . StatusPreconditionFailed , hadIfMatch : true , want : directStreamConditionalIfMatchFailed },
{ name : "If-Range matched" , status : http . StatusPartialContent , hadIfRange : true , ifRangeResult : directStreamConditionalIfRangeMatched , want : directStreamConditionalIfRangeMatched },
{ name : "If-Range mismatched" , status : http . StatusOK , hadIfRange : true , ifRangeResult : directStreamConditionalIfRangeMismatched , want : directStreamConditionalIfRangeMismatched },
{ name : "If-Range not evaluated after 304" , status : http . StatusNotModified , hadIfRange : true , ifRangeResult : directStreamConditionalIfRangeMatched , want : directStreamConditionalIfRangeNotEvaluated },
{ name : "If-Range not evaluated after 412" , status : http . StatusPreconditionFailed , hadIfRange : true , ifRangeResult : directStreamConditionalIfRangeMismatched , want : directStreamConditionalIfRangeNotEvaluated },
{ name : "If-Range without Range" , status : http . StatusOK , hadIfRange : true , want : directStreamConditionalIfRangeNotEvaluated },
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
if got := directStreamConditionalResult ( tt . status , tt . hadIfMatch , tt . hadIfRange , tt . ifRangeResult ); got != tt . want {
t . Fatalf ( "conditional result = %q, want %q" , got , tt . want )
}
})
}
}
func TestDirectStreamIfRangeResult ( t * testing . T ) {
const currentETag = `"current"`
modtime := time . Date ( 2026 , time . August , 11 , 12 , 0 , 0 , 0 , time . UTC )
tests := [] struct {
name string
method string
rangeHead string
validator string
modtime time . Time
want string
}{
{ name : "matching ETag" , method : http . MethodGet , rangeHead : "bytes=2-4" , validator : currentETag , modtime : modtime , want : directStreamConditionalIfRangeMatched },
{ name : "matching ETag with whitespace" , method : http . MethodGet , rangeHead : "bytes=2-4" , validator : ` "current" ` , modtime : modtime , want : directStreamConditionalIfRangeMatched },
{ name : "stale ETag" , method : http . MethodGet , rangeHead : "bytes=2-4" , validator : `"stale"` , modtime : modtime , want : directStreamConditionalIfRangeMismatched },
{ name : "weak ETag" , method : http . MethodGet , rangeHead : "bytes=2-4" , validator : `W/"current"` , modtime : modtime , want : directStreamConditionalIfRangeMismatched },
{ name : "matching date" , method : http . MethodHead , rangeHead : "bytes=2-4" , validator : modtime . Format ( http . TimeFormat ), modtime : modtime , want : directStreamConditionalIfRangeMatched },
{ name : "stale date" , method : http . MethodGet , rangeHead : "bytes=2-4" , validator : modtime . Add ( - time . Second ). Format ( http . TimeFormat ), modtime : modtime , want : directStreamConditionalIfRangeMismatched },
{ name : "missing range" , method : http . MethodGet , validator : currentETag , modtime : modtime },
{ name : "empty validator" , method : http . MethodGet , rangeHead : "bytes=2-4" , modtime : modtime },
{ name : "unsupported method" , method : http . MethodPost , rangeHead : "bytes=2-4" , validator : currentETag , modtime : modtime },
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
req := httptest . NewRequest ( tt . method , "/stream" , nil )
if tt . rangeHead != "" {
req . Header . Set ( "Range" , tt . rangeHead )
}
if tt . validator != "" {
req . Header . Set ( "If-Range" , tt . validator )
}
if got := directStreamIfRangeResult ( req , currentETag , tt . modtime ); got != tt . want {
t . Fatalf ( "If-Range result = %q, want %q" , got , tt . want )
}
})
}
t . Run ( "uses first If-Range header" , func ( t * testing . T ) {
req := httptest . NewRequest ( http . MethodGet , "/stream" , nil )
req . Header . Set ( "Range" , "bytes=2-4" )
req . Header [ "If-Range" ] = [] string { currentETag , `"stale"` }
if got := directStreamIfRangeResult ( req , currentETag , modtime ); got != directStreamConditionalIfRangeMatched {
t . Fatalf ( "If-Range result = %q, want %q" , got , directStreamConditionalIfRangeMatched )
}
})
}
func TestDirectStreamValidatorFingerprint ( t * testing . T ) {
const validator = "\"private-validator\""
fingerprint := directStreamValidatorFingerprint ( validator )
if len ( fingerprint ) != 16 {
t . Fatalf ( "fingerprint length = %d, want 16" , len ( fingerprint ))
}
if strings . Contains ( fingerprint , "private" ) || strings . Contains ( fingerprint , "validator" ) {
t . Fatalf ( "fingerprint leaked validator text: %q" , fingerprint )
}
if got := directStreamValidatorFingerprint ( validator ); got != fingerprint {
t . Fatalf ( "fingerprint = %q, want stable %q" , got , fingerprint )
}
if got := directStreamValidatorFingerprint ( "\"different\"" ); got == fingerprint {
t . Fatalf ( "different validator produced the same fingerprint %q" , got )
}
if got := directStreamValidatorFingerprint ( " " ); got != "" {
t . Fatalf ( "blank validator fingerprint = %q, want omitted" , got )
}
}
func TestServeDirectPlayConditionalDiagnostics ( t * testing . T ) {
if ! platformRequiresDirectPlayValidator () {
t . Skip ( "platform does not expose a durable file revision" )
}
filePath := filepath . Join ( t . TempDir (), "private-fixture.mp4" )
if err := os . WriteFile ( filePath , [] byte ( "0123456789" ), 0 o600 ); err != nil {
t . Fatal ( err )
}
var logs bytes . Buffer
previousLogger := slog . Default ()
slog . SetDefault ( slog . New ( slog . NewJSONHandler ( & logs , nil )))
t . Cleanup ( func () { slog . SetDefault ( previousLogger ) })
initial := httptest . NewRecorder ()
if err := ServeDirectPlay ( initial , httptest . NewRequest ( http . MethodGet , "/stream" , nil ), filePath ); err != nil {
t . Fatal ( err )
}
currentETag := initial . Header (). Get ( "ETag" )
if currentETag == "" {
t . Fatal ( "initial response omitted ETag" )
}
tests := [] struct {
name string
rangeHeader string
headerName string
validator string
wantStatus int
wantResult string
fingerprintName string
}{
{
name : "If-Match mismatch" ,
rangeHeader : "bytes=2-4" ,
headerName : "If-Match" ,
validator : "\"stale-private-if-match\"" ,
wantStatus : http . StatusPreconditionFailed ,
wantResult : directStreamConditionalIfMatchFailed ,
fingerprintName : "if_match_fingerprint" ,
},
{
name : "If-Range mismatch" ,
rangeHeader : "bytes=2-4" ,
headerName : "If-Range" ,
validator : "\"stale-private-if-range\"" ,
wantStatus : http . StatusOK ,
wantResult : directStreamConditionalIfRangeMismatched ,
fingerprintName : "if_range_fingerprint" ,
},
{
name : "If-Range match with unsatisfiable range" ,
rangeHeader : "bytes=999-1000" ,
headerName : "If-Range" ,
validator : currentETag ,
wantStatus : http . StatusRequestedRangeNotSatisfiable ,
wantResult : directStreamConditionalIfRangeMatched ,
fingerprintName : "if_range_fingerprint" ,
},
{
name : "If-Range match with ignored aggregate ranges" ,
rangeHeader : "bytes=0-9,0-9" ,
headerName : "If-Range" ,
validator : currentETag ,
wantStatus : http . StatusOK ,
wantResult : directStreamConditionalIfRangeMatched ,
fingerprintName : "if_range_fingerprint" ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
logs . Reset ()
req := httptest . NewRequest ( http . MethodGet , "/stream" , nil )
req . Header . Set ( "Range" , tt . rangeHeader )
req . Header . Set ( tt . headerName , tt . validator )
rr := httptest . NewRecorder ()
if err := ServeDirectPlay ( rr , req , filePath ); err != nil {
t . Fatal ( err )
}
if rr . Code != tt . wantStatus {
t . Fatalf ( "status = %d, want %d" , rr . Code , tt . wantStatus )
}
var record map [ string ] any
if err := json . Unmarshal ( logs . Bytes (), & record ); err != nil {
t . Fatalf ( "decode structured log %q: %v" , logs . String (), err )
}
if got := record [ "conditional_result" ]; got != tt . wantResult {
t . Fatalf ( "conditional_result = %v, want %q" , got , tt . wantResult )
}
if got := record [ "had_if_match" ]; got != ( tt . headerName == "If-Match" ) {
t . Fatalf ( "had_if_match = %v, want %v" , got , tt . headerName == "If-Match" )
}
if got := record [ tt . fingerprintName ]; got != directStreamValidatorFingerprint ( tt . validator ) {
t . Fatalf ( "%s = %v, want request fingerprint" , tt . fingerprintName , got )
}
responseETag := rr . Header (). Get ( "ETag" )
if tt . wantStatus == http . StatusRequestedRangeNotSatisfiable {
// ServeContent strips ETag from its 416 response, but the end log
// still fingerprints the validator used for If-Range evaluation.
responseETag = currentETag
}
if got := record [ "etag_fingerprint" ]; got != directStreamValidatorFingerprint ( responseETag ) {
t . Fatalf ( "etag_fingerprint = %v, want response ETag fingerprint" , got )
}
if strings . Contains ( logs . String (), strings . Trim ( tt . validator , "\"" )) {
t . Fatalf ( "structured log leaked raw request validator: %s" , logs . String ())
}
if strings . Contains ( logs . String (), strings . Trim ( responseETag , "\"" )) {
t . Fatalf ( "structured log leaked raw response validator: %s" , logs . String ())
}
if strings . Contains ( logs . String (), filePath ) {
t . Fatalf ( "structured log leaked media path: %s" , logs . String ())
}
})
}
}
func TestServeDirectPlayChangedEntityRejectsOldValidators ( t * testing . T ) {
2026-07-24 14:22:12 -04:00
if ! platformRequiresDirectPlayValidator () {
t . Skip ( "platform does not expose a durable file revision" )
}
dir := t . TempDir ()
filePath := filepath . Join ( dir , "fixture.mp4" )
const original = "original bytes"
if err := os . WriteFile ( filePath , [] byte ( original ), 0 o600 ); err != nil {
t . Fatal ( err )
}
originalTime := time . Now (). Add ( - 10 * time . Second ). Truncate ( time . Second )
if err := os . Chtimes ( filePath , originalTime , originalTime ); err != nil {
t . Fatal ( err )
}
first := httptest . NewRecorder ()
if err := ServeDirectPlay ( first , httptest . NewRequest ( http . MethodGet , "/stream" , nil ), filePath ); err != nil {
t . Fatal ( err )
}
oldETag := first . Header (). Get ( "ETag" )
if oldETag == "" {
t . Fatalf ( "ETag omitted on supported platform %s" , runtime . GOOS )
}
const replacement = "replaced bytes"
if len ( replacement ) != len ( original ) {
t . Fatal ( "test fixture must preserve file size" )
}
2026-07-30 10:52:41 -04:00
// Size and mtime are pinned identical on purpose, so the inode change time
// is the only thing left that can distinguish the two entities. Linux
// stamps ctime from a coarse clock — the whole rewrite finishes inside one
// tick — so writing once and reading immediately usually produces the same
// ctime and the test fails for a reason that has nothing to do with the
// validator. Rewrite until the stamp actually moves.
deadline := time . Now (). Add ( 2 * time . Second )
for {
if err := os . WriteFile ( filePath , [] byte ( replacement ), 0 o600 ); err != nil {
t . Fatal ( err )
}
if err := os . Chtimes ( filePath , originalTime , originalTime ); err != nil {
t . Fatal ( err )
}
probe := httptest . NewRecorder ()
if err := ServeDirectPlay ( probe , httptest . NewRequest ( http . MethodGet , "/stream" , nil ), filePath ); err != nil {
t . Fatal ( err )
}
if probe . Header (). Get ( "ETag" ) != oldETag {
break
}
if time . Now (). After ( deadline ) {
t . Fatal ( "file revision never changed across a replacement; the platform exposes no usable validator" )
}
time . Sleep ( time . Millisecond )
2026-07-24 14:22:12 -04:00
}
req := httptest . NewRequest ( http . MethodGet , "/stream" , nil )
req . Header . Set ( "Range" , "bytes=5-" )
req . Header . Set ( "If-Range" , oldETag )
rr := httptest . NewRecorder ()
if err := ServeDirectPlay ( rr , req , filePath ); err != nil {
t . Fatal ( err )
}
if rr . Code != http . StatusOK {
t . Fatalf ( "status = %d, want 200" , rr . Code )
}
if body , err := io . ReadAll ( rr . Result (). Body ); err != nil || string ( body ) != replacement {
t . Fatalf ( "body = %q, err = %v; want full replacement entity" , body , err )
}
if newETag := rr . Header (). Get ( "ETag" ); newETag == oldETag {
t . Fatalf ( "ETag did not change after replacement: %q" , newETag )
}
2026-08-11 10:31:39 -04:00
ifMatchRequest := httptest . NewRequest ( http . MethodGet , "/stream" , nil )
ifMatchRequest . Header . Set ( "Range" , "bytes=5-8" )
ifMatchRequest . Header . Set ( "If-Match" , oldETag )
ifMatchResponse := httptest . NewRecorder ()
if err := ServeDirectPlay ( ifMatchResponse , ifMatchRequest , filePath ); err != nil {
t . Fatal ( err )
}
if ifMatchResponse . Code != http . StatusPreconditionFailed {
t . Fatalf ( "If-Match status = %d, want 412" , ifMatchResponse . Code )
}
if ifMatchResponse . Body . Len () != 0 {
t . Fatalf ( "If-Match body length = %d, want 0" , ifMatchResponse . Body . Len ())
}
if newETag := ifMatchResponse . Header (). Get ( "ETag" ); newETag == oldETag {
t . Fatalf ( "If-Match response did not expose the replacement ETag: %q" , newETag )
}
2026-07-24 14:22:12 -04:00
}
func TestDirectPlayEntityTagOmitsUnsupportedRevision ( t * testing . T ) {
filePath := filepath . Join ( t . TempDir (), "fixture.mp4" )
if err := os . WriteFile ( filePath , [] byte ( "abcdef" ), 0 o600 ); err != nil {
t . Fatal ( err )
}
file , err := os . Open ( filePath )
if err != nil {
t . Fatal ( err )
}
t . Cleanup ( func () {
if err := file . Close (); err != nil {
t . Errorf ( "close fixture: %v" , err )
}
})
info , err := file . Stat ()
if err != nil {
t . Fatal ( err )
}
if got := directPlayEntityTag ( file , fileInfoWithoutSystem { FileInfo : info }); got != "" {
t . Fatalf ( "ETag without durable revision = %q, want omitted validator" , got )
}
}
type fileInfoWithoutSystem struct {
os . FileInfo
}
func ( fileInfoWithoutSystem ) Sys () any {
return nil
}
func platformRequiresDirectPlayValidator () bool {
switch runtime . GOOS {
case directPlayDarwinGOOS , directPlayLinuxGOOS , directPlayWindowsGOOS :
return true
default :
return false
}
}
func TestServeDirectPlayStalledWriteIncrementsOutcomeMetric ( t * testing . T ) {
filePath := filepath . Join ( t . TempDir (), "fixture.mp4" )
if err := os . WriteFile ( filePath , [] byte ( "media bytes" ), 0 o600 ); err != nil {
t . Fatal ( err )
}
stalledEnds := directStreamEnds . WithLabelValues ( string ( httpstream . OutcomeStalledReap ))
endsBefore := counterValue ( t , stalledEnds )
activeBefore := gaugeValue ( t , directStreamActive )
writer := & deadlineResponseWriter { header : make ( http . Header )}
if err := ServeDirectPlay ( writer , httptest . NewRequest ( http . MethodGet , "/stream" , nil ), filePath ); err != nil {
t . Fatal ( err )
}
if writer . status != http . StatusOK {
t . Fatalf ( "status = %d, want %d" , writer . status , http . StatusOK )
}
if got := counterValue ( t , stalledEnds ); got != endsBefore + 1 {
t . Fatalf ( "stalled end counter = %v, want %v" , got , endsBefore + 1 )
}
if got := gaugeValue ( t , directStreamActive ); got != activeBefore {
t . Fatalf ( "active stream gauge = %v, want restored value %v" , got , activeBefore )
}
}
func counterValue ( t testing . TB , counter prometheus . Counter ) float64 {
t . Helper ()
metric := & dto . Metric {}
if err := counter . Write ( metric ); err != nil {
t . Fatalf ( "read counter: %v" , err )
}
return metric . GetCounter (). GetValue ()
}
func gaugeValue ( t testing . TB , gauge prometheus . Gauge ) float64 {
t . Helper ()
metric := & dto . Metric {}
if err := gauge . Write ( metric ); err != nil {
t . Fatalf ( "read gauge: %v" , err )
}
return metric . GetGauge (). GetValue ()
}
type deadlineResponseWriter struct {
header http . Header
status int
}
func ( w * deadlineResponseWriter ) Header () http . Header {
return w . header
}
func ( w * deadlineResponseWriter ) WriteHeader ( status int ) {
w . status = status
}
func ( w * deadlineResponseWriter ) Write ([] byte ) ( int , error ) {
return 0 , os . ErrDeadlineExceeded
}