package service import ( "errors" "testing" "time" ) // TestWebSocketTicketIssueBindsIdentityAndExpiry:签发的票据绑定用户、公司、签发时间, // 并在有效期内可被一次性消费。 func TestWebSocketTicketIssueBindsIdentityAndExpiry(t *testing.T) { base := time.Now() svc := NewWebSocketTicketService(30 * time.Second) svc.now = func() time.Time { return base } ticket, err := svc.Issue(7, 2) if err != nil { t.Fatalf("签发失败: %v", err) } if ticket.UserID != 7 || ticket.OrganizationID != 2 { t.Fatalf("票据未绑定用户/公司: %+v", ticket) } if !ticket.IssuedAt.Equal(base) { t.Fatalf("签发时间错误: %v", ticket.IssuedAt) } if !ticket.ExpiresAt.Equal(base.Add(30 * time.Second)) { t.Fatalf("过期时间应为签发时间 + 30s: %v", ticket.ExpiresAt) } // 有效期最后一刻仍可消费。 svc.now = func() time.Time { return base.Add(29 * time.Second) } consumed, err := svc.Consume(ticket.Ticket) if err != nil { t.Fatalf("有效期内消费失败: %v", err) } if consumed.UserID != 7 || consumed.OrganizationID != 2 { t.Fatalf("消费后身份错误: %+v", consumed) } } // TestWebSocketTicketIsOneTimeOnly:票据一次性,重放或再次消费都被拒绝。 func TestWebSocketTicketIsOneTimeOnly(t *testing.T) { svc := NewWebSocketTicketService() ticket, err := svc.Issue(7, 2) if err != nil { t.Fatal(err) } if _, err := svc.Consume(ticket.Ticket); err != nil { t.Fatalf("首次消费应成功: %v", err) } if _, err := svc.Consume(ticket.Ticket); !errors.Is(err, ErrWebSocketTicketInvalid) { t.Fatalf("重放应被拒绝,实际 err=%v", err) } } func TestWebSocketTicketRejectsUnknownAndEmpty(t *testing.T) { for _, tc := range []struct { name string value string }{ {"empty", ""}, {"unknown", "0123456789abcdef"}, } { t.Run(tc.name, func(t *testing.T) { svc := NewWebSocketTicketService() if _, err := svc.Consume(tc.value); !errors.Is(err, ErrWebSocketTicketInvalid) { t.Fatalf("应返回无效票据错误,实际 %v", err) } }) } } // TestWebSocketTicketExpires:超过 30 秒有效期的票据不能再连接。 func TestWebSocketTicketExpires(t *testing.T) { base := time.Now() svc := NewWebSocketTicketService(30 * time.Second) svc.now = func() time.Time { return base } ticket, err := svc.Issue(7, 2) if err != nil { t.Fatal(err) } svc.now = func() time.Time { return base.Add(31 * time.Second) } if _, err := svc.Consume(ticket.Ticket); !errors.Is(err, ErrWebSocketTicketExpired) { t.Fatalf("过期票据应被拒绝,实际 %v", err) } // 过期票据已从集合中移除,再次消费按“未知票据”处理。 if _, err := svc.Consume(ticket.Ticket); !errors.Is(err, ErrWebSocketTicketInvalid) { t.Fatalf("过期后再次消费应为无效票据,实际 %v", err) } } func TestWebSocketTicketIssueRequiresValidIdentity(t *testing.T) { svc := NewWebSocketTicketService() for _, tc := range []struct { name string userID, orgID int64 }{ {"zero user", 0, 2}, {"zero org", 7, 0}, {"negative", -1, -2}, } { t.Run(tc.name, func(t *testing.T) { if _, err := svc.Issue(tc.userID, tc.orgID); !errors.Is(err, ErrInvalidOrganizationID) { t.Fatalf("应返回 ErrInvalidOrganizationID,实际 %v", err) } }) } } // TestWebSocketTicketValuesAreRandom:每张票据的随机标识互不相同。 func TestWebSocketTicketValuesAreRandom(t *testing.T) { svc := NewWebSocketTicketService() first, err := svc.Issue(7, 2) if err != nil { t.Fatal(err) } second, err := svc.Issue(7, 2) if err != nil { t.Fatal(err) } if first.Ticket == second.Ticket || len(first.Ticket) < 32 { t.Fatalf("票据应为足够长的随机串: %q / %q", first.Ticket, second.Ticket) } }