session_test.go 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421
  1. package state
  2. import (
  3. "context"
  4. "fmt"
  5. "math"
  6. "net/netip"
  7. "sync"
  8. "testing"
  9. "time"
  10. "github.com/mk6i/open-oscar-server/wire"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. )
  14. func TestSession_UsesFeedbag(t *testing.T) {
  15. s := NewSession()
  16. if s.UsesFeedbag() {
  17. t.Fatalf("UsesFeedbag() = true; want false")
  18. }
  19. s.SetUsesFeedbag()
  20. if !s.UsesFeedbag() {
  21. t.Fatalf("UsesFeedbag() = false; want true")
  22. }
  23. // idempotent
  24. s.SetUsesFeedbag()
  25. if !s.UsesFeedbag() {
  26. t.Fatalf("UsesFeedbag() = false after second SetUsesFeedbag; want true")
  27. }
  28. }
  29. func TestSessionInstance_NotifyTxn(t *testing.T) {
  30. alice := NewIdentScreenName("Alice")
  31. bob := NewIdentScreenName("Bob")
  32. t.Run("lifecycle", func(t *testing.T) {
  33. inst := NewSession().AddInstance()
  34. inst.BeginNotifyTxn()
  35. assert.True(t, inst.InNotifyTxn())
  36. require.NoError(t, inst.NotifyTxn(alice, bob))
  37. shouldNotify, screenNames := inst.EndNotifyTxn()
  38. assert.True(t, shouldNotify)
  39. assert.ElementsMatch(t, []IdentScreenName{alice, bob}, screenNames)
  40. assert.False(t, inst.InNotifyTxn())
  41. shouldNotify, screenNames = inst.EndNotifyTxn()
  42. assert.False(t, shouldNotify)
  43. assert.Nil(t, screenNames)
  44. })
  45. t.Run("clear on begin", func(t *testing.T) {
  46. inst := NewSession().AddInstance()
  47. inst.BeginNotifyTxn()
  48. require.NoError(t, inst.NotifyTxn(alice))
  49. inst.BeginNotifyTxn()
  50. shouldNotify, screenNames := inst.EndNotifyTxn()
  51. assert.False(t, shouldNotify)
  52. assert.Empty(t, screenNames)
  53. })
  54. t.Run("notify without names", func(t *testing.T) {
  55. inst := NewSession().AddInstance()
  56. inst.BeginNotifyTxn()
  57. require.NoError(t, inst.NotifyTxn())
  58. shouldNotify, screenNames := inst.EndNotifyTxn()
  59. assert.True(t, shouldNotify)
  60. assert.Empty(t, screenNames)
  61. })
  62. t.Run("inactive notify", func(t *testing.T) {
  63. inst := NewSession().AddInstance()
  64. assert.ErrorIs(t, inst.NotifyTxn(alice), errNotifyTxnNotActive)
  65. inst.BeginNotifyTxn()
  66. shouldNotify, screenNames := inst.EndNotifyTxn()
  67. assert.False(t, shouldNotify)
  68. assert.Empty(t, screenNames)
  69. })
  70. t.Run("dedup", func(t *testing.T) {
  71. inst := NewSession().AddInstance()
  72. inst.BeginNotifyTxn()
  73. require.NoError(t, inst.NotifyTxn(alice, alice))
  74. shouldNotify, screenNames := inst.EndNotifyTxn()
  75. assert.True(t, shouldNotify)
  76. assert.Equal(t, []IdentScreenName{alice}, screenNames)
  77. })
  78. t.Run("exceeds max names", func(t *testing.T) {
  79. inst := NewSession().AddInstance()
  80. inst.BeginNotifyTxn()
  81. names := make([]IdentScreenName, maxNotifyTxnNames)
  82. for i := range names {
  83. names[i] = NewIdentScreenName(fmt.Sprintf("user%d", i))
  84. }
  85. require.NoError(t, inst.NotifyTxn(names...))
  86. shouldNotify, screenNames := inst.EndNotifyTxn()
  87. assert.True(t, shouldNotify)
  88. assert.Len(t, screenNames, maxNotifyTxnNames)
  89. inst.BeginNotifyTxn()
  90. require.NoError(t, inst.NotifyTxn(names...))
  91. assert.ErrorIs(t, inst.NotifyTxn(NewIdentScreenName("overflow")), errNotifyTxnTooManyNames)
  92. })
  93. }
  94. func TestSession_IncrementAndGetWarning(t *testing.T) {
  95. s := NewSession().AddInstance()
  96. var wg sync.WaitGroup
  97. wg.Add(1)
  98. go func() {
  99. defer wg.Done()
  100. s.Session().ScaleWarningAndRateLimit(1, 1)
  101. s.Session().ScaleWarningAndRateLimit(2, 1)
  102. s.Session().ScaleWarningAndRateLimit(3, 1)
  103. }()
  104. assert.Equal(t, uint16(1), <-s.WarningCh())
  105. assert.Equal(t, uint16(3), <-s.WarningCh())
  106. assert.Equal(t, uint16(6), <-s.WarningCh())
  107. wg.Wait()
  108. }
  109. func TestSession_SetAndGetInvisible(t *testing.T) {
  110. s := NewSession().AddInstance()
  111. assert.False(t, s.Invisible())
  112. s.SetUserStatusBitmask(wire.OServiceUserStatusInvisible)
  113. assert.True(t, s.Invisible())
  114. }
  115. func TestSession_SetAndGetScreenName(t *testing.T) {
  116. s := NewSession().AddInstance()
  117. assert.Empty(t, s.IdentScreenName())
  118. sn := NewIdentScreenName("user-screen-name")
  119. s.Session().SetIdentScreenName(sn)
  120. assert.Equal(t, sn, s.IdentScreenName())
  121. }
  122. func TestSession_SetAndGetChatRoomCookie(t *testing.T) {
  123. s := NewSession().AddInstance()
  124. assert.Empty(t, s.ChatRoomCookie())
  125. sn := "the-chat-cookie"
  126. s.Session().SetChatRoomCookie(sn)
  127. assert.Equal(t, sn, s.ChatRoomCookie())
  128. }
  129. func TestSession_SetAndGetUIN(t *testing.T) {
  130. s := NewSession().AddInstance()
  131. assert.Empty(t, s.UIN())
  132. uin := uint32(100003)
  133. s.Session().SetUIN(uin)
  134. assert.Equal(t, uin, s.UIN())
  135. }
  136. func TestSession_SetAndGetClientID(t *testing.T) {
  137. s := NewSession().AddInstance()
  138. assert.Empty(t, s.ClientID())
  139. clientID := "AIM Client ID"
  140. s.SetClientID(clientID)
  141. assert.Equal(t, clientID, s.ClientID())
  142. }
  143. func TestSession_SetAndGetKerberosAuth(t *testing.T) {
  144. s := NewSession().AddInstance()
  145. assert.False(t, s.KerberosAuth())
  146. s.SetKerberosAuth(true)
  147. assert.True(t, s.KerberosAuth())
  148. s.SetKerberosAuth(false)
  149. assert.False(t, s.KerberosAuth())
  150. }
  151. func TestSession_SetAndGetRemoteAddr(t *testing.T) {
  152. s := NewSession().AddInstance()
  153. assert.Empty(t, s.RemoteAddr())
  154. remoteAddr, _ := netip.ParseAddrPort("1.2.3.4:1234")
  155. s.SetRemoteAddr(&remoteAddr)
  156. assert.Equal(t, &remoteAddr, s.RemoteAddr())
  157. }
  158. func TestSession_TLVUserInfo(t *testing.T) {
  159. tests := []struct {
  160. name string
  161. givenSessionFn func() *SessionInstance
  162. want wire.TLVUserInfo
  163. }{
  164. {
  165. name: "user is active and visible",
  166. givenSessionFn: func() *SessionInstance {
  167. s := NewSession().AddInstance()
  168. s.Session().SetSignonTime(time.Unix(1, 0))
  169. s.Session().SetIdentScreenName(NewIdentScreenName("xXAIMUSERXx"))
  170. s.Session().SetDisplayScreenName("xXAIMUSERXx")
  171. s.Session().ScaleWarningAndRateLimit(10, 1)
  172. s.SetUserInfoFlag(wire.OServiceUserFlagOSCARFree)
  173. return s
  174. },
  175. want: wire.TLVUserInfo{
  176. ScreenName: "xXAIMUSERXx",
  177. WarningLevel: 10,
  178. TLVBlock: wire.TLVBlock{
  179. TLVList: wire.TLVList{
  180. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  181. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  182. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  183. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  184. },
  185. },
  186. },
  187. },
  188. {
  189. name: "user is on ICQ",
  190. givenSessionFn: func() *SessionInstance {
  191. s := NewSession().AddInstance()
  192. s.Session().SetSignonTime(time.Unix(1, 0))
  193. s.Session().SetIdentScreenName(NewIdentScreenName("1000003"))
  194. s.Session().SetDisplayScreenName("1000003")
  195. s.SetUserInfoFlag(wire.OServiceUserFlagICQ)
  196. return s
  197. },
  198. want: wire.TLVUserInfo{
  199. ScreenName: "1000003",
  200. TLVBlock: wire.TLVBlock{
  201. TLVList: wire.TLVList{
  202. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  203. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagICQ),
  204. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  205. wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{}),
  206. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  207. },
  208. },
  209. },
  210. },
  211. {
  212. name: "user is on ICQ with direct connect info from client",
  213. givenSessionFn: func() *SessionInstance {
  214. s := NewSession().AddInstance()
  215. s.Session().SetSignonTime(time.Unix(1, 0))
  216. s.Session().SetIdentScreenName(NewIdentScreenName("1000003"))
  217. s.Session().SetDisplayScreenName("1000003")
  218. s.SetUserInfoFlag(wire.OServiceUserFlagICQ)
  219. s.SetICQDCInfo(wire.ICQDCInfo{
  220. DCType: 4,
  221. ProtoVersion: 10,
  222. })
  223. return s
  224. },
  225. want: wire.TLVUserInfo{
  226. ScreenName: "1000003",
  227. TLVBlock: wire.TLVBlock{
  228. TLVList: wire.TLVList{
  229. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  230. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagICQ),
  231. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  232. wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{
  233. DCType: 4,
  234. ProtoVersion: 10,
  235. }),
  236. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  237. },
  238. },
  239. },
  240. },
  241. {
  242. name: "user has away message set - all instances away",
  243. givenSessionFn: func() *SessionInstance {
  244. sg := NewSession()
  245. s := sg.AddInstance()
  246. s.Session().SetSignonTime(time.Unix(1, 0))
  247. s.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  248. // Add a second instance that is also away
  249. s2 := sg.AddInstance()
  250. s2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  251. return s
  252. },
  253. want: wire.TLVUserInfo{
  254. TLVBlock: wire.TLVBlock{
  255. TLVList: wire.TLVList{
  256. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  257. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x30)),
  258. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  259. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  260. },
  261. },
  262. },
  263. },
  264. {
  265. name: "user has one instance away, one not away - away flag not set",
  266. givenSessionFn: func() *SessionInstance {
  267. sg := NewSession()
  268. // Create the NOT away instance first so it's used as the base
  269. s2 := sg.AddInstance()
  270. s2.Session().SetSignonTime(time.Unix(1, 0))
  271. // s2 is NOT away - it has default flags only (OServiceUserFlagOSCARFree)
  272. // Now create the away instance
  273. s := sg.AddInstance()
  274. s.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  275. // Since s2 is the first instance and is not away, and allAway() returns false,
  276. // the unavailable flag should not be set
  277. return s
  278. },
  279. want: wire.TLVUserInfo{
  280. TLVBlock: wire.TLVBlock{
  281. TLVList: wire.TLVList{
  282. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  283. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x10)),
  284. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  285. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  286. },
  287. },
  288. },
  289. },
  290. {
  291. name: "user has two instances away, second goes off away - away flag not set",
  292. givenSessionFn: func() *SessionInstance {
  293. sg := NewSession()
  294. sg.SetSignonTime(time.Unix(1, 0))
  295. // Set the first instance as away
  296. s1 := sg.AddInstance()
  297. s1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  298. // Set the second instance as away
  299. s2 := sg.AddInstance()
  300. s2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  301. // Make the second instance as not away
  302. s2.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable)
  303. return s1
  304. },
  305. want: wire.TLVUserInfo{
  306. TLVBlock: wire.TLVBlock{
  307. TLVList: wire.TLVList{
  308. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  309. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x10)),
  310. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  311. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  312. },
  313. },
  314. },
  315. },
  316. {
  317. name: "user is invisible",
  318. givenSessionFn: func() *SessionInstance {
  319. s := NewSession().AddInstance()
  320. s.Session().SetSignonTime(time.Unix(1, 0))
  321. s.SetUserStatusBitmask(wire.OServiceUserStatusInvisible)
  322. return s
  323. },
  324. want: wire.TLVUserInfo{
  325. TLVBlock: wire.TLVBlock{
  326. TLVList: wire.TLVList{
  327. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  328. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  329. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
  330. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  331. },
  332. },
  333. },
  334. },
  335. {
  336. name: "user is idle",
  337. givenSessionFn: func() *SessionInstance {
  338. s := NewSession().AddInstance()
  339. // sign on at t=0m
  340. timeBegin := time.Unix(0, 0)
  341. s.Session().SetSignonTime(timeBegin)
  342. // set idle for 1m at t=+5m (ergo user idled @ t=+4m)
  343. timeIdle := timeBegin.Add(5 * time.Minute)
  344. s.Session().SetNowFn(func() time.Time { return timeIdle })
  345. s.SetIdle(1 * time.Minute)
  346. // now it's t=+10m, ergo idle time should be t10-t4=6m
  347. timeNow := timeBegin.Add(10 * time.Minute)
  348. s.Session().SetNowFn(func() time.Time { return timeNow })
  349. return s
  350. },
  351. want: wire.TLVUserInfo{
  352. TLVBlock: wire.TLVBlock{
  353. TLVList: wire.TLVList{
  354. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(0)),
  355. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  356. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  357. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(6)),
  358. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  359. },
  360. },
  361. },
  362. },
  363. {
  364. name: "user goes idle then returns",
  365. givenSessionFn: func() *SessionInstance {
  366. s := NewSession().AddInstance()
  367. s.Session().SetSignonTime(time.Unix(1, 0))
  368. s.SetIdle(1 * time.Second)
  369. s.UnsetIdle()
  370. return s
  371. },
  372. want: wire.TLVUserInfo{
  373. TLVBlock: wire.TLVBlock{
  374. TLVList: wire.TLVList{
  375. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  376. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  377. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  378. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  379. },
  380. },
  381. },
  382. },
  383. {
  384. name: "user has capabilities",
  385. givenSessionFn: func() *SessionInstance {
  386. s := NewSession().AddInstance()
  387. s.Session().SetSignonTime(time.Unix(1, 0))
  388. s.SetCaps([][16]byte{
  389. {
  390. // chat: "748F2420-6287-11D1-8222-444553540000"
  391. 0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  392. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00,
  393. },
  394. {
  395. // chat2: "748F2420-6287-11D1-8222-444553540000"
  396. 0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  397. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01,
  398. },
  399. })
  400. return s
  401. },
  402. want: wire.TLVUserInfo{
  403. TLVBlock: wire.TLVBlock{
  404. TLVList: wire.TLVList{
  405. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  406. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  407. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  408. wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, []byte{
  409. // chat: "748F2420-6287-11D1-8222-444553540000"
  410. 0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  411. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00,
  412. // chat: "748F2420-6287-11D1-8222-444553540000"
  413. 0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
  414. 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01,
  415. }),
  416. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  417. },
  418. },
  419. },
  420. },
  421. {
  422. name: "user has buddy icon",
  423. givenSessionFn: func() *SessionInstance {
  424. s := NewSession().AddInstance()
  425. s.Session().SetSignonTime(time.Unix(1, 0))
  426. return s
  427. },
  428. want: wire.TLVUserInfo{
  429. WarningLevel: 0,
  430. TLVBlock: wire.TLVBlock{
  431. TLVList: wire.TLVList{
  432. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
  433. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
  434. wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
  435. wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
  436. },
  437. },
  438. },
  439. },
  440. }
  441. for _, tt := range tests {
  442. t.Run(tt.name, func(t *testing.T) {
  443. s := tt.givenSessionFn()
  444. assert.Equal(t, tt.want, s.Session().TLVUserInfo())
  445. })
  446. }
  447. }
  448. func TestSession_SendAndRecvMessage_ExpectSessSendOK(t *testing.T) {
  449. s := NewSession().AddInstance()
  450. s.SetSignonComplete()
  451. msg := wire.SNACMessage{
  452. Frame: wire.SNACFrame{
  453. FoodGroup: wire.ICBM,
  454. },
  455. }
  456. var wg sync.WaitGroup
  457. wg.Add(1)
  458. go func() {
  459. defer wg.Done()
  460. defer s.CloseInstance()
  461. status := s.RelayMessageToInstance(msg)
  462. assert.Equal(t, SessSendOK, status)
  463. }()
  464. loop:
  465. for {
  466. select {
  467. case m := <-s.ReceiveMessage():
  468. assert.Equal(t, msg, m)
  469. case <-s.Closed():
  470. break loop
  471. }
  472. }
  473. wg.Wait()
  474. }
  475. func TestSession_SendMessage_SessSendClosed(t *testing.T) {
  476. s := NewSession().AddInstance()
  477. s.CloseInstance()
  478. if res := s.RelayMessageToInstance(wire.SNACMessage{}); res != SessSendClosed {
  479. t.Fatalf("expected SessSendClosed, got %+v", res)
  480. }
  481. }
  482. func TestSession_SendMessage_SessQueueFull(t *testing.T) {
  483. s := NewSession().AddInstance()
  484. s.SetSignonComplete()
  485. // Fill up the message channel (default buffer size is 1000)
  486. for i := 0; i < 1000; i++ {
  487. assert.Equal(t, SessSendOK, s.RelayMessageToInstance(wire.SNACMessage{}))
  488. }
  489. assert.Equal(t, SessQueueFull, s.RelayMessageToInstance(wire.SNACMessage{}))
  490. }
  491. func TestSession_Close_Twice(t *testing.T) {
  492. s := NewSession().AddInstance()
  493. s.CloseInstance()
  494. s.CloseInstance() // make sure close is idempotent
  495. // Check that the session is closed by trying to relay a message
  496. if res := s.RelayMessageToInstance(wire.SNACMessage{}); res != SessSendClosed {
  497. t.Fatalf("expected SessSendClosed, got %+v", res)
  498. }
  499. select {
  500. case <-s.Closed():
  501. case <-time.After(1 * time.Second):
  502. t.Fatalf("channel is not closed")
  503. }
  504. }
  505. func TestSession_Closed(t *testing.T) {
  506. s := NewSession().AddInstance()
  507. select {
  508. case <-s.Closed():
  509. assert.Fail(t, "channel is closed")
  510. default:
  511. // channel is open by default
  512. }
  513. s.Session().CloseSession()
  514. <-s.Closed()
  515. }
  516. func TestSession_EvaluateRateLimit_ObserveRateChanges(t *testing.T) {
  517. classParams := [5]wire.RateClass{
  518. {
  519. ID: 1,
  520. WindowSize: 80,
  521. ClearLevel: 2500,
  522. AlertLevel: 2000,
  523. LimitLevel: 1500,
  524. DisconnectLevel: 800,
  525. MaxLevel: 6000,
  526. },
  527. {
  528. ID: 2,
  529. WindowSize: 80,
  530. ClearLevel: 3000,
  531. AlertLevel: 2000,
  532. LimitLevel: 1500,
  533. DisconnectLevel: 1000,
  534. MaxLevel: 6000,
  535. },
  536. {
  537. ID: 3,
  538. WindowSize: 20,
  539. ClearLevel: 5100,
  540. AlertLevel: 5000,
  541. LimitLevel: 4000,
  542. DisconnectLevel: 3000,
  543. MaxLevel: 6000,
  544. },
  545. {
  546. ID: 4,
  547. WindowSize: 20,
  548. ClearLevel: 5500,
  549. AlertLevel: 5300,
  550. LimitLevel: 4200,
  551. DisconnectLevel: 3000,
  552. MaxLevel: 8000,
  553. },
  554. {
  555. ID: 5,
  556. WindowSize: 10,
  557. ClearLevel: 5500,
  558. AlertLevel: 5300,
  559. LimitLevel: 4200,
  560. DisconnectLevel: 3000,
  561. MaxLevel: 8000,
  562. },
  563. }
  564. rateClasses := wire.NewRateLimitClasses(classParams)
  565. t.Run("we can action every 5 seconds indefinitely without getting rate limited", func(t *testing.T) {
  566. now := time.Now()
  567. instance := NewSession().AddInstance()
  568. instance.Session().SetRateClasses(now, rateClasses)
  569. rateClass := rateClasses.Get(3)
  570. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  571. for i := 0; i < 100; i++ {
  572. now = now.Add(5 * time.Second)
  573. have := instance.Session().EvaluateRateLimit(now, rateClass.ID)
  574. assert.Equal(t, wire.RateLimitStatusClear, have)
  575. }
  576. })
  577. t.Run("reach disconnect threshold", func(t *testing.T) {
  578. now := time.Now()
  579. sess := NewSession()
  580. sess.SetRateClasses(now, rateClasses)
  581. sess.AddInstance()
  582. sess.AddInstance()
  583. sess.AddInstance()
  584. rateClass := rateClasses.Get(3)
  585. sess.SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  586. // record some event in the rate limiter
  587. want := []wire.RateLimitStatus{
  588. wire.RateLimitStatusClear,
  589. wire.RateLimitStatusClear,
  590. wire.RateLimitStatusClear,
  591. wire.RateLimitStatusClear,
  592. wire.RateLimitStatusAlert,
  593. wire.RateLimitStatusAlert,
  594. wire.RateLimitStatusAlert,
  595. wire.RateLimitStatusAlert,
  596. wire.RateLimitStatusAlert,
  597. wire.RateLimitStatusLimited,
  598. wire.RateLimitStatusLimited,
  599. wire.RateLimitStatusLimited,
  600. wire.RateLimitStatusLimited,
  601. wire.RateLimitStatusLimited,
  602. wire.RateLimitStatusLimited,
  603. wire.RateLimitStatusLimited,
  604. wire.RateLimitStatusLimited,
  605. wire.RateLimitStatusDisconnect,
  606. }
  607. for i := 0; i < len(want); i++ {
  608. now = now.Add(1 * time.Second)
  609. have := sess.EvaluateRateLimit(now, rateClass.ID)
  610. assert.Equal(t, want[i], have)
  611. }
  612. for _, instance := range sess.Instances() {
  613. select {
  614. case <-instance.Closed():
  615. default:
  616. t.Error("expected session to be closed")
  617. }
  618. }
  619. })
  620. t.Run("reach rate limit threshold, wait for clear threshold", func(t *testing.T) {
  621. now := time.Now()
  622. instance := NewSession().AddInstance()
  623. instance.Session().SetRateClasses(now, rateClasses)
  624. rateClass := rateClasses.Get(3)
  625. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  626. // first reach the rate limit threshold
  627. want := []wire.RateLimitStatus{
  628. wire.RateLimitStatusClear,
  629. wire.RateLimitStatusClear,
  630. wire.RateLimitStatusClear,
  631. wire.RateLimitStatusClear,
  632. wire.RateLimitStatusAlert,
  633. wire.RateLimitStatusAlert,
  634. wire.RateLimitStatusAlert,
  635. wire.RateLimitStatusAlert,
  636. wire.RateLimitStatusAlert,
  637. wire.RateLimitStatusLimited,
  638. }
  639. for i := 0; i < len(want); i++ {
  640. now = now.Add(1 * time.Second)
  641. have := instance.Session().EvaluateRateLimit(now, rateClass.ID)
  642. assert.Equal(t, want[i], have)
  643. if i > 0 && want[i-1] != want[i] {
  644. classChanges, rateChanges := instance.Session().ObserveRateChanges(now)
  645. assert.Empty(t, classChanges)
  646. if assert.NotEmpty(t, rateChanges) {
  647. rateDelta := rateChanges[0]
  648. assert.Equal(t, rateClass, rateDelta.RateClass)
  649. assert.Equal(t, want[i], rateDelta.CurrentStatus)
  650. assert.True(t, rateDelta.Subscribed)
  651. if want[i] == wire.RateLimitStatusLimited {
  652. assert.True(t, rateDelta.LimitedNow)
  653. }
  654. }
  655. }
  656. }
  657. // this is a rearranged moving average formula that determines how many
  658. // milliseconds it will take to reach the clear threshold
  659. rateLimitStates := instance.RateLimitStates()
  660. timeToRecover := int(math.Ceil((time.Duration(rateClass.ClearLevel*rateClass.WindowSize-rateLimitStates[rateClass.ID-1].CurrentLevel*(rateClass.WindowSize-1)) * time.Millisecond).Seconds()))
  661. assert.True(t, timeToRecover > 0)
  662. // indicate the time rate limiting kicked in
  663. timeLimited := now
  664. for i := 0; i < timeToRecover; i++ {
  665. now = now.Add(1 * time.Second)
  666. classDelta, stateDelta := instance.Session().ObserveRateChanges(now)
  667. assert.Empty(t, classDelta)
  668. if i == timeToRecover-1 {
  669. // assert that the clear threshold has been met.
  670. assert.ElementsMatch(t, stateDelta, []RateClassState{
  671. {
  672. RateClass: rateClass,
  673. CurrentLevel: 5140,
  674. CurrentStatus: wire.RateLimitStatusClear,
  675. LastTime: timeLimited,
  676. Subscribed: true,
  677. LimitedNow: false,
  678. }})
  679. } else {
  680. // assert that no changed have been observed, it's still rate-limited
  681. assert.Nil(t, stateDelta)
  682. }
  683. }
  684. })
  685. t.Run("observe a rate class change", func(t *testing.T) {
  686. now := time.Now()
  687. instance := NewSession().AddInstance()
  688. instance.Session().SetRateClasses(now, rateClasses)
  689. rateClass := rateClasses.Get(3)
  690. instance.Session().SubscribeRateLimits([]wire.RateLimitClassID{rateClass.ID})
  691. now = now.Add(1 * time.Second)
  692. classDelta, stateDelta := instance.Session().ObserveRateChanges(now)
  693. assert.Empty(t, classDelta)
  694. assert.Empty(t, stateDelta)
  695. paramsCopy := classParams
  696. paramsCopy[rateClass.ID-1].LimitLevel++
  697. newRateClasses := wire.NewRateLimitClasses(paramsCopy)
  698. now = now.Add(1 * time.Second)
  699. instance.Session().SetRateClasses(now, newRateClasses)
  700. now = now.Add(1 * time.Second)
  701. classDelta, stateDelta = instance.Session().ObserveRateChanges(now)
  702. assert.Equal(t, classDelta[0].RateClass, newRateClasses.Get(rateClass.ID))
  703. assert.Empty(t, stateDelta)
  704. })
  705. t.Run("as a bot, I can action every second indefinitely without getting rate limited", func(t *testing.T) {
  706. now := time.Now()
  707. instance := NewSession().AddInstance()
  708. instance.SetUserInfoFlag(wire.OServiceUserFlagBot)
  709. instance.Session().SetRateClasses(now, rateClasses)
  710. for i := 0; i < 100; i++ {
  711. now = now.Add(1 * time.Second)
  712. have := instance.Session().EvaluateRateLimit(now, wire.RateLimitClassID(1))
  713. assert.Equal(t, wire.RateLimitStatusClear, have)
  714. }
  715. })
  716. }
  717. func TestSession_SetAndGetFoodGroupVersions(t *testing.T) {
  718. versions := [wire.MDir + 1]uint16{}
  719. versions[wire.Feedbag] = 1
  720. versions[wire.OService] = 2
  721. s := NewSession().AddInstance()
  722. s.SetFoodGroupVersions(versions)
  723. assert.Equal(t, versions, s.FoodGroupVersions())
  724. }
  725. func TestSession_SetAndGetTypingEventsEnabled(t *testing.T) {
  726. s := NewSession().AddInstance()
  727. assert.False(t, s.TypingEventsEnabled())
  728. s.Session().SetTypingEventsEnabled(true)
  729. assert.True(t, s.TypingEventsEnabled())
  730. s.Session().SetTypingEventsEnabled(false)
  731. assert.False(t, s.TypingEventsEnabled())
  732. }
  733. func TestSession_SetAndGetMultiConnFlag(t *testing.T) {
  734. s := NewSession().AddInstance()
  735. assert.Zero(t, s.MultiConnFlag())
  736. s.SetMultiConnFlag(wire.MultiConnFlagsOldClient)
  737. assert.Equal(t, wire.MultiConnFlagsOldClient, s.MultiConnFlag())
  738. s.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
  739. assert.Equal(t, wire.MultiConnFlagsRecentClient, s.MultiConnFlag())
  740. s.SetMultiConnFlag(wire.MultiConnFlagsSingleClient)
  741. assert.Equal(t, wire.MultiConnFlagsSingleClient, s.MultiConnFlag())
  742. }
  743. func TestSession_SetAndGetLastWarnLevel(t *testing.T) {
  744. s := NewSession().AddInstance()
  745. assert.Zero(t, s.Warning())
  746. level := uint16(500)
  747. s.Session().SetWarning(level)
  748. assert.Equal(t, level, s.Warning())
  749. }
  750. func TestSessionInstance_ContactsInit(t *testing.T) {
  751. instance := NewSession().AddInstance()
  752. assert.False(t, instance.ContactsInit())
  753. instance.SetContactsInit()
  754. assert.True(t, instance.ContactsInit())
  755. instance.SetContactsInit()
  756. assert.True(t, instance.ContactsInit())
  757. }
  758. func TestInstance_Active(t *testing.T) {
  759. tests := []struct {
  760. name string
  761. setupInstance func() *SessionInstance
  762. expectedActive bool
  763. }{
  764. {
  765. name: "active instance - not closed, not idle, no away message",
  766. setupInstance: func() *SessionInstance {
  767. sg := NewSession()
  768. instance := &SessionInstance{
  769. session: sg,
  770. closed: false,
  771. idle: false,
  772. awayMsg: "",
  773. signonComplete: true,
  774. }
  775. return instance
  776. },
  777. expectedActive: true,
  778. },
  779. {
  780. name: "inactive instance - closed",
  781. setupInstance: func() *SessionInstance {
  782. sg := NewSession()
  783. instance := &SessionInstance{
  784. session: sg,
  785. closed: true,
  786. idle: false,
  787. awayMsg: "",
  788. }
  789. return instance
  790. },
  791. expectedActive: false,
  792. },
  793. {
  794. name: "inactive instance - idle",
  795. setupInstance: func() *SessionInstance {
  796. sg := NewSession()
  797. instance := &SessionInstance{
  798. session: sg,
  799. closed: false,
  800. idle: true,
  801. awayMsg: "",
  802. }
  803. return instance
  804. },
  805. expectedActive: false,
  806. },
  807. {
  808. name: "inactive instance - has away message",
  809. setupInstance: func() *SessionInstance {
  810. sg := NewSession()
  811. instance := &SessionInstance{
  812. session: sg,
  813. closed: false,
  814. idle: false,
  815. awayMsg: "I'm away",
  816. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  817. }
  818. return instance
  819. },
  820. expectedActive: false,
  821. },
  822. {
  823. name: "inactive instance - closed and idle",
  824. setupInstance: func() *SessionInstance {
  825. sg := NewSession()
  826. instance := &SessionInstance{
  827. session: sg,
  828. closed: true,
  829. idle: true,
  830. awayMsg: "",
  831. }
  832. return instance
  833. },
  834. expectedActive: false,
  835. },
  836. {
  837. name: "inactive instance - closed and has away message",
  838. setupInstance: func() *SessionInstance {
  839. sg := NewSession()
  840. instance := &SessionInstance{
  841. session: sg,
  842. closed: true,
  843. idle: false,
  844. awayMsg: "I'm away",
  845. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  846. }
  847. return instance
  848. },
  849. expectedActive: false,
  850. },
  851. {
  852. name: "inactive instance - idle and has away message",
  853. setupInstance: func() *SessionInstance {
  854. sg := NewSession()
  855. instance := &SessionInstance{
  856. session: sg,
  857. closed: false,
  858. idle: true,
  859. awayMsg: "I'm away",
  860. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  861. }
  862. return instance
  863. },
  864. expectedActive: false,
  865. },
  866. {
  867. name: "inactive instance - closed, idle, and has away message",
  868. setupInstance: func() *SessionInstance {
  869. sg := NewSession()
  870. instance := &SessionInstance{
  871. session: sg,
  872. closed: true,
  873. idle: true,
  874. awayMsg: "I'm away",
  875. userInfoBitmask: wire.OServiceUserFlagUnavailable,
  876. }
  877. return instance
  878. },
  879. expectedActive: false,
  880. },
  881. {
  882. name: "inactive instance - signon not complete",
  883. setupInstance: func() *SessionInstance {
  884. sg := NewSession()
  885. instance := &SessionInstance{
  886. session: sg,
  887. closed: false,
  888. idle: false,
  889. awayMsg: "",
  890. signonComplete: false,
  891. }
  892. return instance
  893. },
  894. expectedActive: false,
  895. },
  896. {
  897. name: "inactive instance - signon not complete and idle",
  898. setupInstance: func() *SessionInstance {
  899. sg := NewSession()
  900. instance := &SessionInstance{
  901. session: sg,
  902. closed: false,
  903. idle: true,
  904. awayMsg: "",
  905. signonComplete: false,
  906. }
  907. return instance
  908. },
  909. expectedActive: false,
  910. },
  911. }
  912. for _, tt := range tests {
  913. t.Run(tt.name, func(t *testing.T) {
  914. instance := tt.setupInstance()
  915. assert.Equal(t, tt.expectedActive, instance.active())
  916. })
  917. }
  918. }
  919. func TestSessionGroup_AllInactive(t *testing.T) {
  920. tests := []struct {
  921. name string
  922. setupSessionGroup func() *Session
  923. expectedResult bool
  924. }{
  925. {
  926. name: "no instances - should return true",
  927. setupSessionGroup: func() *Session {
  928. return NewSession()
  929. },
  930. expectedResult: true,
  931. },
  932. {
  933. name: "one active instance - should return false",
  934. setupSessionGroup: func() *Session {
  935. sg := NewSession()
  936. instance := sg.AddInstance()
  937. instance.closed = false
  938. instance.idle = false
  939. instance.awayMsg = ""
  940. instance.signonComplete = true
  941. return sg
  942. },
  943. expectedResult: false,
  944. },
  945. {
  946. name: "one closed instance - should return true",
  947. setupSessionGroup: func() *Session {
  948. sg := NewSession()
  949. instance := sg.AddInstance()
  950. instance.closed = true
  951. instance.idle = false
  952. instance.awayMsg = ""
  953. return sg
  954. },
  955. expectedResult: true,
  956. },
  957. {
  958. name: "one idle instance - should return true",
  959. setupSessionGroup: func() *Session {
  960. sg := NewSession()
  961. instance := sg.AddInstance()
  962. instance.closed = false
  963. instance.idle = true
  964. instance.awayMsg = ""
  965. return sg
  966. },
  967. expectedResult: true,
  968. },
  969. {
  970. name: "one instance with away message - should return true",
  971. setupSessionGroup: func() *Session {
  972. sg := NewSession()
  973. instance := sg.AddInstance()
  974. instance.closed = false
  975. instance.idle = false
  976. instance.awayMsg = "I'm away"
  977. return sg
  978. },
  979. expectedResult: true,
  980. },
  981. {
  982. name: "multiple instances - all inactive - should return true",
  983. setupSessionGroup: func() *Session {
  984. sg := NewSession()
  985. // Add closed instance
  986. instance1 := sg.AddInstance()
  987. instance1.closed = true
  988. instance1.idle = false
  989. instance1.awayMsg = ""
  990. // Add idle instance
  991. instance2 := sg.AddInstance()
  992. instance2.closed = false
  993. instance2.idle = true
  994. instance2.awayMsg = ""
  995. // Add instance with away message
  996. instance3 := sg.AddInstance()
  997. instance3.closed = false
  998. instance3.idle = false
  999. instance3.awayMsg = "I'm away"
  1000. return sg
  1001. },
  1002. expectedResult: true,
  1003. },
  1004. {
  1005. name: "multiple instances - one active - should return false",
  1006. setupSessionGroup: func() *Session {
  1007. sg := NewSession()
  1008. // Add closed instance
  1009. instance1 := sg.AddInstance()
  1010. instance1.closed = true
  1011. instance1.idle = false
  1012. instance1.awayMsg = ""
  1013. // Add active instance
  1014. instance2 := sg.AddInstance()
  1015. instance2.closed = false
  1016. instance2.idle = false
  1017. instance2.awayMsg = ""
  1018. instance2.signonComplete = true
  1019. // Add idle instance
  1020. instance3 := sg.AddInstance()
  1021. instance3.closed = false
  1022. instance3.idle = true
  1023. instance3.awayMsg = ""
  1024. return sg
  1025. },
  1026. expectedResult: false,
  1027. },
  1028. {
  1029. name: "multiple instances - all active - should return false",
  1030. setupSessionGroup: func() *Session {
  1031. sg := NewSession()
  1032. // Add first active instance
  1033. instance1 := sg.AddInstance()
  1034. instance1.closed = false
  1035. instance1.idle = false
  1036. instance1.awayMsg = ""
  1037. instance1.signonComplete = true
  1038. // Add second active instance
  1039. instance2 := sg.AddInstance()
  1040. instance2.closed = false
  1041. instance2.idle = false
  1042. instance2.awayMsg = ""
  1043. instance2.signonComplete = true
  1044. return sg
  1045. },
  1046. expectedResult: false,
  1047. },
  1048. {
  1049. name: "mixed scenarios - some closed, some idle, some away, one active - should return false",
  1050. setupSessionGroup: func() *Session {
  1051. sg := NewSession()
  1052. // Add closed instance
  1053. instance1 := sg.AddInstance()
  1054. instance1.closed = true
  1055. instance1.idle = false
  1056. instance1.awayMsg = ""
  1057. // Add idle instance
  1058. instance2 := sg.AddInstance()
  1059. instance2.closed = false
  1060. instance2.idle = true
  1061. instance2.awayMsg = ""
  1062. // Add instance with away message
  1063. instance3 := sg.AddInstance()
  1064. instance3.closed = false
  1065. instance3.idle = false
  1066. instance3.awayMsg = "I'm away"
  1067. // Add active instance
  1068. instance4 := sg.AddInstance()
  1069. instance4.closed = false
  1070. instance4.idle = false
  1071. instance4.awayMsg = ""
  1072. instance4.signonComplete = true
  1073. return sg
  1074. },
  1075. expectedResult: false,
  1076. },
  1077. }
  1078. for _, tt := range tests {
  1079. t.Run(tt.name, func(t *testing.T) {
  1080. sg := tt.setupSessionGroup()
  1081. assert.Equal(t, tt.expectedResult, sg.Inactive())
  1082. })
  1083. }
  1084. }
  1085. func TestSessionGroup_InstanceCount(t *testing.T) {
  1086. tests := []struct {
  1087. name string
  1088. setupGroup func() *Session
  1089. expectedCount int
  1090. }{
  1091. {
  1092. name: "empty session group should return 0",
  1093. setupGroup: func() *Session {
  1094. return NewSession()
  1095. },
  1096. expectedCount: 0,
  1097. },
  1098. {
  1099. name: "one instance should return 1",
  1100. setupGroup: func() *Session {
  1101. sg := NewSession()
  1102. sg.AddInstance()
  1103. return sg
  1104. },
  1105. expectedCount: 1,
  1106. },
  1107. {
  1108. name: "multiple instances should return correct count",
  1109. setupGroup: func() *Session {
  1110. sg := NewSession()
  1111. for i := 0; i < 3; i++ {
  1112. sg.AddInstance()
  1113. }
  1114. return sg
  1115. },
  1116. expectedCount: 3,
  1117. },
  1118. {
  1119. name: "instance count decreases after removal",
  1120. setupGroup: func() *Session {
  1121. sg := NewSession()
  1122. sg.AddInstance()
  1123. instance2 := sg.AddInstance()
  1124. sg.AddInstance()
  1125. // Remove one instance
  1126. sg.RemoveInstance(instance2)
  1127. return sg
  1128. },
  1129. expectedCount: 2,
  1130. },
  1131. {
  1132. name: "instance count is correct after multiple add/remove operations",
  1133. setupGroup: func() *Session {
  1134. sg := NewSession()
  1135. instance1 := sg.AddInstance()
  1136. sg.AddInstance()
  1137. sg.RemoveInstance(instance1)
  1138. sg.AddInstance()
  1139. return sg
  1140. },
  1141. expectedCount: 2,
  1142. },
  1143. }
  1144. for _, tt := range tests {
  1145. t.Run(tt.name, func(t *testing.T) {
  1146. sg := tt.setupGroup()
  1147. assert.Equal(t, tt.expectedCount, sg.InstanceCount())
  1148. })
  1149. }
  1150. }
  1151. func TestSessionGroup_Instances(t *testing.T) {
  1152. tests := []struct {
  1153. name string
  1154. setupGroup func() *Session
  1155. expectedCount int
  1156. expectedAll bool // whether all instances should be returned (including non-signed-in)
  1157. }{
  1158. {
  1159. name: "empty session group should return empty slice",
  1160. setupGroup: func() *Session {
  1161. return NewSession()
  1162. },
  1163. expectedCount: 0,
  1164. expectedAll: true,
  1165. },
  1166. {
  1167. name: "returns all instances including non-signed-in",
  1168. setupGroup: func() *Session {
  1169. sg := NewSession()
  1170. instance1 := sg.AddInstance()
  1171. instance1.SetSignonComplete()
  1172. _ = sg.AddInstance()
  1173. // instance2 has not completed signon
  1174. return sg
  1175. },
  1176. expectedCount: 2,
  1177. expectedAll: true,
  1178. },
  1179. {
  1180. name: "returns all instances with mixed signon states",
  1181. setupGroup: func() *Session {
  1182. sg := NewSession()
  1183. instance1 := sg.AddInstance()
  1184. instance1.SetSignonComplete()
  1185. _ = sg.AddInstance()
  1186. // instance2 has not completed signon
  1187. instance3 := sg.AddInstance()
  1188. instance3.SetSignonComplete()
  1189. return sg
  1190. },
  1191. expectedCount: 3,
  1192. expectedAll: true,
  1193. },
  1194. }
  1195. for _, tt := range tests {
  1196. t.Run(tt.name, func(t *testing.T) {
  1197. sg := tt.setupGroup()
  1198. instances := sg.Instances()
  1199. assert.Equal(t, tt.expectedCount, len(instances), "should return all instances")
  1200. if tt.expectedAll {
  1201. // Verify that instances() returns all instances regardless of their state
  1202. // This is the key change: it should return all instances, not just live ones
  1203. assert.Equal(t, sg.InstanceCount(), len(instances), "Instances() should return all instances")
  1204. }
  1205. })
  1206. }
  1207. }
  1208. func TestSession_SetAndGetProfile(t *testing.T) {
  1209. s := NewSession().AddInstance()
  1210. profile := s.Session().Profile()
  1211. assert.Empty(t, profile.ProfileText)
  1212. assert.Empty(t, profile.MIMEType)
  1213. assert.True(t, profile.UpdateTime.IsZero())
  1214. profileTime := time.Unix(1234567890, 0)
  1215. newProfile := UserProfile{
  1216. ProfileText: "My profile text",
  1217. MIMEType: "text/plain",
  1218. UpdateTime: profileTime,
  1219. }
  1220. s.SetProfile(newProfile)
  1221. retrievedProfile := s.Session().Profile()
  1222. assert.Equal(t, newProfile, retrievedProfile)
  1223. assert.Equal(t, "My profile text", retrievedProfile.ProfileText)
  1224. assert.Equal(t, "text/plain", retrievedProfile.MIMEType)
  1225. assert.Equal(t, profileTime, retrievedProfile.UpdateTime)
  1226. }
  1227. func TestSession_Profile(t *testing.T) {
  1228. tests := []struct {
  1229. name string
  1230. setupSession func() *Session
  1231. expectedProfile UserProfile
  1232. }{
  1233. {
  1234. name: "no instances - returns empty profile",
  1235. setupSession: func() *Session {
  1236. return NewSession()
  1237. },
  1238. expectedProfile: UserProfile{},
  1239. },
  1240. {
  1241. name: "one instance with empty profile - returns empty profile",
  1242. setupSession: func() *Session {
  1243. s := NewSession()
  1244. s.AddInstance()
  1245. return s
  1246. },
  1247. expectedProfile: UserProfile{},
  1248. },
  1249. {
  1250. name: "one instance with non-empty profile - returns that profile",
  1251. setupSession: func() *Session {
  1252. s := NewSession()
  1253. instance := s.AddInstance()
  1254. profileTime := time.Unix(1234567890, 0)
  1255. instance.SetProfile(UserProfile{
  1256. ProfileText: "My profile",
  1257. MIMEType: "text/plain",
  1258. UpdateTime: profileTime,
  1259. })
  1260. return s
  1261. },
  1262. expectedProfile: UserProfile{
  1263. ProfileText: "My profile",
  1264. MIMEType: "text/plain",
  1265. UpdateTime: time.Unix(1234567890, 0),
  1266. },
  1267. },
  1268. {
  1269. name: "multiple instances, all empty - returns empty profile",
  1270. setupSession: func() *Session {
  1271. s := NewSession()
  1272. s.AddInstance()
  1273. s.AddInstance()
  1274. s.AddInstance()
  1275. return s
  1276. },
  1277. expectedProfile: UserProfile{},
  1278. },
  1279. {
  1280. name: "multiple instances, one non-empty - returns that one",
  1281. setupSession: func() *Session {
  1282. s := NewSession()
  1283. s.AddInstance() // empty instance
  1284. instance2 := s.AddInstance()
  1285. instance2.SetProfile(UserProfile{
  1286. ProfileText: "Profile 2",
  1287. MIMEType: "text/plain",
  1288. UpdateTime: time.Unix(1234567890, 0),
  1289. })
  1290. s.AddInstance() // empty instance
  1291. return s
  1292. },
  1293. expectedProfile: UserProfile{
  1294. ProfileText: "Profile 2",
  1295. MIMEType: "text/plain",
  1296. UpdateTime: time.Unix(1234567890, 0),
  1297. },
  1298. },
  1299. {
  1300. name: "multiple instances, multiple non-empty - returns most recent UpdateTime",
  1301. setupSession: func() *Session {
  1302. s := NewSession()
  1303. instance1 := s.AddInstance()
  1304. instance1.SetProfile(UserProfile{
  1305. ProfileText: "Profile 1",
  1306. MIMEType: "text/plain",
  1307. UpdateTime: time.Unix(1234567900, 0), // later time - should be returned
  1308. })
  1309. instance2 := s.AddInstance()
  1310. instance2.SetProfile(UserProfile{
  1311. ProfileText: "Profile 2",
  1312. MIMEType: "text/plain",
  1313. UpdateTime: time.Unix(1234567890, 0), // earlier time
  1314. })
  1315. instance3 := s.AddInstance()
  1316. instance3.SetProfile(UserProfile{
  1317. ProfileText: "Profile 3",
  1318. MIMEType: "text/plain",
  1319. UpdateTime: time.Unix(1234567895, 0), // middle time
  1320. })
  1321. return s
  1322. },
  1323. expectedProfile: UserProfile{
  1324. ProfileText: "Profile 1",
  1325. MIMEType: "text/plain",
  1326. UpdateTime: time.Unix(1234567900, 0),
  1327. },
  1328. },
  1329. {
  1330. name: "first instance empty, later instances have profiles - returns most recent non-empty",
  1331. setupSession: func() *Session {
  1332. s := NewSession()
  1333. s.AddInstance() // empty instance
  1334. instance2 := s.AddInstance()
  1335. instance2.SetProfile(UserProfile{
  1336. ProfileText: "Profile 2",
  1337. MIMEType: "text/plain",
  1338. UpdateTime: time.Unix(1234567890, 0), // earlier
  1339. })
  1340. instance3 := s.AddInstance()
  1341. instance3.SetProfile(UserProfile{
  1342. ProfileText: "Profile 3",
  1343. MIMEType: "text/plain",
  1344. UpdateTime: time.Unix(1234567900, 0), // later time - should be returned
  1345. })
  1346. return s
  1347. },
  1348. expectedProfile: UserProfile{
  1349. ProfileText: "Profile 3",
  1350. MIMEType: "text/plain",
  1351. UpdateTime: time.Unix(1234567900, 0),
  1352. },
  1353. },
  1354. {
  1355. name: "profile with empty ProfileText is considered empty",
  1356. setupSession: func() *Session {
  1357. s := NewSession()
  1358. instance := s.AddInstance()
  1359. instance.SetProfile(UserProfile{
  1360. ProfileText: "",
  1361. MIMEType: "text/plain",
  1362. UpdateTime: time.Unix(1234567890, 0),
  1363. })
  1364. return s
  1365. },
  1366. expectedProfile: UserProfile{},
  1367. },
  1368. {
  1369. name: "profile with null byte ProfileText is considered empty",
  1370. setupSession: func() *Session {
  1371. s := NewSession()
  1372. instance := s.AddInstance()
  1373. instance.SetProfile(UserProfile{
  1374. ProfileText: "\x00",
  1375. MIMEType: "text/plain",
  1376. UpdateTime: time.Unix(1234567890, 0),
  1377. })
  1378. return s
  1379. },
  1380. expectedProfile: UserProfile{},
  1381. },
  1382. }
  1383. for _, tt := range tests {
  1384. t.Run(tt.name, func(t *testing.T) {
  1385. s := tt.setupSession()
  1386. profile := s.Profile()
  1387. assert.Equal(t, tt.expectedProfile, profile)
  1388. })
  1389. }
  1390. }
  1391. func TestSession_SetAndGetMemberSince(t *testing.T) {
  1392. s := NewSession().AddInstance()
  1393. assert.True(t, s.Session().MemberSince().IsZero())
  1394. memberTime := time.Unix(1234567890, 0)
  1395. s.Session().SetMemberSince(memberTime)
  1396. assert.Equal(t, memberTime, s.Session().MemberSince())
  1397. }
  1398. func TestSession_SetAndGetOfflineMsgCount(t *testing.T) {
  1399. s := NewSession().AddInstance()
  1400. assert.Zero(t, s.OfflineMsgCount())
  1401. count := 5
  1402. s.Session().SetOfflineMsgCount(count)
  1403. assert.Equal(t, count, s.OfflineMsgCount())
  1404. count = 10
  1405. s.Session().SetOfflineMsgCount(count)
  1406. assert.Equal(t, count, s.OfflineMsgCount())
  1407. }
  1408. func TestSession_ScaleWarningAndRateLimit(t *testing.T) {
  1409. t.Run("scale up", func(t *testing.T) {
  1410. classParams := [5]wire.RateClass{
  1411. {},
  1412. {},
  1413. {
  1414. ID: 3,
  1415. WindowSize: 20,
  1416. ClearLevel: 5100,
  1417. AlertLevel: 5000,
  1418. LimitLevel: 4000,
  1419. DisconnectLevel: 3000,
  1420. MaxLevel: 6000,
  1421. },
  1422. {},
  1423. {},
  1424. }
  1425. rateClasses := wire.NewRateLimitClasses(classParams)
  1426. now := time.Now()
  1427. instance := NewSession().AddInstance()
  1428. instance.Session().SetRateClasses(now, rateClasses)
  1429. var wg sync.WaitGroup
  1430. wg.Add(1)
  1431. ctx, cancel := context.WithCancel(t.Context())
  1432. go func() {
  1433. defer wg.Done()
  1434. for {
  1435. select {
  1436. case <-ctx.Done():
  1437. return
  1438. case <-instance.WarningCh():
  1439. }
  1440. }
  1441. }()
  1442. rateLimitStates := instance.RateLimitStates()
  1443. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1444. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1445. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1446. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1447. rateLimitStates = instance.RateLimitStates()
  1448. assert.Equal(t, int32(5085), rateLimitStates[2].AlertLevel)
  1449. assert.Equal(t, int32(5175), rateLimitStates[2].ClearLevel)
  1450. assert.Equal(t, int32(4185), rateLimitStates[2].LimitLevel)
  1451. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1452. rateLimitStates = instance.RateLimitStates()
  1453. assert.Equal(t, int32(5170), rateLimitStates[2].AlertLevel)
  1454. assert.Equal(t, int32(5250), rateLimitStates[2].ClearLevel)
  1455. assert.Equal(t, int32(4370), rateLimitStates[2].LimitLevel)
  1456. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1457. rateLimitStates = instance.RateLimitStates()
  1458. assert.Equal(t, int32(5255), rateLimitStates[2].AlertLevel)
  1459. assert.Equal(t, int32(5325), rateLimitStates[2].ClearLevel)
  1460. assert.Equal(t, int32(4555), rateLimitStates[2].LimitLevel)
  1461. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1462. rateLimitStates = instance.RateLimitStates()
  1463. assert.Equal(t, int32(5340), rateLimitStates[2].AlertLevel)
  1464. assert.Equal(t, int32(5400), rateLimitStates[2].ClearLevel)
  1465. assert.Equal(t, int32(4740), rateLimitStates[2].LimitLevel)
  1466. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1467. rateLimitStates = instance.RateLimitStates()
  1468. assert.Equal(t, int32(5425), rateLimitStates[2].AlertLevel)
  1469. assert.Equal(t, int32(5475), rateLimitStates[2].ClearLevel)
  1470. assert.Equal(t, int32(4925), rateLimitStates[2].LimitLevel)
  1471. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1472. rateLimitStates = instance.RateLimitStates()
  1473. assert.Equal(t, int32(5510), rateLimitStates[2].AlertLevel)
  1474. assert.Equal(t, int32(5550), rateLimitStates[2].ClearLevel)
  1475. assert.Equal(t, int32(5110), rateLimitStates[2].LimitLevel)
  1476. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1477. rateLimitStates = instance.RateLimitStates()
  1478. assert.Equal(t, int32(5595), rateLimitStates[2].AlertLevel)
  1479. assert.Equal(t, int32(5625), rateLimitStates[2].ClearLevel)
  1480. assert.Equal(t, int32(5295), rateLimitStates[2].LimitLevel)
  1481. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1482. rateLimitStates = instance.RateLimitStates()
  1483. assert.Equal(t, int32(5680), rateLimitStates[2].AlertLevel)
  1484. assert.Equal(t, int32(5700), rateLimitStates[2].ClearLevel)
  1485. assert.Equal(t, int32(5480), rateLimitStates[2].LimitLevel)
  1486. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1487. rateLimitStates = instance.RateLimitStates()
  1488. assert.Equal(t, int32(5765), rateLimitStates[2].AlertLevel)
  1489. assert.Equal(t, int32(5775), rateLimitStates[2].ClearLevel)
  1490. assert.Equal(t, int32(5665), rateLimitStates[2].LimitLevel)
  1491. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1492. rateLimitStates = instance.RateLimitStates()
  1493. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1494. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1495. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1496. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1497. rateLimitStates = instance.RateLimitStates()
  1498. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1499. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1500. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1501. cancel()
  1502. wg.Wait()
  1503. })
  1504. t.Run("scale down", func(t *testing.T) {
  1505. currentClassParams := [5]wire.RateClass{
  1506. {},
  1507. {},
  1508. {
  1509. ID: 3,
  1510. WindowSize: 20,
  1511. ClearLevel: 5100,
  1512. AlertLevel: 5000,
  1513. LimitLevel: 4000,
  1514. DisconnectLevel: 3000,
  1515. MaxLevel: 6000,
  1516. },
  1517. {},
  1518. {},
  1519. }
  1520. rateClasses := wire.NewRateLimitClasses(currentClassParams)
  1521. now := time.Now()
  1522. instance := NewSession().AddInstance()
  1523. instance.Session().SetRateClasses(now, rateClasses)
  1524. var wg sync.WaitGroup
  1525. wg.Add(1)
  1526. ctx, cancel := context.WithCancel(t.Context())
  1527. go func() {
  1528. defer wg.Done()
  1529. for {
  1530. select {
  1531. case <-ctx.Done():
  1532. return
  1533. case <-instance.WarningCh():
  1534. }
  1535. }
  1536. }()
  1537. for i := 0; i < 10; i++ {
  1538. instance.Session().ScaleWarningAndRateLimit(100, 3)
  1539. }
  1540. rateLimitStates := instance.RateLimitStates()
  1541. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1542. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1543. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1544. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1545. rateLimitStates = instance.RateLimitStates()
  1546. assert.Equal(t, int32(5765), rateLimitStates[2].AlertLevel)
  1547. assert.Equal(t, int32(5775), rateLimitStates[2].ClearLevel)
  1548. assert.Equal(t, int32(5665), rateLimitStates[2].LimitLevel)
  1549. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1550. rateLimitStates = instance.RateLimitStates()
  1551. assert.Equal(t, int32(5680), rateLimitStates[2].AlertLevel)
  1552. assert.Equal(t, int32(5700), rateLimitStates[2].ClearLevel)
  1553. assert.Equal(t, int32(5480), rateLimitStates[2].LimitLevel)
  1554. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1555. rateLimitStates = instance.RateLimitStates()
  1556. assert.Equal(t, int32(5595), rateLimitStates[2].AlertLevel)
  1557. assert.Equal(t, int32(5625), rateLimitStates[2].ClearLevel)
  1558. assert.Equal(t, int32(5295), rateLimitStates[2].LimitLevel)
  1559. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1560. rateLimitStates = instance.RateLimitStates()
  1561. assert.Equal(t, int32(5510), rateLimitStates[2].AlertLevel)
  1562. assert.Equal(t, int32(5550), rateLimitStates[2].ClearLevel)
  1563. assert.Equal(t, int32(5110), rateLimitStates[2].LimitLevel)
  1564. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1565. rateLimitStates = instance.RateLimitStates()
  1566. assert.Equal(t, int32(5425), rateLimitStates[2].AlertLevel)
  1567. assert.Equal(t, int32(5475), rateLimitStates[2].ClearLevel)
  1568. assert.Equal(t, int32(4925), rateLimitStates[2].LimitLevel)
  1569. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1570. rateLimitStates = instance.RateLimitStates()
  1571. assert.Equal(t, int32(5340), rateLimitStates[2].AlertLevel)
  1572. assert.Equal(t, int32(5400), rateLimitStates[2].ClearLevel)
  1573. assert.Equal(t, int32(4740), rateLimitStates[2].LimitLevel)
  1574. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1575. rateLimitStates = instance.RateLimitStates()
  1576. assert.Equal(t, int32(5255), rateLimitStates[2].AlertLevel)
  1577. assert.Equal(t, int32(5325), rateLimitStates[2].ClearLevel)
  1578. assert.Equal(t, int32(4555), rateLimitStates[2].LimitLevel)
  1579. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1580. rateLimitStates = instance.RateLimitStates()
  1581. assert.Equal(t, int32(5170), rateLimitStates[2].AlertLevel)
  1582. assert.Equal(t, int32(5250), rateLimitStates[2].ClearLevel)
  1583. assert.Equal(t, int32(4370), rateLimitStates[2].LimitLevel)
  1584. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1585. rateLimitStates = instance.RateLimitStates()
  1586. assert.Equal(t, int32(5085), rateLimitStates[2].AlertLevel)
  1587. assert.Equal(t, int32(5175), rateLimitStates[2].ClearLevel)
  1588. assert.Equal(t, int32(4185), rateLimitStates[2].LimitLevel)
  1589. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1590. rateLimitStates = instance.RateLimitStates()
  1591. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1592. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1593. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1594. instance.Session().ScaleWarningAndRateLimit(-100, 3)
  1595. rateLimitStates = instance.RateLimitStates()
  1596. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1597. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1598. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1599. cancel()
  1600. wg.Wait()
  1601. })
  1602. t.Run("increment 100%", func(t *testing.T) {
  1603. classParams := [5]wire.RateClass{
  1604. {},
  1605. {},
  1606. {
  1607. ID: 3,
  1608. WindowSize: 20,
  1609. ClearLevel: 5100,
  1610. AlertLevel: 5000,
  1611. LimitLevel: 4000,
  1612. DisconnectLevel: 3000,
  1613. MaxLevel: 6000,
  1614. },
  1615. {},
  1616. {},
  1617. }
  1618. rateClasses := wire.NewRateLimitClasses(classParams)
  1619. now := time.Now()
  1620. instance := NewSession().AddInstance()
  1621. instance.Session().SetRateClasses(now, rateClasses)
  1622. var wg sync.WaitGroup
  1623. wg.Add(1)
  1624. ctx, cancel := context.WithCancel(t.Context())
  1625. go func() {
  1626. defer wg.Done()
  1627. for {
  1628. select {
  1629. case <-ctx.Done():
  1630. return
  1631. case <-instance.WarningCh():
  1632. }
  1633. }
  1634. }()
  1635. rateLimitStates := instance.RateLimitStates()
  1636. assert.Equal(t, int32(5000), rateLimitStates[2].AlertLevel)
  1637. assert.Equal(t, int32(5100), rateLimitStates[2].ClearLevel)
  1638. assert.Equal(t, int32(4000), rateLimitStates[2].LimitLevel)
  1639. instance.Session().ScaleWarningAndRateLimit(1000, 3)
  1640. rateLimitStates = instance.RateLimitStates()
  1641. assert.Equal(t, int32(5850), rateLimitStates[2].AlertLevel)
  1642. assert.Equal(t, int32(5850), rateLimitStates[2].ClearLevel)
  1643. assert.Equal(t, int32(5850), rateLimitStates[2].LimitLevel)
  1644. cancel()
  1645. wg.Wait()
  1646. })
  1647. }
  1648. func TestSession_RunOnce(t *testing.T) {
  1649. t.Run("runs function on first call", func(t *testing.T) {
  1650. s := NewSession()
  1651. callCount := 0
  1652. err := s.RunOnce(func() error {
  1653. callCount++
  1654. return nil
  1655. })
  1656. assert.NoError(t, err)
  1657. assert.Equal(t, 1, callCount)
  1658. })
  1659. t.Run("does not run function on subsequent calls", func(t *testing.T) {
  1660. s := NewSession()
  1661. callCount := 0
  1662. // First call
  1663. err1 := s.RunOnce(func() error {
  1664. callCount++
  1665. return nil
  1666. })
  1667. // Second call
  1668. err2 := s.RunOnce(func() error {
  1669. callCount++
  1670. return nil
  1671. })
  1672. // Third call
  1673. err3 := s.RunOnce(func() error {
  1674. callCount++
  1675. return nil
  1676. })
  1677. assert.NoError(t, err1)
  1678. assert.NoError(t, err2)
  1679. assert.NoError(t, err3)
  1680. assert.Equal(t, 1, callCount, "function should only be called once")
  1681. })
  1682. t.Run("returns error from function", func(t *testing.T) {
  1683. s := NewSession()
  1684. expectedErr := assert.AnError
  1685. err := s.RunOnce(func() error {
  1686. return expectedErr
  1687. })
  1688. assert.Error(t, err)
  1689. assert.Equal(t, expectedErr, err)
  1690. })
  1691. }
  1692. func TestSession_CloseInstance(t *testing.T) {
  1693. s := NewSession()
  1694. sessionCloseCount := 0
  1695. s.OnSessionClose(func() {
  1696. sessionCloseCount++
  1697. })
  1698. instance1CloseCount := 0
  1699. instance2CloseCount := 0
  1700. instance3CloseCount := 0
  1701. instance1 := s.AddInstance()
  1702. instance2 := s.AddInstance()
  1703. instance3 := s.AddInstance()
  1704. instance1.OnClose(func() {
  1705. // ensure instance is removed from the session before calling this func
  1706. assert.Equal(t, 2, s.InstanceCount())
  1707. instance1CloseCount++
  1708. })
  1709. instance2.OnClose(func() {
  1710. assert.Equal(t, 1, s.InstanceCount())
  1711. instance2CloseCount++
  1712. })
  1713. instance3.OnClose(func() {
  1714. instance3CloseCount++
  1715. })
  1716. // Close instance1 (instances 2 and 3 remain)
  1717. instance1.CloseInstance()
  1718. instance2.CloseInstance()
  1719. instance3.CloseInstance()
  1720. assert.Equal(t, 1, instance1CloseCount, "instance1 onInstanceCloseFn should only be called once")
  1721. assert.Equal(t, 1, instance2CloseCount, "instance2 onInstanceCloseFn should only be called once")
  1722. assert.Equal(t, 0, instance3CloseCount, "instance3 onInstanceCloseFn should not be called because it's the last instance")
  1723. assert.Equal(t, 1, sessionCloseCount, "session onSessCloseFn should not be called")
  1724. }
  1725. func TestSession_CloseSession(t *testing.T) {
  1726. s := NewSession()
  1727. sessionCloseCount := 0
  1728. s.OnSessionClose(func() {
  1729. sessionCloseCount++
  1730. })
  1731. instance1CloseCount := 0
  1732. instance2CloseCount := 0
  1733. instance3CloseCount := 0
  1734. instance1 := s.AddInstance()
  1735. instance2 := s.AddInstance()
  1736. instance3 := s.AddInstance()
  1737. instance1.OnClose(func() {
  1738. instance1CloseCount++
  1739. })
  1740. instance2.OnClose(func() {
  1741. instance2CloseCount++
  1742. })
  1743. instance3.OnClose(func() {
  1744. instance3CloseCount++
  1745. })
  1746. s.CloseSession()
  1747. assert.Equal(t, 0, instance1CloseCount, "instance1 onInstanceCloseFn should not be called")
  1748. assert.Equal(t, 0, instance2CloseCount, "instance2 onInstanceCloseFn should not be called")
  1749. assert.Equal(t, 0, instance3CloseCount, "instance3 onInstanceCloseFn should not be called")
  1750. assert.Equal(t, 1, sessionCloseCount, "session onSessCloseFn should only be called once")
  1751. }
  1752. func TestSession_AwayMessage(t *testing.T) {
  1753. tests := []struct {
  1754. name string
  1755. setupSession func() *Session
  1756. expectedResult string
  1757. }{
  1758. {
  1759. name: "no instances - should return empty string",
  1760. setupSession: func() *Session {
  1761. return NewSession()
  1762. },
  1763. expectedResult: "",
  1764. },
  1765. {
  1766. name: "one instance not away - should return empty string",
  1767. setupSession: func() *Session {
  1768. sg := NewSession()
  1769. _ = sg.AddInstance()
  1770. // instance has no away message and is not set as away
  1771. return sg
  1772. },
  1773. expectedResult: "",
  1774. },
  1775. {
  1776. name: "one instance away via SetUserInfoFlag - should return away message",
  1777. setupSession: func() *Session {
  1778. sg := NewSession()
  1779. instance := sg.AddInstance()
  1780. instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1781. instance.SetAwayMessage("I'm away")
  1782. return sg
  1783. },
  1784. expectedResult: "I'm away",
  1785. },
  1786. {
  1787. name: "one instance away via SetUserStatusBitmask - should return away message",
  1788. setupSession: func() *Session {
  1789. sg := NewSession()
  1790. instance := sg.AddInstance()
  1791. instance.SetUserStatusBitmask(wire.OServiceUserStatusAway)
  1792. instance.SetAwayMessage("I'm away")
  1793. return sg
  1794. },
  1795. expectedResult: "I'm away",
  1796. },
  1797. {
  1798. name: "multiple instances - not all away - should return away message from away instance",
  1799. setupSession: func() *Session {
  1800. sg := NewSession()
  1801. instance1 := sg.AddInstance()
  1802. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1803. instance1.SetAwayMessage("I'm away")
  1804. _ = sg.AddInstance()
  1805. // instance2 has no away message and is not set as away
  1806. return sg
  1807. },
  1808. expectedResult: "I'm away",
  1809. },
  1810. {
  1811. name: "multiple instances - all away - should return latest away message",
  1812. setupSession: func() *Session {
  1813. sg := NewSession()
  1814. baseTime := time.Now()
  1815. callCount := 0
  1816. sg.nowFn = func() time.Time {
  1817. callCount++
  1818. return baseTime.Add(time.Duration(callCount) * time.Second)
  1819. }
  1820. instance1 := sg.AddInstance()
  1821. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1822. instance1.SetAwayMessage("First away message")
  1823. instance2 := sg.AddInstance()
  1824. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1825. instance2.SetAwayMessage("Second away message")
  1826. return sg
  1827. },
  1828. expectedResult: "Second away message",
  1829. },
  1830. {
  1831. name: "multiple instances - all away after multiple updates - should return latest away message",
  1832. setupSession: func() *Session {
  1833. sg := NewSession()
  1834. baseTime := time.Now()
  1835. callCount := 0
  1836. sg.nowFn = func() time.Time {
  1837. callCount++
  1838. return baseTime.Add(time.Duration(callCount) * time.Second)
  1839. }
  1840. instance1 := sg.AddInstance()
  1841. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1842. instance1.SetAwayMessage("First away message")
  1843. instance2 := sg.AddInstance()
  1844. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1845. instance2.SetAwayMessage("Second away message")
  1846. // Update instance1's away status again (this will update awayTime)
  1847. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1848. instance1.SetAwayMessage("Third away message")
  1849. return sg
  1850. },
  1851. expectedResult: "Third away message",
  1852. },
  1853. {
  1854. name: "multiple instances - different away methods - should return latest away message",
  1855. setupSession: func() *Session {
  1856. sg := NewSession()
  1857. baseTime := time.Now()
  1858. callCount := 0
  1859. sg.nowFn = func() time.Time {
  1860. callCount++
  1861. return baseTime.Add(time.Duration(callCount) * time.Second)
  1862. }
  1863. instance1 := sg.AddInstance()
  1864. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1865. instance1.SetAwayMessage("First away message")
  1866. instance2 := sg.AddInstance()
  1867. instance2.SetUserStatusBitmask(wire.OServiceUserStatusAway)
  1868. instance2.SetAwayMessage("Second away message")
  1869. return sg
  1870. },
  1871. expectedResult: "Second away message",
  1872. },
  1873. {
  1874. name: "instance sets away message then clears message - should return empty string",
  1875. setupSession: func() *Session {
  1876. sg := NewSession()
  1877. instance := sg.AddInstance()
  1878. instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1879. instance.SetAwayMessage("I'm away")
  1880. instance.SetAwayMessage("") // clear away message (but still away)
  1881. return sg
  1882. },
  1883. expectedResult: "",
  1884. },
  1885. {
  1886. name: "instance sets away message then clears away status - should return empty string",
  1887. setupSession: func() *Session {
  1888. sg := NewSession()
  1889. instance := sg.AddInstance()
  1890. instance.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1891. instance.SetAwayMessage("I'm away")
  1892. instance.ClearUserInfoFlag(wire.OServiceUserFlagUnavailable) // clear away status
  1893. return sg
  1894. },
  1895. expectedResult: "",
  1896. },
  1897. {
  1898. name: "multiple instances - one away with message, one away without message - should return message from most recent",
  1899. setupSession: func() *Session {
  1900. sg := NewSession()
  1901. baseTime := time.Now()
  1902. callCount := 0
  1903. sg.nowFn = func() time.Time {
  1904. callCount++
  1905. return baseTime.Add(time.Duration(callCount) * time.Second)
  1906. }
  1907. instance1 := sg.AddInstance()
  1908. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1909. instance1.SetAwayMessage("I'm away")
  1910. instance2 := sg.AddInstance()
  1911. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1912. // instance2 is away but has no message, and was set away after instance1
  1913. return sg
  1914. },
  1915. expectedResult: "", // instance2 has more recent awayTime but no message
  1916. },
  1917. {
  1918. name: "multiple instances - one away with message set later - should return that message",
  1919. setupSession: func() *Session {
  1920. sg := NewSession()
  1921. baseTime := time.Now()
  1922. callCount := 0
  1923. sg.nowFn = func() time.Time {
  1924. callCount++
  1925. return baseTime.Add(time.Duration(callCount) * time.Second)
  1926. }
  1927. instance1 := sg.AddInstance()
  1928. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1929. instance1.SetAwayMessage("I'm away")
  1930. instance2 := sg.AddInstance()
  1931. instance2.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1932. // instance2 is away but has no message
  1933. // Now update instance1's away status to make it more recent
  1934. instance1.SetUserInfoFlag(wire.OServiceUserFlagUnavailable)
  1935. return sg
  1936. },
  1937. expectedResult: "I'm away", // instance1 has more recent awayTime and has a message
  1938. },
  1939. }
  1940. for _, tt := range tests {
  1941. t.Run(tt.name, func(t *testing.T) {
  1942. sg := tt.setupSession()
  1943. result := sg.AwayMessage()
  1944. assert.Equal(t, tt.expectedResult, result)
  1945. })
  1946. }
  1947. }
  1948. func TestSession_Caps(t *testing.T) {
  1949. // Helper function to compare capability slices (order-independent)
  1950. capsEqual := func(a, b [][16]byte) bool {
  1951. if len(a) != len(b) {
  1952. return false
  1953. }
  1954. capMap := make(map[[16]byte]bool)
  1955. for _, cap := range a {
  1956. capMap[cap] = true
  1957. }
  1958. for _, cap := range b {
  1959. if !capMap[cap] {
  1960. return false
  1961. }
  1962. }
  1963. return true
  1964. }
  1965. tests := []struct {
  1966. name string
  1967. setupSession func() *Session
  1968. expectedCaps [][16]byte
  1969. expectedCount int
  1970. }{
  1971. {
  1972. name: "empty session with no instances - should return empty slice",
  1973. setupSession: func() *Session {
  1974. return NewSession()
  1975. },
  1976. expectedCaps: [][16]byte{},
  1977. expectedCount: 0,
  1978. },
  1979. {
  1980. name: "single instance with no capabilities - should return empty slice",
  1981. setupSession: func() *Session {
  1982. sg := NewSession()
  1983. _ = sg.AddInstance()
  1984. return sg
  1985. },
  1986. expectedCaps: [][16]byte{},
  1987. expectedCount: 0,
  1988. },
  1989. {
  1990. name: "single instance with one cap - should return that cap",
  1991. setupSession: func() *Session {
  1992. sg := NewSession()
  1993. instance := sg.AddInstance()
  1994. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  1995. instance.SetCaps([][16]byte{cap1})
  1996. return sg
  1997. },
  1998. expectedCaps: [][16]byte{
  1999. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2000. },
  2001. expectedCount: 1,
  2002. },
  2003. {
  2004. name: "single instance with multiple capabilities - should return all capabilities",
  2005. setupSession: func() *Session {
  2006. sg := NewSession()
  2007. instance := sg.AddInstance()
  2008. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2009. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2010. cap3 := [16]byte{0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02}
  2011. instance.SetCaps([][16]byte{cap1, cap2, cap3})
  2012. return sg
  2013. },
  2014. expectedCaps: [][16]byte{
  2015. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2016. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2017. {0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02},
  2018. },
  2019. expectedCount: 3,
  2020. },
  2021. {
  2022. name: "multiple instances with no overlapping capabilities - should return union of all capabilities",
  2023. setupSession: func() *Session {
  2024. sg := NewSession()
  2025. instance1 := sg.AddInstance()
  2026. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2027. instance1.SetCaps([][16]byte{cap1})
  2028. instance2 := sg.AddInstance()
  2029. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2030. instance2.SetCaps([][16]byte{cap2})
  2031. instance3 := sg.AddInstance()
  2032. cap3 := [16]byte{0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02}
  2033. instance3.SetCaps([][16]byte{cap3})
  2034. return sg
  2035. },
  2036. expectedCaps: [][16]byte{
  2037. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2038. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2039. {0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02},
  2040. },
  2041. expectedCount: 3,
  2042. },
  2043. {
  2044. name: "multiple instances with overlapping capabilities - should deduplicate",
  2045. setupSession: func() *Session {
  2046. sg := NewSession()
  2047. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2048. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2049. cap3 := [16]byte{0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02}
  2050. instance1 := sg.AddInstance()
  2051. instance1.SetCaps([][16]byte{cap1, cap2})
  2052. instance2 := sg.AddInstance()
  2053. instance2.SetCaps([][16]byte{cap2, cap3}) // cap2 overlaps
  2054. return sg
  2055. },
  2056. expectedCaps: [][16]byte{
  2057. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2058. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2059. {0x76, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x02},
  2060. },
  2061. expectedCount: 3,
  2062. },
  2063. {
  2064. name: "multiple instances with all same capabilities - should return unique capabilities",
  2065. setupSession: func() *Session {
  2066. sg := NewSession()
  2067. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2068. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2069. instance1 := sg.AddInstance()
  2070. instance1.SetCaps([][16]byte{cap1, cap2})
  2071. instance2 := sg.AddInstance()
  2072. instance2.SetCaps([][16]byte{cap1, cap2}) // same caps
  2073. instance3 := sg.AddInstance()
  2074. instance3.SetCaps([][16]byte{cap1, cap2}) // same caps
  2075. return sg
  2076. },
  2077. expectedCaps: [][16]byte{
  2078. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2079. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2080. },
  2081. expectedCount: 2,
  2082. },
  2083. {
  2084. name: "multiple instances with some having capabilities and some not - should return union",
  2085. setupSession: func() *Session {
  2086. sg := NewSession()
  2087. _ = sg.AddInstance()
  2088. // instance1 has no caps
  2089. instance2 := sg.AddInstance()
  2090. cap1 := [16]byte{0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}
  2091. instance2.SetCaps([][16]byte{cap1})
  2092. _ = sg.AddInstance()
  2093. // instance3 has no caps
  2094. instance4 := sg.AddInstance()
  2095. cap2 := [16]byte{0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01}
  2096. instance4.SetCaps([][16]byte{cap2})
  2097. return sg
  2098. },
  2099. expectedCaps: [][16]byte{
  2100. {0x74, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00},
  2101. {0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01},
  2102. },
  2103. expectedCount: 2,
  2104. },
  2105. }
  2106. for _, tt := range tests {
  2107. t.Run(tt.name, func(t *testing.T) {
  2108. sg := tt.setupSession()
  2109. result := sg.Caps()
  2110. assert.Equal(t, tt.expectedCount, len(result), "cap count should match")
  2111. assert.True(t, capsEqual(tt.expectedCaps, result), "capabilities should match (order-independent)")
  2112. })
  2113. }
  2114. }
  2115. func TestSession_InstanceNumberAssignment(t *testing.T) {
  2116. t.Run("first instance gets number 1", func(t *testing.T) {
  2117. s := NewSession()
  2118. instance := s.AddInstance()
  2119. assert.Equal(t, uint8(1), instance.Num())
  2120. })
  2121. t.Run("multiple instances get sequential numbers", func(t *testing.T) {
  2122. s := NewSession()
  2123. instance1 := s.AddInstance()
  2124. instance2 := s.AddInstance()
  2125. instance3 := s.AddInstance()
  2126. assert.Equal(t, uint8(1), instance1.Num())
  2127. assert.Equal(t, uint8(2), instance2.Num())
  2128. assert.Equal(t, uint8(3), instance3.Num())
  2129. })
  2130. t.Run("removed instance numbers are reused", func(t *testing.T) {
  2131. s := NewSession()
  2132. instance1 := s.AddInstance()
  2133. instance2 := s.AddInstance()
  2134. instance3 := s.AddInstance()
  2135. assert.Equal(t, uint8(1), instance1.Num())
  2136. assert.Equal(t, uint8(2), instance2.Num())
  2137. assert.Equal(t, uint8(3), instance3.Num())
  2138. // Remove instance 2
  2139. s.RemoveInstance(instance2)
  2140. // New instance should reuse number 2
  2141. instance4 := s.AddInstance()
  2142. assert.Equal(t, uint8(2), instance4.Num())
  2143. // Verify all instance numbers are unique
  2144. instances := s.Instances()
  2145. instanceNums := make(map[uint8]bool)
  2146. for _, inst := range instances {
  2147. assert.False(t, instanceNums[inst.Num()], "instance number %d should be unique", inst.Num())
  2148. instanceNums[inst.Num()] = true
  2149. }
  2150. })
  2151. t.Run("finds lowest available number", func(t *testing.T) {
  2152. s := NewSession()
  2153. // Create instances 1, 2, 3
  2154. instance1 := s.AddInstance()
  2155. instance2 := s.AddInstance()
  2156. instance3 := s.AddInstance()
  2157. assert.Equal(t, uint8(1), instance1.Num())
  2158. assert.Equal(t, uint8(2), instance2.Num())
  2159. assert.Equal(t, uint8(3), instance3.Num())
  2160. // Remove instance 1
  2161. s.RemoveInstance(instance1)
  2162. // New instance should get number 1 (lowest available)
  2163. instance4 := s.AddInstance()
  2164. assert.Equal(t, uint8(1), instance4.Num())
  2165. // Remove instance 2
  2166. s.RemoveInstance(instance2)
  2167. // New instance should get number 2 (lowest available)
  2168. instance5 := s.AddInstance()
  2169. assert.Equal(t, uint8(2), instance5.Num())
  2170. // Verify instance 3 still has its number
  2171. assert.Equal(t, uint8(3), instance3.Num())
  2172. })
  2173. t.Run("panics when all instance numbers are taken", func(t *testing.T) {
  2174. s := NewSession()
  2175. // Fill up all 255 instance numbers
  2176. instances := make([]*SessionInstance, 255)
  2177. for i := 0; i < 255; i++ {
  2178. instances[i] = s.AddInstance()
  2179. }
  2180. // Verify we have 255 instances
  2181. assert.Equal(t, 255, s.InstanceCount())
  2182. // Try to create one more - should panic
  2183. assert.PanicsWithValue(t, "all instance numbers are taken (max 255 instances per session)", func() {
  2184. s.AddInstance()
  2185. })
  2186. })
  2187. }