package roomversion import ( "errors" "strings" "testing" ) func TestGet(t *testing.T) { t.Run("a supported version", func(t *testing.T) { rules, err := Get(V11) if err != nil { t.Fatalf("Get(11): %v", err) } if rules.Version != V11 { t.Errorf("Version = %q, want 11", rules.Version) } if !rules.Supported { t.Error("the rules we were handed say they are not supported") } }) t.Run("a known but unsupported version", func(t *testing.T) { _, err := Get(V1) if !errors.Is(err, ErrUnsupportedVersion) { t.Errorf("Get(1) = %v, want ErrUnsupportedVersion", err) } }) t.Run("an unknown version", func(t *testing.T) { _, err := Get("99") if !errors.Is(err, ErrUnknownVersion) { t.Errorf("Get(99) = %v, want ErrUnknownVersion", err) } }) } func TestMustGetPanicsOnAnUnsupportedVersion(t *testing.T) { defer func() { if recover() == nil { t.Error("MustGet did not panic on an unsupported version") } }() MustGet(V1) } func TestDefaultIsSupported(t *testing.T) { if _, err := Get(Default); err != nil { t.Fatalf("the default room version %q is not usable: %v", Default, err) } if !IsSupported(Default) { t.Errorf("IsSupported(%q) = false", Default) } } func TestSupported(t *testing.T) { supported := Supported() if len(supported) == 0 { t.Fatal("no room versions are supported") } for _, v := range supported { if !IsSupported(v) { t.Errorf("Supported() lists %q but IsSupported says no", v) } } for _, v := range []Version{V1, V2} { if IsSupported(v) { t.Errorf("IsSupported(%q) = true, but the event format for it is not implemented", v) } } if s := SupportedString(); !strings.Contains(s, string(Default)) { t.Errorf("SupportedString() = %q, want it to include the default", s) } } func TestKnownListsEveryVersion(t *testing.T) { known := Known() if len(known) < 12 { t.Errorf("Known() returned %d versions, want every version from 1 to 12", len(known)) } // Known hands out a copy, so a caller cannot edit the registry. known[0].Supported = !known[0].Supported if Known()[0].Supported == known[0].Supported { t.Error("Known() returns the registry itself rather than a copy") } } // TestFlagsAreMonotonic checks the flags that a later room version never // takes back. Each of these was added once and kept, so a version where // one is off after a version where it is on is a transcription error in // the table — the kind of mistake that would otherwise only show up as a // room whose history stops verifying. func TestFlagsAreMonotonic(t *testing.T) { flags := map[string]func(Rules) bool{ "StrictCanonicalJSON": func(r Rules) bool { return r.StrictCanonicalJSON }, "EnforceSigningKeyValidity": func(r Rules) bool { return r.EnforceSigningKeyValidity }, "KnockJoinRule": func(r Rules) bool { return r.KnockJoinRule }, "RestrictedJoinRule": func(r Rules) bool { return r.RestrictedJoinRule }, "KnockRestrictedJoinRule": func(r Rules) bool { return r.KnockRestrictedJoinRule }, "RequireIntegerPowerLevels": func(r Rules) bool { return r.RequireIntegerPowerLevels }, "RedactsInContent": func(r Rules) bool { return r.RedactsInContent }, "RedactionKeepsWholeCreateContent": func(r Rules) bool { return r.RedactionKeepsWholeCreateContent }, "RedactionKeepsRedacts": func(r Rules) bool { return r.RedactionKeepsRedacts }, "RedactionKeepsPowerLevelsInvite": func(r Rules) bool { return r.RedactionKeepsPowerLevelsInvite }, "RedactionKeepsThirdPartyInviteSigned": func(r Rules) bool { return r.RedactionKeepsThirdPartyInviteSigned }, "RedactionKeepsJoinAuthorisedViaUsersServer": func(r Rules) bool { return r.RedactionKeepsJoinAuthorisedViaUsersServer }, "RedactionKeepsJoinRulesAllow": func(r Rules) bool { return r.RedactionKeepsJoinRulesAllow }, "RoomIDIsCreateEventID": func(r Rules) bool { return r.RoomIDIsCreateEventID }, "CreateEventExcludedFromAuthEvents": func(r Rules) bool { return r.CreateEventExcludedFromAuthEvents }, "CreatorsHaveInfinitePower": func(r Rules) bool { return r.CreatorsHaveInfinitePower }, "AdditionalCreators": func(r Rules) bool { return r.AdditionalCreators }, } known := Known() for name, get := range flags { seen := false for _, rules := range known { if get(rules) { seen = true continue } if seen { t.Errorf("%s is on before room version %s and off again at it", name, rules.Version) break } } if !seen { t.Errorf("%s is never on in any room version", name) } } } // TestFlagsThatWereDropped covers the other direction: the four pieces of // behaviour a later room version took away. func TestFlagsThatWereDropped(t *testing.T) { cases := []struct { name string get func(Rules) bool lastOnFor Version }{ {"AliasesEventAuth", func(r Rules) bool { return r.AliasesEventAuth }, V5}, {"RedactionKeepsAliases", func(r Rules) bool { return r.RedactionKeepsAliases }, V5}, {"CreateEventRequiresCreator", func(r Rules) bool { return r.CreateEventRequiresCreator }, V10}, {"RedactionProtectsLegacyKeys", func(r Rules) bool { return r.RedactionProtectsLegacyKeys }, V10}, } known := Known() for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { past := false for _, rules := range known { want := !past if got := tc.get(rules); got != want { t.Errorf("room version %s has %s = %v, want %v", rules.Version, tc.name, got, want) } if rules.Version == tc.lastOnFor { past = true } } }) } } func TestEventIDFormatByVersion(t *testing.T) { cases := map[Version]EventIDFormat{ V1: EventIDFormatExplicit, V2: EventIDFormatExplicit, V3: EventIDFormatSHA256, V4: EventIDFormatSHA256URLSafe, V11: EventIDFormatSHA256URLSafe, V12: EventIDFormatSHA256URLSafe, } for _, rules := range Known() { want, ok := cases[rules.Version] if !ok { continue } if rules.EventIDFormat != want { t.Errorf("room version %s has event ID format %d, want %d", rules.Version, rules.EventIDFormat, want) } } } func TestStateResByVersion(t *testing.T) { cases := map[Version]StateResVersion{ V1: StateResV1, V2: StateResV2, V11: StateResV2, V12: StateResV2Subgraph, } for _, rules := range Known() { want, ok := cases[rules.Version] if !ok { continue } if rules.StateRes != want { t.Errorf("room version %s uses state resolution %d, want %d", rules.Version, rules.StateRes, want) } } } func TestValidate(t *testing.T) { valid := []string{"1", "11", "12", "org.example.room-version.1", "a.b-c", strings.Repeat("a", 32)} for _, s := range valid { if err := Validate(s); err != nil { t.Errorf("Validate(%q) = %v, want it to be accepted", s, err) } } invalid := map[string]string{ "": "empty", strings.Repeat("a", 33): "the limit is", "V11": "contains", "11_beta": "contains", "org.example/room": "contains", "room version 11": "contains", } for s, want := range invalid { err := Validate(s) if err == nil { t.Errorf("Validate(%q) was accepted, want an error", s) continue } if !strings.Contains(err.Error(), want) { t.Errorf("Validate(%q) = %q, want it to mention %q", s, err, want) } } } func TestRulesString(t *testing.T) { if got := MustGet(V11).String(); got != "11" { t.Errorf("String() = %q, want 11", got) } }