helpers_test.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package webapi
  2. import (
  3. "log/slog"
  4. "testing"
  5. "time"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/mock"
  8. "github.com/mk6i/open-oscar-server/state"
  9. "github.com/mk6i/open-oscar-server/wire"
  10. )
  11. // newTestIconSource returns a BuddyIconSource whose users have no buddy icon,
  12. // for tests that are not exercising icons.
  13. func newTestIconSource(t *testing.T) BuddyIconSource {
  14. iconRetriever := newMockBuddyIconRetriever(t)
  15. iconRetriever.EXPECT().BuddyIconMetadata(mock.Anything, mock.Anything).Return(nil, nil).Maybe()
  16. return BuddyIconSource{
  17. IconRetriever: iconRetriever,
  18. BARTService: newMockBARTService(t),
  19. Logger: slog.Default(),
  20. }
  21. }
  22. // tightRateLimitClasses returns rate classes scaled down so tests run fast.
  23. //
  24. // OSCAR's moving average tracks the interval between requests in milliseconds,
  25. // seeded at MaxLevel, and each back-to-back request halves it at WindowSize 2.
  26. // So from 200 the sequence is 100 (clear), 50 (limited), 25, 12, 6 — the second
  27. // request trips the limit, and none of the first five fall below the disconnect
  28. // threshold. Recovering past ClearLevel takes a ~150ms pause rather than the
  29. // several seconds the production classes would need.
  30. func tightRateLimitClasses() wire.RateLimitClasses {
  31. var classes [5]wire.RateClass
  32. for i := range classes {
  33. classes[i] = wire.RateClass{
  34. ID: wire.RateLimitClassID(i + 1),
  35. WindowSize: 2,
  36. ClearLevel: 100,
  37. AlertLevel: 80,
  38. LimitLevel: 70,
  39. DisconnectLevel: 2,
  40. MaxLevel: 200,
  41. }
  42. }
  43. return wire.NewRateLimitClasses(classes)
  44. }
  45. // newTestOSCARInstance builds an OSCAR session with rate limit state
  46. // initialized, mirroring what RegisterBOSSession does at startSession time.
  47. func newTestOSCARInstance(t *testing.T, classes wire.RateLimitClasses) *state.SessionInstance {
  48. t.Helper()
  49. instance := state.NewSession().AddInstance()
  50. instance.Session().SetIdentScreenName(state.NewIdentScreenName("me"))
  51. instance.Session().SetDisplayScreenName("me")
  52. instance.Session().SetRateClasses(time.Now(), classes)
  53. return instance
  54. }
  55. // newTestWebAPISessionOn builds a WebAPI session over an existing OSCAR
  56. // instance. Two of them model two browser tabs signed in as the same account:
  57. // each tab holds its own aimsid and its own Session, but the account has
  58. // one OSCAR session and therefore one set of rate limit states.
  59. func newTestWebAPISessionOn(aimsid string, instance *state.SessionInstance) *Session {
  60. return &Session{
  61. AimSID: aimsid,
  62. ScreenName: "me",
  63. OSCARSession: instance,
  64. EventQueue: NewEventQueue(10),
  65. }
  66. }
  67. // newTestWebAPISession builds a WebAPI session backed by a real OSCAR session
  68. // with rate limit state initialized.
  69. func newTestWebAPISession(t *testing.T, classes wire.RateLimitClasses) *Session {
  70. t.Helper()
  71. return newTestWebAPISessionOn("aimsid-1", newTestOSCARInstance(t, classes))
  72. }
  73. // rateLimitEventStatuses returns the status string of every rateLimit event
  74. // queued on the session, in order.
  75. func rateLimitEventStatuses(t *testing.T, session *Session) []string {
  76. t.Helper()
  77. var statuses []string
  78. for _, event := range session.EventQueue.GetAllEvents() {
  79. if event.Type != EventTypeRateLimit {
  80. continue
  81. }
  82. payload, ok := event.Data.(RateLimitEvent)
  83. if !assert.True(t, ok, "rateLimit event carried %T", event.Data) {
  84. continue
  85. }
  86. if assert.Len(t, payload.Classes, 1) {
  87. statuses = append(statuses, payload.Classes[0].Status)
  88. }
  89. }
  90. return statuses
  91. }