| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| package httpsfv |
| |
| import ( |
| "slices" |
| "strconv" |
| "strings" |
| "testing" |
| ) |
| |
| func TestParseList(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| wantMembers []string |
| wantParams []string |
| wantOk bool |
| }{ |
| { |
| name: "valid list", |
| in: `a, b,c`, |
| wantMembers: []string{"a", "b", "c"}, |
| wantParams: []string{"", "", ""}, |
| wantOk: true, |
| }, |
| { |
| name: "valid list with params", |
| in: `a;foo=bar, b,c; baz=baz`, |
| wantMembers: []string{"a", "b", "c"}, |
| wantParams: []string{";foo=bar", "", "; baz=baz"}, |
| wantOk: true, |
| }, |
| { |
| name: "valid list with fake commas", |
| in: `a;foo=",", (",")`, |
| wantMembers: []string{"a", `(",")`}, |
| wantParams: []string{`;foo=","`, ""}, |
| wantOk: true, |
| }, |
| { |
| name: "valid list with inner list member", |
| in: `(a b c); foo, bar;baz`, |
| wantMembers: []string{"(a b c)", "bar"}, |
| wantParams: []string{"; foo", ";baz"}, |
| wantOk: true, |
| }, |
| { |
| name: "invalid list with trailing comma", |
| in: `a;foo=bar, b,c; baz=baz,`, |
| wantMembers: []string{"a", "b", "c"}, |
| wantParams: []string{";foo=bar", "", "; baz=baz"}, |
| }, |
| { |
| name: "invalid list with unclosed string", |
| in: `", b, c,d`, |
| }, |
| } |
| |
| for _, tc := range tests { |
| var gotMembers, gotParams []string |
| f := func(member, param string) { |
| gotMembers = append(gotMembers, member) |
| gotParams = append(gotParams, param) |
| } |
| ok := ParseList(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if !slices.Equal(tc.wantMembers, gotMembers) { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotMembers, tc.wantMembers) |
| } |
| if !slices.Equal(tc.wantParams, gotParams) { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams) |
| } |
| } |
| } |
| |
| func TestConsumeBareInnerList(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| wantBareItems []string |
| wantParams []string |
| wantListParam string |
| wantOk bool |
| }{ |
| { |
| name: "valid inner list without param", |
| in: `(a b c)`, |
| wantBareItems: []string{"a", "b", "c"}, |
| wantParams: []string{"", "", ""}, |
| wantOk: true, |
| }, |
| { |
| name: "valid inner list with param", |
| in: `(a;d b c;e)`, |
| wantBareItems: []string{"a", "b", "c"}, |
| wantParams: []string{";d", "", ";e"}, |
| wantOk: true, |
| }, |
| { |
| name: "valid inner list with fake ending parenthesis", |
| in: `(")";foo=")")`, |
| wantBareItems: []string{`")"`}, |
| wantParams: []string{`;foo=")"`}, |
| wantOk: true, |
| }, |
| { |
| name: "valid inner list with list parameter", |
| in: `(a b;c); d`, |
| wantBareItems: []string{"a", "b"}, |
| wantParams: []string{"", ";c"}, |
| wantOk: true, |
| }, |
| { |
| name: "valid inner list with more content after", |
| in: `(a b;c); d, a`, |
| wantBareItems: []string{"a", "b"}, |
| wantParams: []string{"", ";c"}, |
| wantOk: true, |
| }, |
| { |
| name: "invalid inner list", |
| in: `(a b;c `, |
| wantBareItems: []string{"a", "b"}, |
| wantParams: []string{"", ";c"}, |
| }, |
| } |
| |
| for _, tc := range tests { |
| var gotBareItems, gotParams []string |
| f := func(bareItem, param string) { |
| gotBareItems = append(gotBareItems, bareItem) |
| gotParams = append(gotParams, param) |
| } |
| gotConsumed, gotRest, ok := consumeBareInnerList(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if !slices.Equal(tc.wantBareItems, gotBareItems) { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItems, tc.wantBareItems) |
| } |
| if !slices.Equal(tc.wantParams, gotParams) { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams) |
| } |
| if gotConsumed+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, gotConsumed, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestParseBareInnerList(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| wantBareItems []string |
| wantParams []string |
| wantOk bool |
| }{ |
| { |
| name: "valid inner list", |
| in: `(a b;c)`, |
| wantBareItems: []string{"a", "b"}, |
| wantParams: []string{"", ";c"}, |
| wantOk: true, |
| }, |
| { |
| name: "valid inner list with list parameter", |
| in: `(a b;c); d`, |
| wantBareItems: []string{"a", "b"}, |
| wantParams: []string{"", ";c"}, |
| }, |
| { |
| name: "invalid inner list", |
| in: `(a b;c `, |
| wantBareItems: []string{"a", "b"}, |
| wantParams: []string{"", ";c"}, |
| }, |
| } |
| |
| for _, tc := range tests { |
| var gotBareItems, gotParams []string |
| f := func(bareItem, param string) { |
| gotBareItems = append(gotBareItems, bareItem) |
| gotParams = append(gotParams, param) |
| } |
| ok := ParseBareInnerList(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if !slices.Equal(tc.wantBareItems, gotBareItems) { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItems, tc.wantBareItems) |
| } |
| if !slices.Equal(tc.wantParams, gotParams) { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParams, tc.wantParams) |
| } |
| } |
| } |
| |
| func TestConsumeItem(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| wantBareItem string |
| wantParam string |
| wantOk bool |
| }{ |
| { |
| name: "valid bare item", |
| in: `fookey`, |
| wantBareItem: `fookey`, |
| wantOk: true, |
| }, |
| { |
| name: "valid bare item and param", |
| in: `fookey; a="123"`, |
| wantBareItem: `fookey`, |
| wantParam: `; a="123"`, |
| wantOk: true, |
| }, |
| { |
| name: "valid item with content after", |
| in: `fookey; a="123", otheritem; otherparam=1`, |
| wantBareItem: `fookey`, |
| wantParam: `; a="123"`, |
| wantOk: true, |
| }, |
| { |
| name: "invalid just param", |
| in: `;a="123"`, |
| }, |
| } |
| |
| for _, tc := range tests { |
| var gotBareItem, gotParam string |
| f := func(bareItem, param string) { |
| gotBareItem = bareItem |
| gotParam = param |
| } |
| gotConsumed, gotRest, ok := consumeItem(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.wantBareItem != gotBareItem { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItem, tc.wantBareItem) |
| } |
| if tc.wantParam != gotParam { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam) |
| } |
| if gotConsumed+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, gotConsumed, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestParseItem(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| wantBareItem string |
| wantParam string |
| wantOk bool |
| }{ |
| { |
| name: "valid bare item", |
| in: `fookey`, |
| wantBareItem: `fookey`, |
| wantOk: true, |
| }, |
| { |
| name: "valid bare item and param", |
| in: `fookey; a="123"`, |
| wantBareItem: `fookey`, |
| wantParam: `; a="123"`, |
| wantOk: true, |
| }, |
| { |
| name: "valid item with content after", |
| in: `fookey; a="123", otheritem; otherparam=1`, |
| wantBareItem: `fookey`, |
| wantParam: `; a="123"`, |
| }, |
| { |
| name: "invalid just param", |
| in: `;a="123"`, |
| }, |
| } |
| |
| for _, tc := range tests { |
| var gotBareItem, gotParam string |
| f := func(bareItem, param string) { |
| gotBareItem = bareItem |
| gotParam = param |
| } |
| ok := ParseItem(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.wantBareItem != gotBareItem { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotBareItem, tc.wantBareItem) |
| } |
| if tc.wantParam != gotParam { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam) |
| } |
| } |
| } |
| |
| func TestParseDictionary(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| wantVal string |
| wantParam string |
| wantOk bool |
| }{ |
| { |
| name: "valid dictionary with simple value", |
| in: `a=b, want=foo, c=d`, |
| wantVal: "foo", |
| wantOk: true, |
| }, |
| { |
| name: "valid dictionary with implicit value", |
| in: `a, want, c=d`, |
| wantVal: "?1", |
| wantOk: true, |
| }, |
| { |
| name: "valid dictionary with parameter", |
| in: `a, want=foo;bar=baz, c=d`, |
| wantVal: "foo", |
| wantParam: ";bar=baz", |
| wantOk: true, |
| }, |
| { |
| name: "valid dictionary with inner list", |
| in: `a, want=(a b c d;e;f);g=h, c=d`, |
| wantVal: "(a b c d;e;f)", |
| wantParam: ";g=h", |
| wantOk: true, |
| }, |
| { |
| name: "valid dictionary with fake commas", |
| in: `a=(";");b=";",want=foo;bar`, |
| wantVal: "foo", |
| wantParam: ";bar", |
| wantOk: true, |
| }, |
| { |
| name: "invalid dictionary with bad key", |
| in: `UPPERCASEKEY=BAD, want=foo, c=d`, |
| }, |
| { |
| name: "invalid dictionary with trailing comma", |
| in: `trailing=comma,`, |
| }, |
| { |
| name: "invalid dictionary with unclosed string", |
| in: `a=""",want=foo;bar`, |
| }, |
| } |
| |
| for _, tc := range tests { |
| var gotVal, gotParam string |
| f := func(key, val, param string) { |
| if key == "want" { |
| gotVal = val |
| gotParam = param |
| } |
| } |
| ok := ParseDictionary(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.wantVal != gotVal { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotVal, tc.wantVal) |
| } |
| if tc.wantParam != gotParam { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, gotParam, tc.wantParam) |
| } |
| } |
| } |
| |
| func TestConsumeParameter(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want any |
| wantOk bool |
| }{ |
| { |
| name: "valid string", |
| in: `;parameter;want="wantvalue"`, |
| want: "wantvalue", |
| wantOk: true, |
| }, |
| { |
| name: "valid integer", |
| in: `;parameter;want=123456;something`, |
| want: 123456, |
| wantOk: true, |
| }, |
| { |
| name: "valid decimal", |
| in: `;parameter;want=3.14;something`, |
| want: 3.14, |
| wantOk: true, |
| }, |
| { |
| name: "valid implicit bool", |
| in: `;parameter;want;something`, |
| want: true, |
| wantOk: true, |
| }, |
| { |
| name: "valid token", |
| in: `;want=*atoken;something`, |
| want: "*atoken", |
| wantOk: true, |
| }, |
| { |
| name: "valid byte sequence", |
| in: `;want=:eWF5Cg==:;something`, |
| want: "eWF5Cg==", |
| wantOk: true, |
| }, |
| { |
| name: "valid repeated key", |
| in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!"`, |
| want: "overwritten!", |
| wantOk: true, |
| }, |
| { |
| name: "valid parameter with content after", |
| in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!", some=stuff`, |
| want: "overwritten!", |
| wantOk: true, |
| }, |
| { |
| name: "invalid parameter", |
| in: `;UPPERCASEKEY=NOT_ACCEPTED`, |
| }, |
| } |
| |
| for _, tc := range tests[len(tests)-1:] { |
| var got any |
| f := func(key, val string) { |
| if key != "want" { |
| return |
| } |
| switch { |
| case strings.HasPrefix(val, "?"): // Bool |
| got = val == "?1" |
| case strings.HasPrefix(val, `"`): // String |
| got = val[1 : len(val)-1] |
| case strings.HasPrefix(val, "*"): // Token |
| got = val |
| case strings.HasPrefix(val, ":"): // Byte sequence |
| got = val[1 : len(val)-1] |
| default: |
| if valConv, err := strconv.Atoi(val); err == nil { // Integer |
| got = valConv |
| return |
| } |
| if valConv, err := strconv.ParseFloat(val, 64); err == nil { // Float |
| got = valConv |
| return |
| } |
| } |
| } |
| consumed, rest, ok := consumeParameter(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if got != tc.want { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if consumed+rest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, rest, tc.in) |
| } |
| } |
| } |
| |
| func TestParseParameter(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want any |
| wantOk bool |
| }{ |
| { |
| name: "valid parameter", |
| in: `;parameter;want="wantvalue"`, |
| want: "wantvalue", |
| wantOk: true, |
| }, |
| { |
| name: "valid parameter with content after", |
| in: `;want=:eWF5Cg==:;now;want=1;is;repeated;want="overwritten!", some=stuff`, |
| want: "overwritten!", |
| }, |
| { |
| name: "invalid parameter", |
| in: `;UPPERCASEKEY=NOT_ACCEPTED`, |
| }, |
| } |
| |
| for _, tc := range tests[len(tests)-1:] { |
| var got any |
| f := func(key, val string) { |
| if key != "want" { |
| return |
| } |
| switch { |
| case strings.HasPrefix(val, "?"): // Bool |
| got = val == "?1" |
| case strings.HasPrefix(val, `"`): // String |
| got = val[1 : len(val)-1] |
| case strings.HasPrefix(val, "*"): // Token |
| got = val |
| case strings.HasPrefix(val, ":"): // Byte sequence |
| got = val[1 : len(val)-1] |
| default: |
| if valConv, err := strconv.Atoi(val); err == nil { // Integer |
| got = valConv |
| return |
| } |
| if valConv, err := strconv.ParseFloat(val, 64); err == nil { // Float |
| got = valConv |
| return |
| } |
| } |
| } |
| ok := ParseParameter(tc.in, f) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if got != tc.want { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| } |
| } |
| |
| func TestConsumeKey(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid basic key", |
| in: `fookey`, |
| want: `fookey`, |
| wantOk: true, |
| }, |
| { |
| name: "valid basic key with more content after", |
| in: `fookey,u=7`, |
| want: `fookey`, |
| wantOk: true, |
| }, |
| { |
| name: "invalid key", |
| in: `1keycannotstartwithnum`, |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeKey(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeIntegerOrDecimal(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid integer", |
| in: "123456", |
| want: "123456", |
| wantOk: true, |
| }, |
| { |
| name: "valid integer with more content after", |
| in: "123456,12345", |
| want: "123456", |
| wantOk: true, |
| }, |
| { |
| name: "valid max integer", |
| in: "999999999999999", |
| want: "999999999999999", |
| wantOk: true, |
| }, |
| { |
| name: "valid min integer", |
| in: "-999999999999999", |
| want: "-999999999999999", |
| wantOk: true, |
| }, |
| { |
| name: "invalid integer too high", |
| in: "9999999999999999", |
| }, |
| { |
| name: "invalid integer too low", |
| in: "-9999999999999999", |
| }, |
| { |
| name: "valid decimal", |
| in: "-123456789012.123", |
| want: "-123456789012.123", |
| wantOk: true, |
| }, |
| { |
| name: "invalid decimal integer component too long", |
| in: "1234567890123.1", |
| }, |
| { |
| name: "invalid decimal fraction component too long", |
| in: "1.1234", |
| }, |
| { |
| name: "invalid decimal trailing dot", |
| in: "1.", |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeIntegerOrDecimal(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeString(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid basic string", |
| in: `"foo bar"`, |
| want: `"foo bar"`, |
| wantOk: true, |
| }, |
| { |
| name: "valid basic string with more content after", |
| in: `"foo bar", a=3`, |
| want: `"foo bar"`, |
| wantOk: true, |
| }, |
| { |
| name: "valid string with escaped dquote", |
| in: `"foo bar \""`, |
| want: `"foo bar \""`, |
| wantOk: true, |
| }, |
| { |
| name: "invalid string no starting dquote", |
| in: `foo bar"`, |
| }, |
| { |
| name: "invalid string no closing dquote", |
| in: `"foo bar`, |
| }, |
| { |
| name: "invalid string invalid character", |
| in: string([]byte{'"', 0x00, '"'}), |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeString(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeToken(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid token", |
| in: "*atoken", |
| want: "*atoken", |
| wantOk: true, |
| }, |
| { |
| name: "valid token with more content after", |
| in: "*atoken something", |
| want: "*atoken", |
| wantOk: true, |
| }, |
| { |
| name: "invalid token", |
| in: "0invalid", |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeToken(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeByteSequence(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid byte sequence", |
| in: ":aGVsbG8gd29ybGQ=:", |
| want: ":aGVsbG8gd29ybGQ=:", |
| wantOk: true, |
| }, |
| { |
| name: "valid byte sequence with more content after", |
| in: ":aGVsbG8gd29ybGQ=::aGVsbG8gd29ybGQ=:", |
| want: ":aGVsbG8gd29ybGQ=:", |
| wantOk: true, |
| }, |
| { |
| name: "invalid byte sequence character", |
| in: ":-:", |
| }, |
| { |
| name: "invalid byte sequence opening", |
| in: "aGVsbG8gd29ybGQ=:", |
| }, |
| { |
| name: "invalid byte sequence closing", |
| in: ":aGVsbG8gd29ybGQ=", |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeByteSequence(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeBoolean(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid boolean", |
| in: "?0", |
| want: "?0", |
| wantOk: true, |
| }, |
| { |
| name: "valid boolean with more content after", |
| in: "?1, a=1", |
| want: "?1", |
| wantOk: true, |
| }, |
| { |
| name: "invalid boolean", |
| in: "!2", |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeBoolean(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeDate(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid zero date", |
| in: "@0", |
| want: "@0", |
| wantOk: true, |
| }, |
| { |
| name: "valid positive date", |
| in: "@1659578233", |
| want: "@1659578233", |
| wantOk: true, |
| }, |
| { |
| name: "valid negative date", |
| in: "@-1659578233", |
| want: "@-1659578233", |
| wantOk: true, |
| }, |
| { |
| name: "valid large date", |
| in: "@25340221440", |
| want: "@25340221440", |
| wantOk: true, |
| }, |
| { |
| name: "valid small date", |
| in: "@-62135596800", |
| want: "@-62135596800", |
| wantOk: true, |
| }, |
| { |
| name: "invalid decimal date", |
| in: "@1.2", |
| }, |
| { |
| name: "valid date with more content after", |
| in: "@1659578233, foo;bar", |
| want: "@1659578233", |
| wantOk: true, |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeDate(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |
| |
| func TestConsumeDisplayString(t *testing.T) { |
| tests := []struct { |
| name string |
| in string |
| want string |
| wantOk bool |
| }{ |
| { |
| name: "valid ascii string", |
| in: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"", |
| want: "%\" !%22#$%25&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"", |
| wantOk: true, |
| }, |
| { |
| name: "valid lowercase non-ascii string", |
| in: `%"f%c3%bc%c3%bc"`, |
| want: `%"f%c3%bc%c3%bc"`, |
| wantOk: true, |
| }, |
| { |
| name: "invalid uppercase non-ascii string", |
| in: `%"f%C3%BC%C3%BC"`, |
| }, |
| { |
| name: "invalid unqouted string", |
| in: "%foo", |
| }, |
| { |
| name: "invalid string missing initial quote", |
| in: `%foo"`, |
| }, |
| { |
| name: "invalid string missing closing quote", |
| in: `%"foo`, |
| }, |
| { |
| name: "invalid tab in string", |
| in: "%\"\t\"", |
| }, |
| { |
| name: "invalid newline in string", |
| in: "%\"\n\"", |
| }, |
| { |
| name: "invalid single quoted string", |
| in: `%'foo'`, |
| }, |
| { |
| name: "invalid string bad escaping", |
| in: `%\"foo %a"`, |
| }, |
| { |
| name: "valid string with escaped quotes", |
| in: `%"foo %22bar%22 \\ baz"`, |
| want: `%"foo %22bar%22 \\ baz"`, |
| wantOk: true, |
| }, |
| { |
| name: "invalid sequence id utf-8 string", |
| in: `%"%a0%a1"`, |
| }, |
| { |
| name: "invalid 2 bytes sequence utf-8 string", |
| in: `%"%c3%28"`, |
| }, |
| { |
| name: "invalid 3 bytes sequence utf-8 string", |
| in: `%"%e2%28%a1"`, |
| }, |
| { |
| name: "invalid 4 bytes sequence utf-8 string", |
| in: `%"%f0%28%8c%28"`, |
| }, |
| { |
| name: "invalid hex utf-8 string", |
| in: `%"%g0%1w"`, |
| }, |
| { |
| name: "valid byte order mark in display string", |
| in: `%"BOM: %ef%bb%bf"`, |
| want: `%"BOM: %ef%bb%bf"`, |
| wantOk: true, |
| }, |
| { |
| name: "valid string with content after", |
| in: `%"foo\nbar", foo;bar`, |
| want: `%"foo\nbar"`, |
| wantOk: true, |
| }, |
| { |
| name: "invalid unfinished 4 bytes rune", |
| in: `%"%f0%9f%98"`, |
| }, |
| } |
| |
| for _, tc := range tests { |
| got, gotRest, ok := consumeDisplayString(tc.in) |
| if ok != tc.wantOk { |
| t.Fatalf("test %q: want ok to be %v, got: %v", tc.name, tc.wantOk, ok) |
| } |
| if tc.want != got { |
| t.Fatalf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tc.name, got, tc.want) |
| } |
| if got+gotRest != tc.in { |
| t.Fatalf("test %q: %#v + %#v != %#v", tc.name, got, gotRest, tc.in) |
| } |
| } |
| } |