session_test.go 65 KB

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