package protocol import "testing" const ( venuePub = "1212121212121212121212121212121212121212121212121212121212121212" witness = "1313131313131313131313131313131313131313131313131313131313131313" missing = "1414141414141414141414141414141414141414141414141414141414141414" ) // The calendar example from FEATURES_CALENDAR.md. func TestCalendarEventRoundTrip(t *testing.T) { var want [][]string want = appendKV(want, TagD, "event-d") want = appendKV(want, TagTitle, "Friday Night at Lux Frágil") want = appendKV(want, TagSummary, "DJ sets from local selectors") want = appendKV(want, TagImage, "https://blossom.musiquay.com/"+coverHash) want = appendKV(want, TagStart, "1735851600") want = appendKV(want, TagEnd, "1735876800") want = appendKV(want, TagDay, "20090") want = appendKV(want, TagStartTzid, "Europe/Lisbon") want = appendKV(want, TagLocation, "Lux Frágil, Av. Infante Dom Henrique, Lisboa") want = appendKV(want, TagG, "eyckch") want = appendTag(want, (&Participant{Pubkey: artistPub, Relay: "wss://relay.musiquay.com", Role: "performer"}).Tag()) want = appendTag(want, (&Participant{Pubkey: venuePub, Relay: "wss://relay.musiquay.com", Role: "sound-foh"}).Tag()) want = appendKV(want, TagT, "electronic") want = appendKV(want, TagT, "dj") want = appendTag(want, (&Price{Amount: "10", Unit: "EUR"}).Tag()) want = appendKV(want, TagR, "https://tickets.example.com/event/123") c, err := ParseCalendarEvent(want, "Description of the event.") if err != nil { t.Fatalf("parse: %s", err.Error()) } if verr := c.Validate(); verr != nil { t.Fatalf("validate: %s", verr.Error()) } if c.Geohash != "eyckch" || c.StartTzid != "Europe/Lisbon" { t.Fatalf("event = %+v", c) } if len(c.Participants) != 2 || c.Participants[1].Role != "sound-foh" { t.Fatalf("participants = %d", len(c.Participants)) } if got := c.Tags(); !sameTags(got, want) { t.Fatalf("round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(want)) } } // The roster is the calendar event after the show: same fields, every // participant, roles from the roster vocabulary. func TestRosterFormRoundTrip(t *testing.T) { var want [][]string want = appendKV(want, TagD, "event-d") want = appendKV(want, TagTitle, "Friday Night at Lux Frágil") want = appendKV(want, TagStart, "1735851600") want = appendKV(want, TagEnd, "1735876800") want = appendKV(want, TagDay, "20090") want = appendKV(want, TagLocation, "Lux Frágil, Av. Infante Dom Henrique, Lisboa") want = appendKV(want, TagG, "eyckch") want = appendTag(want, (&Participant{Pubkey: ownerPub, Relay: "wss://relay.musiquay.com", Role: "promoter"}).Tag()) want = appendTag(want, (&Participant{Pubkey: artistPub, Relay: "wss://relay.musiquay.com", Role: "performer-headliner"}).Tag()) want = appendTag(want, (&Participant{Pubkey: venuePub, Relay: "wss://relay.musiquay.com", Role: "sound-monitors"}).Tag()) want = appendTag(want, (&Participant{Pubkey: missing, Relay: "wss://relay.musiquay.com", Role: "lighting"}).Tag()) want = appendTag(want, (&Participant{Pubkey: hostPub, Relay: "wss://relay.musiquay.com", Role: "van-rental"}).Tag()) c, err := ParseCalendarEvent(want, "") if err != nil { t.Fatalf("parse: %s", err.Error()) } if verr := c.Validate(); verr != nil { t.Fatalf("validate: %s", verr.Error()) } if len(c.Participants) != 5 { t.Fatalf("roster = %d participants", len(c.Participants)) } for i := range c.Participants { if !KnownRole(c.Participants[i].Role) { t.Fatalf("roster role %q is not in the vocabulary", c.Participants[i].Role) } } if got := c.Tags(); !sameTags(got, want) { t.Fatalf("round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(want)) } if w := c.Warnings(); len(w) != 0 { t.Fatalf("warnings on a complete roster: %s", joinWarnings(w)) } } func TestDayDerivation(t *testing.T) { if d := Day(1735851600); d != "20090" { t.Fatalf("Day(1735851600) = %s, want 20090", d) } if d := Day(0); d != "0" { t.Fatalf("Day(0) = %s", d) } days := DaysCovering(1735851600, 1736024400) if len(days) != 3 { t.Fatalf("DaysCovering = %s", joinWarnings(days)) } if days[0] != "20090" || days[2] != "20092" { t.Fatalf("DaysCovering = %s", joinWarnings(days)) } // A single-instant event covers one day. if one := DaysCovering(1735851600, 1735851600); len(one) != 1 { t.Fatalf("single day = %s", joinWarnings(one)) } } func TestCalendarValidateRejects(t *testing.T) { c := &CalendarEvent{D: "x", Start: "1", End: "2"} if err := c.Validate(); err == nil { t.Fatal("a time-based event with no D tag was accepted") } c2 := &CalendarEvent{D: "x", Days: []string{"20090"}} c2.Participants = push(c2.Participants, Participant{Pubkey: artistPub}) if err := c2.Validate(); err == nil { t.Fatal("a participant with no role was accepted") } c3 := &CalendarEvent{D: "x", Capacity: "many"} if err := c3.Validate(); err == nil { t.Fatal("a non-numeric capacity was accepted") } c4 := &CalendarEvent{} if err := c4.Validate(); err == nil { t.Fatal("an event with no d tag was accepted") } } // The confirmation and attestation examples from FEATURES_ROSTER.md. func TestCreditLabelRoundTrip(t *testing.T) { roster := &Address{Kind: KindCalendar, Pubkey: ownerPub, D: "event-d", Relay: "wss://relay.musiquay.com"} var conf [][]string conf = appendTag(conf, roster.Tag()) conf = appendKV(conf, TagP, artistPub) conf = appendKV(conf, TagL, NamespaceCredit) conf = appendTag(conf, []string{TagLValue, LabelConfirmed, NamespaceCredit}) conf = appendKV(conf, TagRole, "sound-foh") c, err := ParseCreditLabel(conf, "Great gig, crowd was amazing.") if err != nil { t.Fatalf("parse confirmation: %s", err.Error()) } if verr := c.Validate(); verr != nil { t.Fatalf("validate confirmation: %s", verr.Error()) } if !c.IsConfirmation() || c.Label != LabelConfirmed || c.Role != "sound-foh" { t.Fatalf("confirmation = %+v", c) } if got := c.Tags(); !sameTags(got, conf) { t.Fatalf("confirmation round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(conf)) } // An attestation: a witness vouching for someone else. var att [][]string att = appendTag(att, roster.Tag()) att = appendKV(att, TagP, missing) att = appendKV(att, TagL, NamespaceAttest) att = appendTag(att, []string{TagLValue, LabelAttests, NamespaceAttest}) att = appendKV(att, TagRole, "lighting") a, err := ParseCreditLabel(att, "") if err != nil { t.Fatalf("parse attestation: %s", err.Error()) } if verr := a.Validate(); verr != nil { t.Fatalf("validate attestation: %s", verr.Error()) } if a.IsConfirmation() { t.Fatal("an attestation reported as a confirmation") } if got := a.Tags(); !sameTags(got, att) { t.Fatalf("attestation round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(att)) } } func TestCreditLabelValidate(t *testing.T) { roster := &Address{Kind: KindCalendar, Pubkey: ownerPub, D: "event-d"} base := func() (l *CreditLabel) { return &CreditLabel{Target: roster, Subject: artistPub, Namespace: NamespaceCredit, Label: LabelCorrected, Role: "sound-monitors"} } if err := base().Validate(); err != nil { t.Fatalf("a corrected label was rejected: %s", err.Error()) } l := base() l.Namespace = NamespaceAttest if err := l.Validate(); err == nil { t.Fatal("an attest namespace with a credit label was accepted") } l2 := base() l2.Label = "maybe" if err := l2.Validate(); err == nil { t.Fatal("an unknown confirmation label was accepted") } l3 := base() l3.Role = "" if err := l3.Validate(); err == nil { t.Fatal("a label with no role was accepted") } l4 := base() l4.Target = &Address{Kind: KindProduct, Pubkey: ownerPub, D: "p"} if err := l4.Validate(); err == nil { t.Fatal("a label on an unrelated kind was accepted") } l5 := base() l5.Subject = "short" if err := l5.Validate(); err == nil { t.Fatal("a short subject pubkey was accepted") } } // The contribution example: root and parent references, plus the missing // participant and the suggested role. func TestContributionRoundTrip(t *testing.T) { roster := &Address{Kind: KindCalendar, Pubkey: ownerPub, D: "event-d", Relay: "wss://relay.musiquay.com"} var want [][]string root := roster.Tag() root[0] = TagARoot want = appendTag(want, root) want = appendKV(want, TagKRoot, "31923") want = appendKV(want, TagPRoot, ownerPub) want = appendTag(want, roster.Tag()) want = appendKV(want, TagE, "roster-event-id") want = appendKV(want, TagK, "31923") want = appendKV(want, TagP, ownerPub) want = appendTag(want, (&Participant{Pubkey: missing, Relay: "wss://relay.musiquay.com"}).Tag()) want = appendKV(want, TagRole, "lighting") c, err := ParseContribution(want, "Adding the lighting tech.") if err != nil { t.Fatalf("parse: %s", err.Error()) } if verr := c.Validate(); verr != nil { t.Fatalf("validate: %s", verr.Error()) } if c.RootAddress == nil || c.ParentAddress == nil { t.Fatalf("root=%v parent=%v", c.RootAddress, c.ParentAddress) } if c.Creator != ownerPub || c.Subject.Pubkey != missing || c.Subject.Relay != "wss://relay.musiquay.com" { t.Fatalf("creator=%q subject=%+v", c.Creator, c.Subject) } if c.Role != "lighting" { t.Fatalf("role = %q", c.Role) } if got := c.Tags(); !sameTags(got, want) { t.Fatalf("round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(want)) } if !IsRootTag(TagARoot) || !IsRootTag(TagKRoot) || IsRootTag(TagA) { t.Fatal("root tag detection is wrong") } } func TestContributionValidate(t *testing.T) { roster := &Address{Kind: KindCalendar, Pubkey: ownerPub, D: "event-d"} base := func() (c *Contribution) { return &Contribution{ RootAddress: roster, ParentAddress: roster, Creator: ownerPub, Subject: Participant{Pubkey: missing}, Role: "lighting", } } if err := base().Validate(); err != nil { t.Fatalf("valid contribution rejected: %s", err.Error()) } c := base() c.RootAddress = nil if err := c.Validate(); err == nil { t.Fatal("a contribution with no root was accepted") } c2 := base() c2.Role = "" if err := c2.Validate(); err == nil { t.Fatal("a contribution with no role was accepted") } c3 := base() c3.Subject.Pubkey = "short" if err := c3.Validate(); err == nil { t.Fatal("a short subject pubkey was accepted") } } // The RSVP and waitlist examples from FEATURES_CALENDAR.md. func TestRSVPRoundTrip(t *testing.T) { event := &Address{Kind: KindCalendar, Pubkey: venuePub, D: "event-d", Relay: "wss://relay.musiquay.com"} var want [][]string want = appendTag(want, event.Tag()) want = appendKV(want, TagD, "rsvp-d") want = appendKV(want, TagStatus, StatusAccepted) want = appendKV(want, TagFb, FbBusy) r, err := ParseRSVP(want, "") if err != nil { t.Fatalf("parse: %s", err.Error()) } if verr := r.Validate(); verr != nil { t.Fatalf("validate: %s", verr.Error()) } if r.IsWaitlist() { t.Fatal("an accepted RSVP reported as a waitlist entry") } if got := r.Tags(); !sameTags(got, want) { t.Fatalf("round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(want)) } var wait [][]string wait = appendTag(wait, event.Tag()) wait = appendKV(wait, TagD, "waitlist-d") wait = appendKV(wait, TagStatus, StatusWaitlist) wait = appendKV(wait, TagPosition, "14") wait = appendKV(wait, TagJoinedAt, "1735851600") w, err := ParseRSVP(wait, "") if err != nil { t.Fatalf("parse waitlist: %s", err.Error()) } if verr := w.Validate(); verr != nil { t.Fatalf("validate waitlist: %s", verr.Error()) } if !w.IsWaitlist() || w.Position != "14" || w.JoinedAt != "1735851600" { t.Fatalf("waitlist = %+v", w) } if got := w.Tags(); !sameTags(got, wait) { t.Fatalf("waitlist round trip differs:\n got %s\nwant %s", joinTags(got), joinTags(want)) } } func TestRSVPValidate(t *testing.T) { event := &Address{Kind: KindCalendar, Pubkey: venuePub, D: "event-d"} r := &RSVP{Event: event, D: "d", Status: StatusAccepted} if err := r.Validate(); err != nil { t.Fatalf("valid rsvp rejected: %s", err.Error()) } r2 := &RSVP{Event: event, D: "d", Status: StatusWaitlist} if err := r2.Validate(); err == nil { t.Fatal("a waitlist entry with no joined_at was accepted") } r3 := &RSVP{Event: event, D: "d", Status: "maybe"} if err := r3.Validate(); err == nil { t.Fatal("an unknown status was accepted") } r4 := &RSVP{Event: event, D: "d", Status: StatusTentative, Fb: "sleepy"} if err := r4.Validate(); err == nil { t.Fatal("an unknown free/busy value was accepted") } r5 := &RSVP{D: "d", Status: StatusAccepted} if err := r5.Validate(); err == nil { t.Fatal("an rsvp with no event was accepted") } }