- server: relay storing signed objects (PUT/GET), per-IP rate limiting, per-subject quota (1000), one-response-per-request, pagination, /v1/healthz /v1/readyz /v1/metrics - verify: signature-verifying trust evaluator; every object is checked via env.Verify(), approvals via VerifyApprovalResponse, revocations via VerifyRevocationOf; k-of-n approval quorum - docs: TRUST-MODEL.md and API.md describing issuer-anchored signatures and the endpoint/status-code contract - tests: server, verify, and ratelimit packages
120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
package tce_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.n1ko.dev/Niko/niko_trust/internal/tce"
|
|
)
|
|
|
|
// TestCanonicalNumber exercises section 5.1: arbitrary-precision decimals must
|
|
// collapse to one canonical spelling, and malformed tokens must be refused.
|
|
func TestCanonicalNumber(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want string
|
|
wantErr bool
|
|
}{
|
|
// Integers and their redundant spellings.
|
|
{"1", "1", false},
|
|
{"1.0", "1", false},
|
|
{"1e0", "1", false},
|
|
{"1E0", "1", false},
|
|
{"1.00", "1", false},
|
|
{"001", "", true}, // leading zero
|
|
{"0", "0", false},
|
|
{"0.0", "0", false},
|
|
{"-0", "0", false},
|
|
{"-0.0", "0", false},
|
|
{"0e10", "0", false},
|
|
{"-0.000", "0", false},
|
|
|
|
// Fractions (no trailing zeros, no leading zero in int part).
|
|
{"0.1", "0.1", false},
|
|
{"0.10", "0.1", false},
|
|
{".5", "", true}, // missing integer part
|
|
{"1.", "", true}, // missing fraction digits
|
|
{"1.2.3", "", true},
|
|
|
|
// Exponents move the point and strip factors of ten.
|
|
{"123e2", "12300", false},
|
|
{"123E-2", "1.23", false},
|
|
{"1.5e1", "15", false},
|
|
{"150e-2", "1.5", false},
|
|
{"1e+2", "100", false},
|
|
{"1e-2", "0.01", false},
|
|
{"1e999999999", "", true}, // exponent too large
|
|
{"1e", "", true}, // exponent without digits
|
|
{"1e+x", "", true},
|
|
{"1e-", "", true},
|
|
|
|
// Negatives.
|
|
{"-1", "-1", false},
|
|
{"-1.0", "-1", false},
|
|
{"-0.5", "-0.5", false},
|
|
{"-1e1", "-10", false},
|
|
|
|
// Grammar rejections.
|
|
{"", "", true},
|
|
{"abc", "", true},
|
|
{"+1", "", true}, // sign must be minus
|
|
{" 1", "", true}, // no whitespace
|
|
{"1 ", "", true},
|
|
{"0x1", "", true},
|
|
{"1_000", "", true},
|
|
{" Infinity", "", true},
|
|
{"NaN", "", true},
|
|
|
|
// Range: large integers and deep fractions are bounded.
|
|
{"123456789012345678901234567890", "123456789012345678901234567890", false},
|
|
{"1e40", "", true}, // far beyond max integer digits
|
|
}
|
|
for _, c := range cases {
|
|
got, err := tce.CanonicalNumber(c.in)
|
|
if c.wantErr {
|
|
if err == nil {
|
|
t.Errorf("CanonicalNumber(%q): expected error, got %q", c.in, got)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("CanonicalNumber(%q): unexpected error %v", c.in, err)
|
|
continue
|
|
}
|
|
if got != c.want {
|
|
t.Errorf("CanonicalNumber(%q) = %q, want %q", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestIsCanonicalNumber checks the decoder's accept/reject predicate.
|
|
func TestIsCanonicalNumber(t *testing.T) {
|
|
if !tce.IsCanonicalNumber("1.5") {
|
|
t.Error("1.5 is canonical")
|
|
}
|
|
if tce.IsCanonicalNumber("1.50") {
|
|
t.Error("1.50 is not canonical")
|
|
}
|
|
if tce.IsCanonicalNumber("01") {
|
|
t.Error("01 must be rejected")
|
|
}
|
|
}
|
|
|
|
// TestNumberRoundTripEquality confirms numbers that mean the same value
|
|
// canonicalize identically, so they encode to the same bytes.
|
|
func TestNumberRoundTripEquality(t *testing.T) {
|
|
forms := []string{"1", "1.0", "1e0", "1E0", "1.00", "001"}
|
|
base, _ := tce.CanonicalNumber("1")
|
|
for _, f := range forms {
|
|
c, err := tce.CanonicalNumber(f)
|
|
if err != nil {
|
|
// 001 is rejected; that is expected and not an equality case.
|
|
if f == "001" {
|
|
continue
|
|
}
|
|
t.Fatalf("CanonicalNumber(%q): %v", f, err)
|
|
}
|
|
if c != base {
|
|
t.Errorf("CanonicalNumber(%q) = %q, want %q", f, c, base)
|
|
}
|
|
}
|
|
}
|