1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package httprange_test
4
5import (
6 "net/http"
7 "net/http/httptest"
8 "testing"
9
10 "github.com/stretchr/testify/require"
11
12 "bnema.dev/ermite/internal/httprange"
13)
14
15func TestSelect(t *testing.T) {
16 const etag = `"sha256-abc"`
17 for _, tc := range []struct {
18 name string
19 method, rng, ifRange string
20 size int64
21 offset, length int64
22 partial bool
23 err error
24 }{
25 {name: "no range", size: 10, length: 10},
26 {name: "closed", rng: "bytes=2-4", size: 10, offset: 2, length: 3, partial: true},
27 {name: "open", rng: "bytes=7-", size: 10, offset: 7, length: 3, partial: true},
28 {name: "suffix", rng: "bytes=-4", size: 10, offset: 6, length: 4, partial: true},
29 {name: "suffix longer than body", rng: "bytes=-40", size: 10, offset: 0, length: 10, partial: true},
30 {name: "end cut to size", rng: "bytes=8-99", size: 10, offset: 8, length: 2, partial: true},
31 {name: "spaces", rng: "bytes= 1-2 ", size: 10, offset: 1, length: 2, partial: true},
32 {name: "start past end", rng: "bytes=10-", size: 10, err: httprange.ErrUnsatisfiable},
33 {name: "end before start", rng: "bytes=5-4", size: 10, err: httprange.ErrUnsatisfiable},
34 {name: "zero suffix", rng: "bytes=-0", size: 10, err: httprange.ErrUnsatisfiable},
35 {name: "garbage", rng: "bytes=a-b", size: 10, err: httprange.ErrUnsatisfiable},
36 {name: "no dash", rng: "bytes=5", size: 10, err: httprange.ErrUnsatisfiable},
37 {name: "empty body", rng: "bytes=0-", size: 0, err: httprange.ErrUnsatisfiable},
38 {name: "multiple ignored", rng: "bytes=0-1,3-4", size: 10, length: 10},
39 {name: "other unit ignored", rng: "items=0-1", size: 10, length: 10},
40 {name: "head ignores range", method: http.MethodHead, rng: "bytes=2-4", size: 10, length: 10},
41 {name: "head ignores bad range", method: http.MethodHead, rng: "bytes=99-", size: 10, length: 10},
42 {name: "matching if-range", rng: "bytes=2-4", ifRange: etag, size: 10, offset: 2, length: 3, partial: true},
43 {name: "stale if-range", rng: "bytes=2-4", ifRange: `"other"`, size: 10, length: 10},
44 {name: "date if-range", rng: "bytes=2-4", ifRange: "Wed, 21 Oct 2015 07:28:00 GMT", size: 10, length: 10},
45 {name: "weak if-range", rng: "bytes=2-4", ifRange: "W/" + etag, size: 10, length: 10},
46 {name: "stale if-range with bad range", rng: "bytes=99-", ifRange: `"other"`, size: 10, length: 10},
47 } {
48 t.Run(tc.name, func(t *testing.T) {
49 method := tc.method
50 if method == "" {
51 method = http.MethodGet
52 }
53 r := httptest.NewRequest(method, "/x", nil)
54 if tc.rng != "" {
55 r.Header.Set("Range", tc.rng)
56 }
57 if tc.ifRange != "" {
58 r.Header.Set("If-Range", tc.ifRange)
59 }
60 offset, length, partial, err := httprange.Select(r, tc.size, etag)
61 require.ErrorIs(t, err, tc.err)
62 require.Equal(t, tc.offset, offset)
63 require.Equal(t, tc.length, length)
64 require.Equal(t, tc.partial, partial)
65 })
66 }
67}
68
69func TestContentRange(t *testing.T) {
70 require.Equal(t, "bytes 2-4/10", httprange.ContentRange(2, 3, 10))
71 require.Equal(t, "bytes */10", httprange.Unsatisfied(10))
72}