Files
streamdata/streamdata_test.go
Yonah 2879d9d186 streamdata: metadata load helper
This loads metadata by ident.

Signed-off-by: Yonah <contrib@gensokyo.uk>
2026-03-19 00:57:35 +09:00

375 lines
8.8 KiB
Go

package streamdata_test
import (
"encoding/json"
"errors"
"io"
"os"
"path"
"reflect"
"syscall"
"testing"
"time"
"git.gensokyo.uk/yonah/streamdata"
)
func TestChannelInvalid(t *testing.T) {
t.Parallel()
if err := (*streamdata.Channel)(nil).Create(""); !reflect.DeepEqual(err, syscall.EINVAL) {
t.Fatalf("Create: error = %v", err)
}
if err := (*streamdata.Channel)(nil).Close(); !reflect.DeepEqual(err, syscall.EINVAL) {
t.Fatalf("Close: error = %v", err)
}
}
func TestChannelOpenPerm(t *testing.T) {
t.Parallel()
d := t.TempDir()
if err := os.Chmod(d, 0); err != nil {
t.Fatal(err)
}
wantErr := &os.PathError{
Op: "open",
Path: d,
Err: syscall.EACCES,
}
t.Run("open", func(t *testing.T) {
t.Parallel()
if _, err := streamdata.Open(d); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("Open: error = %#v, want %#v", err, wantErr)
}
})
t.Run("create", func(t *testing.T) {
t.Parallel()
t.Run("root", func(t *testing.T) {
t.Parallel()
if err := (&streamdata.Channel{
Identifier: 0xcafe,
Name: ":3",
}).Create(d); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("Create: error = %#v, want %#v", err, wantErr)
}
})
t.Run("mkdir", func(t *testing.T) {
t.Parallel()
wantErrMkdir := &os.PathError{
Op: "mkdir",
Path: path.Join(d, "stream"),
Err: syscall.EACCES,
}
if err := (&streamdata.Channel{
Identifier: 0xcafe,
Name: ":3",
}).Create(path.Join(d, "stream")); !reflect.DeepEqual(err, wantErrMkdir) {
t.Fatalf("Create: error = %#v, want %#v", err, wantErrMkdir)
}
})
t.Run("metadata", func(t *testing.T) {
t.Parallel()
nx := t.TempDir()
if err := os.Chmod(nx, 0400); err != nil {
t.Fatal(err)
}
wantErrMetadata := &os.PathError{
Op: "openat",
Path: "channel",
Err: syscall.EACCES,
}
if err := (&streamdata.Channel{
Identifier: 0xcafe,
Name: ":3",
}).Create(nx); !reflect.DeepEqual(err, wantErrMetadata) {
t.Fatalf("Create: error = %#v, want %#v", err, wantErrMetadata)
}
})
})
}
func TestChannelCreate(t *testing.T) {
t.Parallel()
d := t.TempDir()
c := streamdata.Channel{
Identifier: 0xcafe,
Name: ":3",
}
if err := c.Create(d); err != nil {
t.Fatalf("Create: error = %v", err)
}
if err := c.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
}
if got, err := streamdata.Open(d); err != nil {
t.Fatalf("Open: error = %v", err)
} else if err = got.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
} else if !got.Is(&c) {
t.Errorf("Open: %#v, want %#v", got, c)
}
}
func TestChannelDangling(t *testing.T) {
t.Parallel()
t.Run("dangling", func(t *testing.T) {
t.Parallel()
d := t.TempDir()
if err := os.WriteFile(path.Join(d, "pending"), nil, 0); err != nil {
t.Fatal(err)
}
if _, err := streamdata.Open(d); !reflect.DeepEqual(err, streamdata.ErrDanglingTransaction) {
t.Errorf("Open: error = %v", err)
}
})
t.Run("perm", func(t *testing.T) {
t.Parallel()
d := t.TempDir()
if err := os.Chmod(d, 0400); err != nil {
t.Fatal(err)
}
wantErr := &os.PathError{
Op: "statat",
Path: "pending",
Err: syscall.EACCES,
}
if _, err := streamdata.Open(d); !reflect.DeepEqual(err, wantErr) {
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
}
})
}
func TestChannelBadMetadata(t *testing.T) {
t.Run("perm", func(t *testing.T) {
t.Parallel()
d := t.TempDir()
if err := os.WriteFile(path.Join(d, "channel"), nil, 0); err != nil {
t.Fatal(err)
}
wantErr := &os.PathError{
Op: "openat",
Path: "channel",
Err: syscall.EACCES,
}
if _, err := streamdata.Open(d); !reflect.DeepEqual(err, wantErr) {
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
}
})
t.Run("invalid", func(t *testing.T) {
t.Parallel()
d := t.TempDir()
if err := os.WriteFile(path.Join(d, "channel"), nil, 0400); err != nil {
t.Fatal(err)
}
wantErr := io.EOF
if _, err := streamdata.Open(d); !reflect.DeepEqual(err, wantErr) {
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
}
})
}
func TestChannelAdd(t *testing.T) {
t.Parallel()
d := t.TempDir()
c := streamdata.Channel{
Identifier: 0xcafe,
Name: ":3",
}
if err := c.Create(d); err != nil {
t.Fatalf("Create: error = %v", err)
}
t.Cleanup(func() {
if err := c.Close(); err != nil {
t.Fatalf("Close: error = %v", err)
}
})
var wantErr error
if err := c.Add(nil, nil); !reflect.DeepEqual(err, syscall.EINVAL) {
t.Errorf("(invalid) Add: error = %#v", err)
}
wantErr = &streamdata.ChannelMismatchError{
Got: 0xbabe,
Want: 0xcafe,
}
if err := c.Add(&streamdata.Ident{
Channel: 0xbabe,
}, func(*streamdata.VOD, io.Writer) error {
panic("unreachable")
}); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("(channel) Add: error = %#v, want %#v", err, wantErr)
}
wantErr = &os.PathError{
Op: "openat",
Path: "pending",
Err: syscall.EEXIST,
}
if err := os.WriteFile(path.Join(d, "pending"), nil, 0); err != nil {
t.Fatal(err)
}
if err := c.Add(&streamdata.Ident{
Channel: 0xcafe,
}, func(*streamdata.VOD, io.Writer) error {
panic("unreachable")
}); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("(create) Add: error = %#v, want %#v", err, wantErr)
}
if err := os.Remove(path.Join(d, "pending")); err != nil {
t.Fatal(err)
}
wantErr = errors.New("unique error")
if err := c.Add(&streamdata.Ident{
Channel: 0xcafe,
}, func(*streamdata.VOD, io.Writer) error {
return wantErr
}); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("(callback) Add: error = %#v, want %#v", err, wantErr)
}
if _, err := os.Stat(path.Join(d, "pending")); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("Stat: error = %v", err)
}
wantData := []byte{0xde, 0xad, 0xbe, 0xef}
wantVOD := streamdata.VOD{
Title: "\x00",
Date: time.Unix(0, 0).UTC(),
Category: "\t",
}
ident := streamdata.Ident{
Serial: 0xfdfdfdfd,
Channel: 0xcafe,
Data: [streamdata.IdentFFLen]byte{
0xfe, 0xe1, 0xde, 0xad,
0xfe, 0xed,
0x0b, 0xad,
0xf0, 0x0d,
0, 0, 0, 0, 0, 0,
},
}
if err := c.Add(&ident, func(v *streamdata.VOD, w io.Writer) error {
*v = wantVOD
_, err := w.Write(wantData)
return err
}); err != nil {
t.Fatalf("Add: error = %v", err)
}
if dents, err := os.ReadDir(path.Join(d, "vod")); err != nil {
t.Fatal(err)
} else if len(dents) != 2 {
t.Fatalf("ReadDir: %#v", dents)
}
const wantIdent = "4261281277-51966-fee1dead-feed-0bad-f00d-000000000000"
var got streamdata.VOD
if r, err := os.Open(path.Join(d, "vod", wantIdent)); err != nil {
t.Fatal(err)
} else if err = json.NewDecoder(r).Decode(&got); err != nil {
_ = r.Close()
t.Fatalf("Decode: error = %v", err)
} else if got != wantVOD {
t.Errorf("Add: %#v, want %#v", got, wantVOD)
}
if gotVOD, err := c.Load(&ident); err != nil {
t.Fatalf("Load: error = %v", err)
} else if *gotVOD != wantVOD {
t.Errorf("Load: %#v, want %#v", *gotVOD, wantVOD)
}
if gotData, err := os.ReadFile(path.Join(d, "vod", wantIdent+streamdata.ChannelVODSuffix)); err != nil {
t.Fatal(err)
} else if string(gotData) != string(wantData) {
t.Errorf("Add: data = %#v, want %#v", gotData, wantData)
}
if fi, err := os.Stat(path.Join(d, "vod", wantIdent)); err != nil {
t.Fatal(err)
} else if fi.Mode().Perm() != 0444 {
t.Errorf("Perm: %#o", fi.Mode().Perm())
}
if fi, err := os.Stat(path.Join(d, "vod", wantIdent+streamdata.ChannelVODSuffix)); err != nil {
t.Fatal(err)
} else if fi.Mode().Perm() != 0444 {
t.Errorf("Perm: %#o", fi.Mode().Perm())
}
if err := os.Chmod(path.Join(d, "vod", wantIdent), 0); err != nil {
t.Fatal(err)
}
wantErr = &os.PathError{
Op: "openat",
Path: path.Join("vod", wantIdent),
Err: syscall.EACCES,
}
if _, err := c.Load(&ident); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("(perm) Load: error = %#v, want %#v", err, wantErr)
}
if err := os.Chmod(path.Join(d, "vod", wantIdent), 0600); err != nil {
t.Fatal(err)
} else if err = os.WriteFile(path.Join(d, "vod", wantIdent), nil, 0); err != nil {
t.Fatal(err)
}
wantErr = io.EOF
if _, err := c.Load(&ident); !reflect.DeepEqual(err, wantErr) {
t.Fatalf("(invalid) Load: error = %#v, want %#v", err, wantErr)
}
}
func TestErrors(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
err error
want string
}{
{"IdentFieldError", streamdata.IdentFieldError(0xcafe),
"got 51966 field(s) instead of 7"},
{"IdentFFError", &streamdata.IdentFFError{Got: 0xcafe, Want: 0xbabe},
"got 51966 bytes for a 47806-byte long free-form field"},
{"ChannelMismatchError", &streamdata.ChannelMismatchError{Got: 0xcafe, Want: 0xbabe},
"attempting to add VOD from channel 51966 to channel 47806"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if got := tc.err.Error(); got != tc.want {
t.Errorf("Error: %q, want %q", got, tc.want)
}
})
}
}