package dbdoor import ( "strings" "testing" ) // Range semantics must match litestream's S3 client exactly // (s3/replica_client.go:646-651), or a resumed read returns the wrong bytes. func TestRangeHeaderMatchesTheS3Client(t *testing.T) { cases := []struct { name string offset, size int64 want string }{ {"whole file", 0, 0, "bytes=0-"}, {"open-ended from an offset", 512, 0, "bytes=512-"}, {"sized read", 0, 100, "bytes=0-99"}, {"sized read at an offset", 512, 100, "bytes=512-611"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := RangeHeader(tc.offset, tc.size); got != tc.want { t.Errorf("RangeHeader(%d, %d) = %q, want %q", tc.offset, tc.size, got, tc.want) } }) } } // ParseRange is RangeHeader's inverse, used by the door. Anything it does not // produce is refused rather than guessed at. func TestParseRangeInvertsRangeHeader(t *testing.T) { for _, c := range []struct{ offset, size int64 }{{0, 0}, {512, 0}, {0, 100}, {512, 100}} { off, size, err := ParseRange(RangeHeader(c.offset, c.size)) if err != nil || off != c.offset || size != c.size { t.Errorf("ParseRange(RangeHeader(%d, %d)) = %d, %d, %v", c.offset, c.size, off, size, err) } } if off, size, err := ParseRange(""); err != nil || off != 0 || size != 0 { t.Errorf("ParseRange(\"\") = %d, %d, %v, want the whole file", off, size, err) } for _, bad := range []string{"bytes=-5", "bytes=5-2", "bytes=0-1,3-4", "items=0-", "bytes=x-", "bytes=0-y"} { if _, _, err := ParseRange(bad); err == nil { t.Errorf("ParseRange(%q) accepted", bad) } } } // Coordinates, never keys: the caller names a level and a TXID range, and // the server decides what object that is. lock.json has no coordinate. func TestGetPathIsCoordinateAddressed(t *testing.T) { got := GetPath("bab", "titogo", "a.bab.oncarlos.com", 3, 16, 32) for _, want := range []string{"bab", "titogo", "a.bab.oncarlos.com", "/3/", "16", "32"} { if !strings.Contains(got, want) { t.Fatalf("GetPath = %q, missing %q", got, want) } } if strings.Contains(got, ".ltx") || strings.Contains(got, "lock") { t.Fatalf("GetPath = %q leaks the key layout", got) } if !strings.HasSuffix(got, GetTail(3, 16, 32)) || !strings.HasSuffix(ListPath("bab", "titogo", "h.example.com", 3), ListTail(3)) { t.Fatalf("full paths and tails disagree") } } // The span segment round-trips, and nothing that is not a span parses. func TestParseSpanRoundTripsAndRefusesTheRest(t *testing.T) { min, max, err := ParseSpan(Span(16, 32)) if err != nil || min != 16 || max != 32 { t.Fatalf("ParseSpan(Span(16, 32)) = %d, %d, %v", min, max, err) } for _, bad := range []string{"", "16", "16-", "-32", "32-16", "0-1", "a-b", "lock.json", "16-32-48", "+1-2"} { if _, _, err := ParseSpan(bad); err == nil { t.Errorf("ParseSpan(%q) accepted", bad) } } }