cmd_client_test.go 214 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278
  1. package toc
  2. import (
  3. "context"
  4. "encoding/hex"
  5. "errors"
  6. "io"
  7. "log/slog"
  8. "testing"
  9. "time"
  10. "github.com/google/uuid"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/mock"
  13. "golang.org/x/sync/errgroup"
  14. "github.com/mk6i/open-oscar-server/config"
  15. "github.com/mk6i/open-oscar-server/state"
  16. "github.com/mk6i/open-oscar-server/wire"
  17. )
  18. // nopOServiceService satisfies OServiceService without running the rate limit
  19. // monitor, for signon tests that only need Signon's RunOnce to not panic.
  20. type nopOServiceService struct{}
  21. func (nopOServiceService) ClientOnline(context.Context, uint16, wire.SNAC_0x01_0x02_OServiceClientOnline, *state.SessionInstance) error {
  22. return nil
  23. }
  24. func (nopOServiceService) IdleNotification(context.Context, *state.SessionInstance, wire.SNAC_0x01_0x11_OServiceIdleNotification) error {
  25. return nil
  26. }
  27. func (nopOServiceService) MonitorRateLimits(context.Context, *state.Session) {}
  28. func (nopOServiceService) ServiceRequest(context.Context, uint16, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x01_0x04_OServiceServiceRequest, config.ListenerGroup) (wire.SNACMessage, error) {
  29. return wire.SNACMessage{}, nil
  30. }
  31. func TestOSCARProxy_RecvClientCmd_AddBuddy(t *testing.T) {
  32. cases := []struct {
  33. // name is the unit test name
  34. name string
  35. // me is the TOC user session
  36. me *state.SessionInstance
  37. // givenCmd is the TOC command
  38. givenCmd []byte
  39. // wantMsg is the expected TOC response
  40. wantMsg []string
  41. // mockParams is the list of params sent to mocks that satisfy this
  42. // method's dependencies
  43. mockParams mockParams
  44. }{
  45. {
  46. name: "successfully add buddies",
  47. me: newTestSession("me"),
  48. givenCmd: []byte("toc_add_buddy friend1 friend2 friend3"),
  49. mockParams: mockParams{
  50. buddyParams: buddyParams{
  51. addBuddiesParams: addBuddiesParams{
  52. {
  53. me: state.NewIdentScreenName("me"),
  54. inBody: wire.SNAC_0x03_0x04_BuddyAddBuddies{
  55. Buddies: []struct {
  56. ScreenName string `oscar:"len_prefix=uint8"`
  57. }{
  58. {ScreenName: "friend1"},
  59. {ScreenName: "friend2"},
  60. {ScreenName: "friend3"},
  61. },
  62. },
  63. },
  64. },
  65. },
  66. },
  67. wantMsg: []string{},
  68. },
  69. {
  70. name: "add buddies with empty list",
  71. me: newTestSession("me"),
  72. givenCmd: []byte("toc_add_buddy"),
  73. mockParams: mockParams{
  74. buddyParams: buddyParams{
  75. addBuddiesParams: addBuddiesParams{
  76. {
  77. me: state.NewIdentScreenName("me"),
  78. inBody: wire.SNAC_0x03_0x04_BuddyAddBuddies{},
  79. },
  80. },
  81. },
  82. },
  83. wantMsg: []string{},
  84. },
  85. {
  86. name: "add buddies, receive error from buddy service",
  87. me: newTestSession("me"),
  88. givenCmd: []byte("toc_add_buddy friend1"),
  89. mockParams: mockParams{
  90. buddyParams: buddyParams{
  91. addBuddiesParams: addBuddiesParams{
  92. {
  93. me: state.NewIdentScreenName("me"),
  94. inBody: wire.SNAC_0x03_0x04_BuddyAddBuddies{
  95. Buddies: []struct {
  96. ScreenName string `oscar:"len_prefix=uint8"`
  97. }{
  98. {ScreenName: "friend1"},
  99. },
  100. },
  101. err: io.EOF,
  102. },
  103. },
  104. },
  105. },
  106. wantMsg: []string{cmdInternalSvcErr},
  107. },
  108. }
  109. for _, tc := range cases {
  110. t.Run(tc.name, func(t *testing.T) {
  111. ctx := context.Background()
  112. buddySvc := newMockBuddyService(t)
  113. for _, params := range tc.mockParams.addBuddiesParams {
  114. buddySvc.EXPECT().
  115. AddBuddies(ctx, matchSession(params.me), mock.Anything, params.inBody).
  116. Return(nil, params.err)
  117. }
  118. svc := OSCARProxy{
  119. Logger: slog.Default(),
  120. BuddyService: buddySvc,
  121. }
  122. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  123. assert.Equal(t, tc.wantMsg, msg)
  124. })
  125. }
  126. }
  127. func TestOSCARProxy_RecvClientCmd_AddPermit(t *testing.T) {
  128. cases := []struct {
  129. // name is the unit test name
  130. name string
  131. // me is the TOC user session
  132. me *state.SessionInstance
  133. // givenCmd is the TOC command
  134. givenCmd []byte
  135. // wantMsg is the expected TOC response
  136. wantMsg []string
  137. // mockParams is the list of params sent to mocks that satisfy this
  138. // method's dependencies
  139. mockParams mockParams
  140. }{
  141. {
  142. name: "successfully permit buddies",
  143. me: newTestSession("me"),
  144. givenCmd: []byte("toc_add_permit friend1 friend2 friend3"),
  145. mockParams: mockParams{
  146. permitDenyParams: permitDenyParams{
  147. addPermListEntriesParams: addPermListEntriesParams{
  148. {
  149. me: state.NewIdentScreenName("me"),
  150. body: wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries{
  151. Users: []struct {
  152. ScreenName string `oscar:"len_prefix=uint8"`
  153. }{
  154. {ScreenName: "friend1"},
  155. {ScreenName: "friend2"},
  156. {ScreenName: "friend3"},
  157. },
  158. },
  159. },
  160. },
  161. },
  162. },
  163. wantMsg: []string{},
  164. },
  165. {
  166. name: "permit buddies, receive error from buddy service",
  167. me: newTestSession("me"),
  168. givenCmd: []byte("toc_add_permit friend1"),
  169. mockParams: mockParams{
  170. permitDenyParams: permitDenyParams{
  171. addPermListEntriesParams: addPermListEntriesParams{
  172. {
  173. me: state.NewIdentScreenName("me"),
  174. body: wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries{
  175. Users: []struct {
  176. ScreenName string `oscar:"len_prefix=uint8"`
  177. }{
  178. {ScreenName: "friend1"},
  179. },
  180. },
  181. err: io.EOF,
  182. },
  183. },
  184. },
  185. },
  186. wantMsg: []string{cmdInternalSvcErr},
  187. },
  188. {
  189. name: "permit buddies with empty list",
  190. me: newTestSession("me"),
  191. givenCmd: []byte("toc_add_permit"),
  192. mockParams: mockParams{
  193. permitDenyParams: permitDenyParams{
  194. addPermListEntriesParams: addPermListEntriesParams{
  195. {
  196. me: state.NewIdentScreenName("me"),
  197. body: wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries{},
  198. },
  199. },
  200. },
  201. },
  202. wantMsg: []string{},
  203. },
  204. }
  205. for _, tc := range cases {
  206. t.Run(tc.name, func(t *testing.T) {
  207. ctx := context.Background()
  208. pdSvc := newMockPermitDenyService(t)
  209. for _, params := range tc.mockParams.addPermListEntriesParams {
  210. pdSvc.EXPECT().
  211. AddPermListEntries(ctx, matchSession(params.me), params.body).
  212. Return(params.err)
  213. }
  214. svc := OSCARProxy{
  215. Logger: slog.Default(),
  216. PermitDenyService: pdSvc,
  217. }
  218. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  219. assert.Equal(t, tc.wantMsg, msg)
  220. })
  221. }
  222. }
  223. func TestOSCARProxy_RecvClientCmd_AddDeny(t *testing.T) {
  224. cases := []struct {
  225. // name is the unit test name
  226. name string
  227. // me is the TOC user session
  228. me *state.SessionInstance
  229. // givenCmd is the TOC command
  230. givenCmd []byte
  231. // wantMsg is the expected TOC response
  232. wantMsg []string
  233. // mockParams is the list of params sent to mocks that satisfy this
  234. // method's dependencies
  235. mockParams mockParams
  236. }{
  237. {
  238. name: "successfully deny buddies",
  239. me: newTestSession("me"),
  240. givenCmd: []byte("toc_add_deny friend1 friend2 friend3"),
  241. mockParams: mockParams{
  242. permitDenyParams: permitDenyParams{
  243. addDenyListEntriesParams: addDenyListEntriesParams{
  244. {
  245. me: state.NewIdentScreenName("me"),
  246. body: wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries{
  247. Users: []struct {
  248. ScreenName string `oscar:"len_prefix=uint8"`
  249. }{
  250. {ScreenName: "friend1"},
  251. {ScreenName: "friend2"},
  252. {ScreenName: "friend3"},
  253. },
  254. },
  255. },
  256. },
  257. },
  258. },
  259. wantMsg: []string{},
  260. },
  261. {
  262. name: "deny buddies, receive error from buddy service",
  263. me: newTestSession("me"),
  264. givenCmd: []byte("toc_add_deny friend1"),
  265. mockParams: mockParams{
  266. permitDenyParams: permitDenyParams{
  267. addDenyListEntriesParams: addDenyListEntriesParams{
  268. {
  269. me: state.NewIdentScreenName("me"),
  270. body: wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries{
  271. Users: []struct {
  272. ScreenName string `oscar:"len_prefix=uint8"`
  273. }{
  274. {ScreenName: "friend1"},
  275. },
  276. },
  277. err: io.EOF,
  278. },
  279. },
  280. },
  281. },
  282. wantMsg: []string{cmdInternalSvcErr},
  283. },
  284. {
  285. name: "deny buddies with empty list",
  286. me: newTestSession("me"),
  287. givenCmd: []byte("toc_add_deny"),
  288. mockParams: mockParams{
  289. permitDenyParams: permitDenyParams{
  290. addDenyListEntriesParams: addDenyListEntriesParams{
  291. {
  292. me: state.NewIdentScreenName("me"),
  293. body: wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries{},
  294. },
  295. },
  296. },
  297. },
  298. wantMsg: []string{},
  299. },
  300. }
  301. for _, tc := range cases {
  302. t.Run(tc.name, func(t *testing.T) {
  303. ctx := context.Background()
  304. pdSvc := newMockPermitDenyService(t)
  305. for _, params := range tc.mockParams.addDenyListEntriesParams {
  306. pdSvc.EXPECT().
  307. AddDenyListEntries(ctx, matchSession(params.me), params.body).
  308. Return(params.err)
  309. }
  310. svc := OSCARProxy{
  311. Logger: slog.Default(),
  312. PermitDenyService: pdSvc,
  313. }
  314. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  315. assert.Equal(t, tc.wantMsg, msg)
  316. })
  317. }
  318. }
  319. func TestOSCARProxy_RecvClientCmd_FormatNickname(t *testing.T) {
  320. cases := []struct {
  321. // name is the unit test name
  322. name string
  323. // me is the TOC user session
  324. me *state.SessionInstance
  325. // givenCmd is the TOC command
  326. givenCmd []byte
  327. // wantMsg is the expected TOC response
  328. wantMsg []string
  329. // mockParams is the list of params sent to mocks that satisfy this
  330. // method's dependencies
  331. mockParams mockParams
  332. }{
  333. {
  334. name: "successfully change screen name format",
  335. me: newTestSession("myScreenName"),
  336. givenCmd: []byte("toc_format_nickname mYsCrEeNnAmE"),
  337. mockParams: mockParams{
  338. adminParams: adminParams{
  339. infoChangeRequestParams: infoChangeRequestParams{
  340. {
  341. me: state.NewIdentScreenName("myScreenName"),
  342. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  343. TLVRestBlock: wire.TLVRestBlock{
  344. TLVList: wire.TLVList{
  345. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "mYsCrEeNnAmE"),
  346. },
  347. },
  348. },
  349. msg: wire.SNACMessage{
  350. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  351. Permissions: wire.AdminInfoPermissionsReadWrite,
  352. TLVBlock: wire.TLVBlock{
  353. TLVList: wire.TLVList{
  354. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "mYsCrEeNnAmE"),
  355. },
  356. },
  357. },
  358. },
  359. },
  360. },
  361. },
  362. },
  363. wantMsg: []string{"ADMIN_NICK_STATUS:0", "NICK:mYsCrEeNnAmE"},
  364. },
  365. {
  366. name: "format nickname - invalid length",
  367. me: newTestSession("sn"),
  368. givenCmd: []byte("toc_format_nickname sN"),
  369. mockParams: mockParams{
  370. adminParams: adminParams{
  371. infoChangeRequestParams: infoChangeRequestParams{
  372. {
  373. me: state.NewIdentScreenName("sn"),
  374. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  375. TLVRestBlock: wire.TLVRestBlock{
  376. TLVList: wire.TLVList{
  377. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "sN"),
  378. },
  379. },
  380. },
  381. msg: wire.SNACMessage{
  382. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  383. Permissions: wire.AdminInfoPermissionsReadWrite,
  384. TLVBlock: wire.TLVBlock{
  385. TLVList: wire.TLVList{
  386. wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorInvalidNickNameLength),
  387. wire.NewTLVBE(wire.AdminTLVUrl, ""),
  388. },
  389. },
  390. },
  391. },
  392. },
  393. },
  394. },
  395. },
  396. wantMsg: []string{"ERROR:911"},
  397. },
  398. {
  399. name: "format nickname - invalid screen name",
  400. me: newTestSession("sn"),
  401. givenCmd: []byte("toc_format_nickname sN"),
  402. mockParams: mockParams{
  403. adminParams: adminParams{
  404. infoChangeRequestParams: infoChangeRequestParams{
  405. {
  406. me: state.NewIdentScreenName("sn"),
  407. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  408. TLVRestBlock: wire.TLVRestBlock{
  409. TLVList: wire.TLVList{
  410. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "sN"),
  411. },
  412. },
  413. },
  414. msg: wire.SNACMessage{
  415. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  416. Permissions: wire.AdminInfoPermissionsReadWrite,
  417. TLVBlock: wire.TLVBlock{
  418. TLVList: wire.TLVList{
  419. wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorInvalidNickName),
  420. wire.NewTLVBE(wire.AdminTLVUrl, ""),
  421. },
  422. },
  423. },
  424. },
  425. },
  426. },
  427. },
  428. },
  429. wantMsg: []string{"ERROR:911"},
  430. },
  431. {
  432. name: "format nickname - catch-all error",
  433. me: newTestSession("sn"),
  434. givenCmd: []byte("toc_format_nickname sN"),
  435. mockParams: mockParams{
  436. adminParams: adminParams{
  437. infoChangeRequestParams: infoChangeRequestParams{
  438. {
  439. me: state.NewIdentScreenName("sn"),
  440. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  441. TLVRestBlock: wire.TLVRestBlock{
  442. TLVList: wire.TLVList{
  443. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "sN"),
  444. },
  445. },
  446. },
  447. msg: wire.SNACMessage{
  448. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  449. Permissions: wire.AdminInfoPermissionsReadWrite,
  450. TLVBlock: wire.TLVBlock{
  451. TLVList: wire.TLVList{
  452. wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorValidateNickName),
  453. wire.NewTLVBE(wire.AdminTLVUrl, ""),
  454. },
  455. },
  456. },
  457. },
  458. },
  459. },
  460. },
  461. },
  462. wantMsg: []string{"ERROR:913"},
  463. },
  464. {
  465. name: "format nickname - runtime error from admin svc",
  466. me: newTestSession("myScreenName"),
  467. givenCmd: []byte("toc_format_nickname mYsCrEeNnAmE"),
  468. mockParams: mockParams{
  469. adminParams: adminParams{
  470. infoChangeRequestParams: infoChangeRequestParams{
  471. {
  472. me: state.NewIdentScreenName("myScreenName"),
  473. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  474. TLVRestBlock: wire.TLVRestBlock{
  475. TLVList: wire.TLVList{
  476. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "mYsCrEeNnAmE"),
  477. },
  478. },
  479. },
  480. err: io.EOF,
  481. },
  482. },
  483. },
  484. },
  485. wantMsg: []string{cmdInternalSvcErr},
  486. },
  487. {
  488. name: "change password - unexpected response from admin svc",
  489. me: newTestSession("myScreenName"),
  490. givenCmd: []byte("toc_format_nickname mYsCrEeNnAmE"),
  491. mockParams: mockParams{
  492. adminParams: adminParams{
  493. infoChangeRequestParams: infoChangeRequestParams{
  494. {
  495. me: state.NewIdentScreenName("myScreenName"),
  496. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  497. TLVRestBlock: wire.TLVRestBlock{
  498. TLVList: wire.TLVList{
  499. wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "mYsCrEeNnAmE"),
  500. },
  501. },
  502. },
  503. msg: wire.SNACMessage{
  504. Body: wire.SNACError{},
  505. },
  506. },
  507. },
  508. },
  509. },
  510. wantMsg: []string{cmdInternalSvcErr},
  511. },
  512. {
  513. name: "bad command",
  514. me: newTestSession("me"),
  515. givenCmd: []byte(`toc_format_nickname`),
  516. wantMsg: []string{cmdInternalSvcErr},
  517. },
  518. }
  519. for _, tc := range cases {
  520. t.Run(tc.name, func(t *testing.T) {
  521. ctx := context.Background()
  522. adminSvc := newMockAdminService(t)
  523. for _, params := range tc.mockParams.infoChangeRequestParams {
  524. adminSvc.EXPECT().
  525. InfoChangeRequest(ctx, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  526. Return(params.msg, params.err)
  527. }
  528. svc := OSCARProxy{
  529. Logger: slog.Default(),
  530. AdminService: adminSvc,
  531. }
  532. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  533. assert.Equal(t, tc.wantMsg, msg)
  534. })
  535. }
  536. }
  537. func TestOSCARProxy_RecvClientCmd_ChatAccept(t *testing.T) {
  538. navInfo := wire.SNACMessage{
  539. Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
  540. TLVRestBlock: wire.TLVRestBlock{
  541. TLVList: wire.TLVList{
  542. wire.NewTLVBE(wire.ChatNavTLVRoomInfo, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
  543. Cookie: "the-cookie",
  544. TLVBlock: wire.TLVBlock{
  545. TLVList: wire.TLVList{
  546. wire.NewTLVBE(wire.ChatRoomTLVRoomName, "cool room"),
  547. },
  548. },
  549. }),
  550. },
  551. },
  552. },
  553. }
  554. svcReq := wire.SNAC_0x01_0x04_OServiceServiceRequest{
  555. FoodGroup: wire.Chat,
  556. TLVRestBlock: wire.TLVRestBlock{
  557. TLVList: wire.TLVList{
  558. wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
  559. Cookie: "the-cookie",
  560. }),
  561. },
  562. },
  563. }
  564. svcResp := wire.SNACMessage{
  565. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  566. TLVRestBlock: wire.TLVRestBlock{
  567. TLVList: wire.TLVList{
  568. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, "chat-auth-cookie"),
  569. },
  570. },
  571. },
  572. }
  573. cases := []struct {
  574. // name is the unit test name
  575. name string
  576. // me is the TOC user session
  577. me *state.SessionInstance
  578. // givenCmd is the TOC command
  579. givenCmd []byte
  580. // givenChatRegistry is the chat registry passed to the function
  581. givenChatRegistry *ChatRegistry
  582. // wantMsg is the expected TOC response
  583. wantMsg []string
  584. // expectChatSession indicates whether a chat session should be present
  585. // in the chat registry
  586. expectChatSession bool
  587. // checkSession validates the state of the registered chat session (chat ID 0)
  588. checkSession func(*testing.T, *state.SessionInstance)
  589. // mockParams is the list of params sent to mocks that satisfy this
  590. // method's dependencies
  591. mockParams mockParams
  592. }{
  593. {
  594. name: "successfully accept chat",
  595. me: newTestSession("me"),
  596. givenCmd: []byte(`toc_chat_accept 0`),
  597. givenChatRegistry: func() *ChatRegistry {
  598. reg := NewChatRegistry()
  599. reg.Add(wire.ICBMRoomInfo{
  600. Cookie: "the-cookie",
  601. Exchange: 4,
  602. Instance: 0,
  603. })
  604. return reg
  605. }(),
  606. mockParams: mockParams{
  607. chatNavParams: chatNavParams{
  608. requestRoomInfoParams: requestRoomInfoParams{
  609. {
  610. inBody: wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
  611. Cookie: "the-cookie",
  612. Exchange: 4,
  613. InstanceNumber: 0,
  614. },
  615. msg: navInfo,
  616. },
  617. },
  618. },
  619. oServiceParams: oServiceParams{
  620. serviceRequestParams: serviceRequestParams{
  621. {
  622. me: state.NewIdentScreenName("me"),
  623. bodyIn: svcReq,
  624. msg: svcResp,
  625. },
  626. },
  627. clientOnlineParams: clientOnlineParams{
  628. {
  629. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  630. me: state.NewIdentScreenName("me"),
  631. },
  632. },
  633. },
  634. authParams: authParams{
  635. crackCookieParams: crackCookieParams{
  636. {
  637. cookieIn: []byte("chat-auth-cookie"),
  638. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  639. },
  640. },
  641. registerChatSessionParams: registerChatSessionParams{
  642. {
  643. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  644. instance: newTestSession("me"),
  645. },
  646. },
  647. },
  648. },
  649. wantMsg: []string{"CHAT_JOIN:0:cool room"},
  650. expectChatSession: true,
  651. },
  652. {
  653. name: "accept chat - TOC2 with encoded messaging propagates to chat session",
  654. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  655. givenCmd: []byte(`toc_chat_accept 0`),
  656. givenChatRegistry: func() *ChatRegistry {
  657. reg := NewChatRegistry()
  658. reg.Add(wire.ICBMRoomInfo{
  659. Cookie: "the-cookie",
  660. Exchange: 4,
  661. Instance: 0,
  662. })
  663. return reg
  664. }(),
  665. mockParams: mockParams{
  666. chatNavParams: chatNavParams{
  667. requestRoomInfoParams: requestRoomInfoParams{
  668. {
  669. inBody: wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
  670. Cookie: "the-cookie",
  671. Exchange: 4,
  672. InstanceNumber: 0,
  673. },
  674. msg: navInfo,
  675. },
  676. },
  677. },
  678. oServiceParams: oServiceParams{
  679. serviceRequestParams: serviceRequestParams{
  680. {
  681. me: state.NewIdentScreenName("me"),
  682. bodyIn: svcReq,
  683. msg: svcResp,
  684. },
  685. },
  686. clientOnlineParams: clientOnlineParams{
  687. {
  688. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  689. me: state.NewIdentScreenName("me"),
  690. },
  691. },
  692. },
  693. authParams: authParams{
  694. crackCookieParams: crackCookieParams{
  695. {
  696. cookieIn: []byte("chat-auth-cookie"),
  697. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  698. },
  699. },
  700. registerChatSessionParams: registerChatSessionParams{
  701. {
  702. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  703. instance: newTestSession("me"),
  704. },
  705. },
  706. },
  707. },
  708. wantMsg: []string{"CHAT_JOIN:0:cool room"},
  709. expectChatSession: true,
  710. checkSession: func(t *testing.T, sess *state.SessionInstance) {
  711. assert.NotNil(t, sess, "chat session should be registered")
  712. assert.True(t, sess.IsTOC2(), "chat session should have TOC2 set")
  713. assert.True(t, sess.SupportsTOC2MsgEnc(), "chat session should have TOC2 msg enc set")
  714. },
  715. },
  716. {
  717. name: "accept chat, receive error from client online",
  718. me: newTestSession("me"),
  719. givenCmd: []byte(`toc_chat_accept 0`),
  720. givenChatRegistry: func() *ChatRegistry {
  721. reg := NewChatRegistry()
  722. reg.Add(wire.ICBMRoomInfo{
  723. Cookie: "the-cookie",
  724. Exchange: 4,
  725. Instance: 0,
  726. })
  727. return reg
  728. }(),
  729. mockParams: mockParams{
  730. chatNavParams: chatNavParams{
  731. requestRoomInfoParams: requestRoomInfoParams{
  732. {
  733. inBody: wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
  734. Cookie: "the-cookie",
  735. Exchange: 4,
  736. InstanceNumber: 0,
  737. },
  738. msg: navInfo,
  739. },
  740. },
  741. },
  742. oServiceParams: oServiceParams{
  743. serviceRequestParams: serviceRequestParams{
  744. {
  745. me: state.NewIdentScreenName("me"),
  746. bodyIn: svcReq,
  747. msg: svcResp,
  748. },
  749. },
  750. clientOnlineParams: clientOnlineParams{
  751. {
  752. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  753. me: state.NewIdentScreenName("me"),
  754. err: io.EOF,
  755. },
  756. },
  757. },
  758. authParams: authParams{
  759. crackCookieParams: crackCookieParams{
  760. {
  761. cookieIn: []byte("chat-auth-cookie"),
  762. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  763. },
  764. },
  765. registerChatSessionParams: registerChatSessionParams{
  766. {
  767. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  768. instance: newTestSession("me"),
  769. },
  770. },
  771. },
  772. },
  773. wantMsg: []string{cmdInternalSvcErr},
  774. expectChatSession: false,
  775. },
  776. {
  777. name: "accept chat, receive error from register chat session",
  778. me: newTestSession("me"),
  779. givenCmd: []byte(`toc_chat_accept 0`),
  780. givenChatRegistry: func() *ChatRegistry {
  781. reg := NewChatRegistry()
  782. reg.Add(wire.ICBMRoomInfo{
  783. Cookie: "the-cookie",
  784. Exchange: 4,
  785. Instance: 0,
  786. })
  787. return reg
  788. }(),
  789. mockParams: mockParams{
  790. chatNavParams: chatNavParams{
  791. requestRoomInfoParams: requestRoomInfoParams{
  792. {
  793. inBody: wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
  794. Cookie: "the-cookie",
  795. Exchange: 4,
  796. InstanceNumber: 0,
  797. },
  798. msg: navInfo,
  799. },
  800. },
  801. },
  802. oServiceParams: oServiceParams{
  803. serviceRequestParams: serviceRequestParams{
  804. {
  805. me: state.NewIdentScreenName("me"),
  806. bodyIn: svcReq,
  807. msg: svcResp,
  808. },
  809. },
  810. },
  811. authParams: authParams{
  812. crackCookieParams: crackCookieParams{
  813. {
  814. cookieIn: []byte("chat-auth-cookie"),
  815. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  816. },
  817. },
  818. registerChatSessionParams: registerChatSessionParams{
  819. {
  820. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  821. instance: newTestSession("me"),
  822. err: io.EOF,
  823. },
  824. },
  825. },
  826. },
  827. wantMsg: []string{cmdInternalSvcErr},
  828. expectChatSession: false,
  829. },
  830. {
  831. name: "accept chat, receive error from BOS oservice svc",
  832. me: newTestSession("me"),
  833. givenCmd: []byte(`toc_chat_accept 0`),
  834. givenChatRegistry: func() *ChatRegistry {
  835. reg := NewChatRegistry()
  836. reg.Add(wire.ICBMRoomInfo{
  837. Cookie: "the-cookie",
  838. Exchange: 4,
  839. Instance: 0,
  840. })
  841. return reg
  842. }(),
  843. mockParams: mockParams{
  844. chatNavParams: chatNavParams{
  845. requestRoomInfoParams: requestRoomInfoParams{
  846. {
  847. inBody: wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
  848. Cookie: "the-cookie",
  849. Exchange: 4,
  850. InstanceNumber: 0,
  851. },
  852. msg: navInfo,
  853. },
  854. },
  855. },
  856. oServiceParams: oServiceParams{
  857. serviceRequestParams: serviceRequestParams{
  858. {
  859. me: state.NewIdentScreenName("me"),
  860. bodyIn: svcReq,
  861. err: io.EOF,
  862. },
  863. },
  864. },
  865. },
  866. wantMsg: []string{cmdInternalSvcErr},
  867. expectChatSession: false,
  868. },
  869. {
  870. name: "accept chat, receive error from chat nav svc",
  871. me: newTestSession("me"),
  872. givenCmd: []byte(`toc_chat_accept 0`),
  873. givenChatRegistry: func() *ChatRegistry {
  874. reg := NewChatRegistry()
  875. reg.Add(wire.ICBMRoomInfo{
  876. Cookie: "the-cookie",
  877. Exchange: 4,
  878. Instance: 0,
  879. })
  880. return reg
  881. }(),
  882. mockParams: mockParams{
  883. chatNavParams: chatNavParams{
  884. requestRoomInfoParams: requestRoomInfoParams{
  885. {
  886. inBody: wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
  887. Cookie: "the-cookie",
  888. Exchange: 4,
  889. InstanceNumber: 0,
  890. },
  891. err: io.EOF,
  892. },
  893. },
  894. },
  895. },
  896. wantMsg: []string{cmdInternalSvcErr},
  897. expectChatSession: false,
  898. },
  899. {
  900. name: "bad command",
  901. me: newTestSession("me"),
  902. givenCmd: []byte(`toc_chat_accept`),
  903. givenChatRegistry: NewChatRegistry(),
  904. wantMsg: []string{cmdInternalSvcErr},
  905. expectChatSession: false,
  906. },
  907. {
  908. name: "bad exchange number",
  909. me: newTestSession("me"),
  910. givenCmd: []byte(`toc_chat_accept four`),
  911. givenChatRegistry: NewChatRegistry(),
  912. wantMsg: []string{cmdInternalSvcErr},
  913. expectChatSession: false,
  914. },
  915. }
  916. for _, tc := range cases {
  917. t.Run(tc.name, func(t *testing.T) {
  918. ctx, cancel := context.WithCancel(context.Background())
  919. cancel()
  920. chatNavSvc := newMockChatNavService(t)
  921. for _, params := range tc.mockParams.requestRoomInfoParams {
  922. chatNavSvc.EXPECT().
  923. RequestRoomInfo(ctx, wire.SNACFrame{}, params.inBody).
  924. Return(params.msg, params.err)
  925. }
  926. oServiceSvc := newMockOServiceService(t)
  927. for _, params := range tc.mockParams.serviceRequestParams {
  928. oServiceSvc.EXPECT().
  929. ServiceRequest(ctx, wire.BOS, matchSession(params.me), wire.SNACFrame{}, params.bodyIn, config.ListenerGroup{}).
  930. Return(params.msg, params.err)
  931. }
  932. for _, params := range tc.mockParams.clientOnlineParams {
  933. oServiceSvc.EXPECT().
  934. ClientOnline(ctx, wire.Chat, params.body, matchSession(params.me)).
  935. Return(params.err)
  936. }
  937. authSvc := newMockAuthService(t)
  938. for _, params := range tc.mockParams.registerChatSessionParams {
  939. authSvc.EXPECT().
  940. RegisterChatSession(ctx, params.authCookie, mock.Anything).
  941. Return(params.instance, params.err)
  942. }
  943. for _, params := range tc.mockParams.crackCookieParams {
  944. authSvc.EXPECT().
  945. CrackCookie(params.cookieIn).
  946. Return(params.cookieOut, time.Now().Add(time.Minute), params.err)
  947. }
  948. svc := OSCARProxy{
  949. AuthService: authSvc,
  950. ChatNavService: chatNavSvc,
  951. Logger: slog.Default(),
  952. OServiceService: oServiceSvc,
  953. }
  954. g := &errgroup.Group{}
  955. tc.me.CloseInstance()
  956. msg := svc.RecvClientCmd(ctx, tc.me, tc.givenChatRegistry, tc.givenCmd, nil, g.Go)
  957. assert.NoError(t, g.Wait())
  958. assert.Equal(t, tc.wantMsg, msg)
  959. assert.Equal(t, tc.expectChatSession, len(tc.givenChatRegistry.Sessions()) == 1)
  960. if tc.checkSession != nil {
  961. tc.checkSession(t, tc.givenChatRegistry.RetrieveSess(0))
  962. }
  963. })
  964. }
  965. }
  966. func TestOSCARProxy_RecvClientCmd_ChatInvite(t *testing.T) {
  967. cases := []struct {
  968. // name is the unit test name
  969. name string
  970. // me is the TOC user session
  971. me *state.SessionInstance
  972. // givenCmd is the TOC command
  973. givenCmd []byte
  974. // givenChatRegistry is the chat registry passed to the function
  975. givenChatRegistry *ChatRegistry
  976. // wantMsg is the expected TOC response
  977. wantMsg []string
  978. // mockParams is the list of params sent to mocks that satisfy this
  979. // method's dependencies
  980. mockParams mockParams
  981. }{
  982. {
  983. name: "successfully send chat invitation",
  984. me: newTestSession("me"),
  985. givenCmd: []byte(`toc_chat_invite 0 "join my chat! :\)" friend1`),
  986. givenChatRegistry: func() *ChatRegistry {
  987. reg := NewChatRegistry()
  988. reg.Add(wire.ICBMRoomInfo{
  989. Exchange: 4,
  990. Cookie: "the-cookie",
  991. Instance: 0,
  992. })
  993. return reg
  994. }(),
  995. mockParams: mockParams{
  996. icbmParams: icbmParams{
  997. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  998. {
  999. sender: state.NewIdentScreenName("me"),
  1000. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  1001. ChannelID: wire.ICBMChannelRendezvous,
  1002. ScreenName: "friend1",
  1003. TLVRestBlock: wire.TLVRestBlock{
  1004. TLVList: wire.TLVList{
  1005. wire.NewTLVBE(0x05, wire.ICBMCh2Fragment{
  1006. Type: 0,
  1007. Capability: wire.CapChat,
  1008. TLVRestBlock: wire.TLVRestBlock{
  1009. TLVList: wire.TLVList{
  1010. wire.NewTLVBE(10, uint16(1)),
  1011. wire.NewTLVBE(12, "join my chat! :)"),
  1012. wire.NewTLVBE(13, "us-ascii"),
  1013. wire.NewTLVBE(14, "en"),
  1014. wire.NewTLVBE(10001, wire.ICBMRoomInfo{
  1015. Exchange: 4,
  1016. Cookie: "the-cookie",
  1017. Instance: 0,
  1018. }),
  1019. },
  1020. },
  1021. }),
  1022. },
  1023. },
  1024. },
  1025. },
  1026. },
  1027. },
  1028. },
  1029. wantMsg: []string{},
  1030. },
  1031. {
  1032. name: "send chat invitation, receive error from ICBM svc",
  1033. me: newTestSession("me"),
  1034. givenCmd: []byte(`toc_chat_invite 0 "join my chat!" friend1`),
  1035. givenChatRegistry: func() *ChatRegistry {
  1036. reg := NewChatRegistry()
  1037. reg.Add(wire.ICBMRoomInfo{
  1038. Exchange: 4,
  1039. Cookie: "the-cookie",
  1040. Instance: 0,
  1041. })
  1042. return reg
  1043. }(),
  1044. mockParams: mockParams{
  1045. icbmParams: icbmParams{
  1046. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  1047. {
  1048. sender: state.NewIdentScreenName("me"),
  1049. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  1050. ChannelID: wire.ICBMChannelRendezvous,
  1051. ScreenName: "friend1",
  1052. TLVRestBlock: wire.TLVRestBlock{
  1053. TLVList: wire.TLVList{
  1054. wire.NewTLVBE(0x05, wire.ICBMCh2Fragment{
  1055. Type: 0,
  1056. Capability: wire.CapChat,
  1057. TLVRestBlock: wire.TLVRestBlock{
  1058. TLVList: wire.TLVList{
  1059. wire.NewTLVBE(10, uint16(1)),
  1060. wire.NewTLVBE(12, "join my chat!"),
  1061. wire.NewTLVBE(13, "us-ascii"),
  1062. wire.NewTLVBE(14, "en"),
  1063. wire.NewTLVBE(10001, wire.ICBMRoomInfo{
  1064. Exchange: 4,
  1065. Cookie: "the-cookie",
  1066. Instance: 0,
  1067. }),
  1068. },
  1069. },
  1070. }),
  1071. },
  1072. },
  1073. },
  1074. err: io.EOF,
  1075. },
  1076. },
  1077. },
  1078. },
  1079. wantMsg: []string{cmdInternalSvcErr},
  1080. },
  1081. {
  1082. name: "send chat invitation to non-existent room",
  1083. me: newTestSession("me"),
  1084. givenCmd: []byte(`toc_chat_invite 0 "join my chat!" friend1`),
  1085. givenChatRegistry: NewChatRegistry(),
  1086. wantMsg: []string{cmdInternalSvcErr},
  1087. },
  1088. {
  1089. name: "bad chat room ID",
  1090. givenCmd: []byte(`toc_chat_invite zero "join my chat!" friend1`),
  1091. wantMsg: []string{cmdInternalSvcErr},
  1092. },
  1093. {
  1094. name: "bad command",
  1095. me: newTestSession("me"),
  1096. givenCmd: []byte(`toc_chat_invite`),
  1097. wantMsg: []string{cmdInternalSvcErr},
  1098. },
  1099. }
  1100. for _, tc := range cases {
  1101. t.Run(tc.name, func(t *testing.T) {
  1102. ctx := context.Background()
  1103. icbmSvc := newMockICBMService(t)
  1104. for _, params := range tc.mockParams.channelMsgToHostParamsICBM {
  1105. icbmSvc.EXPECT().
  1106. ChannelMsgToHost(ctx, matchSession(params.sender), wire.SNACFrame{}, params.inBody).
  1107. Return(nil, params.err)
  1108. }
  1109. svc := OSCARProxy{
  1110. Logger: slog.Default(),
  1111. ICBMService: icbmSvc,
  1112. }
  1113. msg := svc.RecvClientCmd(ctx, tc.me, tc.givenChatRegistry, tc.givenCmd, nil, nil)
  1114. assert.Equal(t, tc.wantMsg, msg)
  1115. })
  1116. }
  1117. }
  1118. func TestOSCARProxy_RecvClientCmd_ChatJoin(t *testing.T) {
  1119. roomInfo := wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
  1120. Exchange: 4,
  1121. Cookie: "create",
  1122. TLVBlock: wire.TLVBlock{
  1123. TLVList: wire.TLVList{
  1124. wire.NewTLVBE(wire.ChatRoomTLVRoomName, "cool room :)"),
  1125. },
  1126. },
  1127. }
  1128. navInfo := wire.SNACMessage{
  1129. Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
  1130. TLVRestBlock: wire.TLVRestBlock{
  1131. TLVList: wire.TLVList{
  1132. wire.NewTLVBE(wire.ChatNavTLVRoomInfo, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
  1133. Cookie: "the-cookie",
  1134. }),
  1135. },
  1136. },
  1137. },
  1138. }
  1139. svcReq := wire.SNAC_0x01_0x04_OServiceServiceRequest{
  1140. FoodGroup: wire.Chat,
  1141. TLVRestBlock: wire.TLVRestBlock{
  1142. TLVList: wire.TLVList{
  1143. wire.NewTLVBE(0x01, wire.SNAC_0x01_0x04_TLVRoomInfo{
  1144. Cookie: "the-cookie",
  1145. }),
  1146. },
  1147. },
  1148. }
  1149. svcResp := wire.SNACMessage{
  1150. Body: wire.SNAC_0x01_0x05_OServiceServiceResponse{
  1151. TLVRestBlock: wire.TLVRestBlock{
  1152. TLVList: wire.TLVList{
  1153. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, "chat-auth-cookie"),
  1154. },
  1155. },
  1156. },
  1157. }
  1158. cases := []struct {
  1159. // name is the unit test name
  1160. name string
  1161. // me is the TOC user session
  1162. me *state.SessionInstance
  1163. // givenCmd is the TOC command
  1164. givenCmd []byte
  1165. // givenChatRegistry is the chat registry passed to the function
  1166. givenChatRegistry *ChatRegistry
  1167. // wantMsg is the expected TOC response
  1168. wantMsg []string
  1169. // expectChatSession indicates whether a chat session should be present
  1170. // in the chat registry
  1171. expectChatSession bool
  1172. // checkSession validates the state of the registered chat session (chat ID 0)
  1173. checkSession func(*testing.T, *state.SessionInstance)
  1174. // mockParams is the list of params sent to mocks that satisfy this
  1175. // method's dependencies
  1176. mockParams mockParams
  1177. }{
  1178. {
  1179. name: "successfully join chat",
  1180. me: newTestSession("me"),
  1181. givenCmd: []byte(`toc_chat_join 4 "cool room :\)"`),
  1182. givenChatRegistry: NewChatRegistry(),
  1183. mockParams: mockParams{
  1184. chatNavParams: chatNavParams{
  1185. createRoomParams: createRoomParams{
  1186. {
  1187. me: state.NewIdentScreenName("me"),
  1188. inBody: roomInfo,
  1189. msg: navInfo,
  1190. },
  1191. },
  1192. },
  1193. oServiceParams: oServiceParams{
  1194. serviceRequestParams: serviceRequestParams{
  1195. {
  1196. me: state.NewIdentScreenName("me"),
  1197. bodyIn: svcReq,
  1198. msg: svcResp,
  1199. },
  1200. },
  1201. clientOnlineParams: clientOnlineParams{
  1202. {
  1203. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  1204. me: state.NewIdentScreenName("me"),
  1205. },
  1206. },
  1207. },
  1208. authParams: authParams{
  1209. crackCookieParams: crackCookieParams{
  1210. {
  1211. cookieIn: []byte("chat-auth-cookie"),
  1212. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1213. },
  1214. },
  1215. registerChatSessionParams: registerChatSessionParams{
  1216. {
  1217. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1218. instance: newTestSession("me"),
  1219. },
  1220. },
  1221. },
  1222. },
  1223. wantMsg: []string{"CHAT_JOIN:0:cool room :)"},
  1224. expectChatSession: true,
  1225. },
  1226. {
  1227. name: "successfully join chat - TOC2 with encoded messaging propagates to chat session",
  1228. me: newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }),
  1229. givenCmd: []byte(`toc_chat_join 4 "cool room :\)"`),
  1230. givenChatRegistry: NewChatRegistry(),
  1231. mockParams: mockParams{
  1232. chatNavParams: chatNavParams{
  1233. createRoomParams: createRoomParams{
  1234. {
  1235. me: state.NewIdentScreenName("me"),
  1236. inBody: roomInfo,
  1237. msg: navInfo,
  1238. },
  1239. },
  1240. },
  1241. oServiceParams: oServiceParams{
  1242. serviceRequestParams: serviceRequestParams{
  1243. {
  1244. me: state.NewIdentScreenName("me"),
  1245. bodyIn: svcReq,
  1246. msg: svcResp,
  1247. },
  1248. },
  1249. clientOnlineParams: clientOnlineParams{
  1250. {
  1251. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  1252. me: state.NewIdentScreenName("me"),
  1253. },
  1254. },
  1255. },
  1256. authParams: authParams{
  1257. crackCookieParams: crackCookieParams{
  1258. {
  1259. cookieIn: []byte("chat-auth-cookie"),
  1260. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1261. },
  1262. },
  1263. registerChatSessionParams: registerChatSessionParams{
  1264. {
  1265. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1266. instance: newTestSession("me"),
  1267. },
  1268. },
  1269. },
  1270. },
  1271. wantMsg: []string{"CHAT_JOIN:0:cool room :)"},
  1272. expectChatSession: true,
  1273. checkSession: func(t *testing.T, sess *state.SessionInstance) {
  1274. assert.NotNil(t, sess, "chat session should be registered")
  1275. assert.True(t, sess.IsTOC2(), "chat session should have TOC2 set")
  1276. assert.True(t, sess.SupportsTOC2MsgEnc(), "chat session should have TOC2 msg enc set")
  1277. },
  1278. },
  1279. {
  1280. name: "accept chat, receive error from client online",
  1281. me: newTestSession("me"),
  1282. givenCmd: []byte(`toc_chat_join 4 "cool room :\)"`),
  1283. givenChatRegistry: NewChatRegistry(),
  1284. mockParams: mockParams{
  1285. chatNavParams: chatNavParams{
  1286. createRoomParams: createRoomParams{
  1287. {
  1288. me: state.NewIdentScreenName("me"),
  1289. inBody: roomInfo,
  1290. msg: navInfo,
  1291. },
  1292. },
  1293. },
  1294. oServiceParams: oServiceParams{
  1295. serviceRequestParams: serviceRequestParams{
  1296. {
  1297. me: state.NewIdentScreenName("me"),
  1298. bodyIn: svcReq,
  1299. msg: svcResp,
  1300. },
  1301. },
  1302. clientOnlineParams: clientOnlineParams{
  1303. {
  1304. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  1305. me: state.NewIdentScreenName("me"),
  1306. err: io.EOF,
  1307. },
  1308. },
  1309. },
  1310. authParams: authParams{
  1311. crackCookieParams: crackCookieParams{
  1312. {
  1313. cookieIn: []byte("chat-auth-cookie"),
  1314. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1315. },
  1316. },
  1317. registerChatSessionParams: registerChatSessionParams{
  1318. {
  1319. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1320. instance: newTestSession("me"),
  1321. },
  1322. },
  1323. },
  1324. },
  1325. wantMsg: []string{cmdInternalSvcErr},
  1326. expectChatSession: false,
  1327. },
  1328. {
  1329. name: "join chat, receive error from register chat session",
  1330. me: newTestSession("me"),
  1331. givenCmd: []byte(`toc_chat_join 4 "cool room :\)"`),
  1332. givenChatRegistry: NewChatRegistry(),
  1333. mockParams: mockParams{
  1334. chatNavParams: chatNavParams{
  1335. createRoomParams: createRoomParams{
  1336. {
  1337. me: state.NewIdentScreenName("me"),
  1338. inBody: roomInfo,
  1339. msg: navInfo,
  1340. },
  1341. },
  1342. },
  1343. oServiceParams: oServiceParams{
  1344. serviceRequestParams: serviceRequestParams{
  1345. {
  1346. me: state.NewIdentScreenName("me"),
  1347. bodyIn: svcReq,
  1348. msg: svcResp,
  1349. },
  1350. },
  1351. },
  1352. authParams: authParams{
  1353. crackCookieParams: crackCookieParams{
  1354. {
  1355. cookieIn: []byte("chat-auth-cookie"),
  1356. cookieOut: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1357. },
  1358. },
  1359. registerChatSessionParams: registerChatSessionParams{
  1360. {
  1361. authCookie: state.ServerCookie{ChatCookie: "chat-auth-cookie"},
  1362. instance: newTestSession("me"),
  1363. err: io.EOF,
  1364. },
  1365. },
  1366. },
  1367. },
  1368. wantMsg: []string{cmdInternalSvcErr},
  1369. expectChatSession: false,
  1370. },
  1371. {
  1372. name: "join chat, receive error from service request",
  1373. me: newTestSession("me"),
  1374. givenCmd: []byte(`toc_chat_join 4 "cool room :\)"`),
  1375. givenChatRegistry: NewChatRegistry(),
  1376. mockParams: mockParams{
  1377. chatNavParams: chatNavParams{
  1378. createRoomParams: createRoomParams{
  1379. {
  1380. me: state.NewIdentScreenName("me"),
  1381. inBody: roomInfo,
  1382. msg: navInfo,
  1383. },
  1384. },
  1385. },
  1386. oServiceParams: oServiceParams{
  1387. serviceRequestParams: serviceRequestParams{
  1388. {
  1389. me: state.NewIdentScreenName("me"),
  1390. bodyIn: svcReq,
  1391. err: io.EOF,
  1392. },
  1393. },
  1394. },
  1395. },
  1396. wantMsg: []string{cmdInternalSvcErr},
  1397. expectChatSession: false,
  1398. },
  1399. {
  1400. name: "join chat, receive error from chat nav svc",
  1401. me: newTestSession("me"),
  1402. givenCmd: []byte(`toc_chat_join 4 "cool room :\)"`),
  1403. givenChatRegistry: NewChatRegistry(),
  1404. mockParams: mockParams{
  1405. chatNavParams: chatNavParams{
  1406. createRoomParams: createRoomParams{
  1407. {
  1408. me: state.NewIdentScreenName("me"),
  1409. inBody: roomInfo,
  1410. err: io.EOF,
  1411. },
  1412. },
  1413. },
  1414. },
  1415. wantMsg: []string{cmdInternalSvcErr},
  1416. expectChatSession: false,
  1417. },
  1418. {
  1419. name: "bad command",
  1420. me: newTestSession("me"),
  1421. givenCmd: []byte(`toc_chat_join`),
  1422. givenChatRegistry: NewChatRegistry(),
  1423. wantMsg: []string{cmdInternalSvcErr},
  1424. expectChatSession: false,
  1425. },
  1426. {
  1427. name: "bad exchange number",
  1428. me: newTestSession("me"),
  1429. givenCmd: []byte(`toc_chat_join four "cool room :\)"`),
  1430. givenChatRegistry: NewChatRegistry(),
  1431. wantMsg: []string{cmdInternalSvcErr},
  1432. expectChatSession: false,
  1433. },
  1434. }
  1435. for _, tc := range cases {
  1436. t.Run(tc.name, func(t *testing.T) {
  1437. ctx, cancel := context.WithCancel(context.Background())
  1438. cancel()
  1439. chatNavSvc := newMockChatNavService(t)
  1440. for _, params := range tc.mockParams.createRoomParams {
  1441. chatNavSvc.EXPECT().
  1442. CreateRoom(ctx, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  1443. Return(params.msg, params.err)
  1444. }
  1445. bosOServiceSvc := newMockOServiceService(t)
  1446. for _, params := range tc.mockParams.serviceRequestParams {
  1447. bosOServiceSvc.EXPECT().
  1448. ServiceRequest(ctx, wire.BOS, matchSession(params.me), wire.SNACFrame{}, params.bodyIn, config.ListenerGroup{}).
  1449. Return(params.msg, params.err)
  1450. }
  1451. for _, params := range tc.mockParams.clientOnlineParams {
  1452. bosOServiceSvc.EXPECT().
  1453. ClientOnline(ctx, wire.Chat, params.body, matchSession(params.me)).
  1454. Return(params.err)
  1455. }
  1456. authSvc := newMockAuthService(t)
  1457. for _, params := range tc.mockParams.registerChatSessionParams {
  1458. authSvc.EXPECT().
  1459. RegisterChatSession(ctx, params.authCookie, mock.Anything).
  1460. Return(params.instance, params.err)
  1461. }
  1462. for _, params := range tc.mockParams.crackCookieParams {
  1463. authSvc.EXPECT().
  1464. CrackCookie(params.cookieIn).
  1465. Return(params.cookieOut, time.Now().Add(time.Minute), params.err)
  1466. }
  1467. svc := OSCARProxy{
  1468. AuthService: authSvc,
  1469. ChatNavService: chatNavSvc,
  1470. Logger: slog.Default(),
  1471. OServiceService: bosOServiceSvc,
  1472. }
  1473. g := &errgroup.Group{}
  1474. tc.me.CloseInstance()
  1475. msg := svc.RecvClientCmd(ctx, tc.me, tc.givenChatRegistry, tc.givenCmd, nil, g.Go)
  1476. assert.NoError(t, g.Wait())
  1477. assert.Equal(t, tc.wantMsg, msg)
  1478. assert.Equal(t, tc.expectChatSession, len(tc.givenChatRegistry.Sessions()) == 1)
  1479. if tc.checkSession != nil {
  1480. tc.checkSession(t, tc.givenChatRegistry.RetrieveSess(0))
  1481. }
  1482. })
  1483. }
  1484. }
  1485. func TestOSCARProxy_RecvClientCmd_ChatLeave(t *testing.T) {
  1486. cases := []struct {
  1487. // name is the unit test name
  1488. name string
  1489. // me is the TOC user session
  1490. me *state.SessionInstance
  1491. // givenCmd is the TOC command
  1492. givenCmd []byte
  1493. // givenChatRegistry is the chat registry passed to the function
  1494. givenChatRegistry *ChatRegistry
  1495. // wantMsg is the expected TOC response
  1496. wantMsg []string
  1497. // mockParams is the list of params sent to mocks that satisfy this
  1498. // method's dependencies
  1499. mockParams mockParams
  1500. }{
  1501. {
  1502. name: "successfully leave chat",
  1503. me: newTestSession("me"),
  1504. givenCmd: []byte(`toc_chat_leave 0`),
  1505. givenChatRegistry: func() *ChatRegistry {
  1506. reg := NewChatRegistry()
  1507. reg.RegisterSess(0, newTestSession("me"))
  1508. return reg
  1509. }(),
  1510. wantMsg: []string{"CHAT_LEFT:0"},
  1511. },
  1512. {
  1513. name: "chat room ID with invalid format",
  1514. givenCmd: []byte(`toc_chat_leave zero`),
  1515. wantMsg: []string{cmdInternalSvcErr},
  1516. },
  1517. {
  1518. name: "missing chat session",
  1519. givenCmd: []byte(`toc_chat_leave 0`),
  1520. givenChatRegistry: NewChatRegistry(),
  1521. wantMsg: []string{cmdInternalSvcErr},
  1522. },
  1523. {
  1524. name: "bad command",
  1525. me: newTestSession("me"),
  1526. givenCmd: []byte(`toc_chat_leave`),
  1527. wantMsg: []string{cmdInternalSvcErr},
  1528. },
  1529. }
  1530. for _, tc := range cases {
  1531. t.Run(tc.name, func(t *testing.T) {
  1532. ctx := context.Background()
  1533. svc := OSCARProxy{
  1534. Logger: slog.Default(),
  1535. }
  1536. msg := svc.RecvClientCmd(ctx, nil, tc.givenChatRegistry, tc.givenCmd, nil, nil)
  1537. assert.Equal(t, tc.wantMsg, msg)
  1538. })
  1539. }
  1540. }
  1541. func TestOSCARProxy_RecvClientCmd_ChatSend(t *testing.T) {
  1542. cases := []struct {
  1543. // name is the unit test name
  1544. name string
  1545. // me is the TOC user session
  1546. me *state.SessionInstance
  1547. // givenCmd is the TOC command
  1548. givenCmd []byte
  1549. // givenChatRegistry is the chat registry passed to the function
  1550. givenChatRegistry *ChatRegistry
  1551. // wantMsg is the expected TOC response
  1552. wantMsg []string
  1553. // mockParams is the list of params sent to mocks that satisfy this
  1554. // method's dependencies
  1555. mockParams mockParams
  1556. }{
  1557. {
  1558. name: "successfully send chat message",
  1559. me: newTestSession("me"),
  1560. givenCmd: []byte(`toc_chat_send 0 "Hello world! :\)"`),
  1561. givenChatRegistry: func() *ChatRegistry {
  1562. reg := NewChatRegistry()
  1563. reg.RegisterSess(0, newTestSession("me"))
  1564. return reg
  1565. }(),
  1566. mockParams: mockParams{
  1567. chatParams: chatParams{
  1568. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1569. {
  1570. sender: state.NewIdentScreenName("me"),
  1571. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1572. Channel: wire.ICBMChannelMIME,
  1573. TLVRestBlock: wire.TLVRestBlock{
  1574. TLVList: wire.TLVList{
  1575. wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
  1576. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1577. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1578. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1579. TLVList: wire.TLVList{
  1580. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world! :)"),
  1581. },
  1582. }),
  1583. },
  1584. },
  1585. },
  1586. result: &wire.SNACMessage{
  1587. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  1588. Channel: wire.ICBMChannelMIME,
  1589. TLVRestBlock: wire.TLVRestBlock{
  1590. TLVList: wire.TLVList{
  1591. wire.NewTLVBE(wire.ChatTLVSenderInformation,
  1592. newTestSession("me").Session().TLVUserInfo()),
  1593. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1594. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1595. TLVList: wire.TLVList{
  1596. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world! :)"),
  1597. },
  1598. }),
  1599. },
  1600. },
  1601. },
  1602. },
  1603. },
  1604. },
  1605. },
  1606. },
  1607. wantMsg: []string{"CHAT_IN:0:me:F:Hello world! :)"},
  1608. },
  1609. {
  1610. name: "successfully send chat message - TOC2 encoded returns CHAT_IN_ENC",
  1611. me: newTestSession("me"),
  1612. givenCmd: []byte(`toc_chat_send 0 "Hello world! :\)"`),
  1613. givenChatRegistry: func() *ChatRegistry {
  1614. reg := NewChatRegistry()
  1615. reg.RegisterSess(0, newTestSession("me", func(i *state.SessionInstance) { i.SetTOC2(true) }))
  1616. return reg
  1617. }(),
  1618. mockParams: mockParams{
  1619. chatParams: chatParams{
  1620. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1621. {
  1622. sender: state.NewIdentScreenName("me"),
  1623. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1624. Channel: wire.ICBMChannelMIME,
  1625. TLVRestBlock: wire.TLVRestBlock{
  1626. TLVList: wire.TLVList{
  1627. wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
  1628. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1629. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1630. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1631. TLVList: wire.TLVList{
  1632. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world! :)"),
  1633. },
  1634. }),
  1635. },
  1636. },
  1637. },
  1638. result: &wire.SNACMessage{
  1639. Body: wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
  1640. Channel: wire.ICBMChannelMIME,
  1641. TLVRestBlock: wire.TLVRestBlock{
  1642. TLVList: wire.TLVList{
  1643. wire.NewTLVBE(wire.ChatTLVSenderInformation,
  1644. newTestSession("me").Session().TLVUserInfo()),
  1645. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1646. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1647. TLVList: wire.TLVList{
  1648. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world! :)"),
  1649. },
  1650. }),
  1651. },
  1652. },
  1653. },
  1654. },
  1655. },
  1656. },
  1657. },
  1658. },
  1659. wantMsg: []string{"CHAT_IN_ENC:0:me:F:A:en:Hello world! :)"},
  1660. },
  1661. {
  1662. name: "send chat message, receive error from chat svc",
  1663. me: newTestSession("me"),
  1664. givenCmd: []byte(`toc_chat_send 0 "Hello world!"`),
  1665. givenChatRegistry: func() *ChatRegistry {
  1666. reg := NewChatRegistry()
  1667. reg.RegisterSess(0, newTestSession("me"))
  1668. return reg
  1669. }(),
  1670. mockParams: mockParams{
  1671. chatParams: chatParams{
  1672. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1673. {
  1674. sender: state.NewIdentScreenName("me"),
  1675. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1676. Channel: wire.ICBMChannelMIME,
  1677. TLVRestBlock: wire.TLVRestBlock{
  1678. TLVList: wire.TLVList{
  1679. wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
  1680. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1681. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1682. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1683. TLVList: wire.TLVList{
  1684. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world!"),
  1685. },
  1686. }),
  1687. },
  1688. },
  1689. },
  1690. err: io.EOF,
  1691. },
  1692. },
  1693. },
  1694. },
  1695. wantMsg: []string{cmdInternalSvcErr},
  1696. },
  1697. {
  1698. name: "send chat message, receive nil response from chat svc",
  1699. me: newTestSession("me"),
  1700. givenCmd: []byte(`toc_chat_send 0 "Hello world!"`),
  1701. givenChatRegistry: func() *ChatRegistry {
  1702. reg := NewChatRegistry()
  1703. reg.RegisterSess(0, newTestSession("me"))
  1704. return reg
  1705. }(),
  1706. mockParams: mockParams{
  1707. chatParams: chatParams{
  1708. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1709. {
  1710. sender: state.NewIdentScreenName("me"),
  1711. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1712. Channel: wire.ICBMChannelMIME,
  1713. TLVRestBlock: wire.TLVRestBlock{
  1714. TLVList: wire.TLVList{
  1715. wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
  1716. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1717. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1718. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1719. TLVList: wire.TLVList{
  1720. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world!"),
  1721. },
  1722. }),
  1723. },
  1724. },
  1725. },
  1726. result: nil,
  1727. },
  1728. },
  1729. },
  1730. },
  1731. wantMsg: []string{cmdInternalSvcErr},
  1732. },
  1733. {
  1734. name: "send chat message, receive unexpected response from chat svc",
  1735. me: newTestSession("me"),
  1736. givenCmd: []byte(`toc_chat_send 0 "Hello world!"`),
  1737. givenChatRegistry: func() *ChatRegistry {
  1738. reg := NewChatRegistry()
  1739. reg.RegisterSess(0, newTestSession("me"))
  1740. return reg
  1741. }(),
  1742. mockParams: mockParams{
  1743. chatParams: chatParams{
  1744. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1745. {
  1746. sender: state.NewIdentScreenName("me"),
  1747. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1748. Channel: wire.ICBMChannelMIME,
  1749. TLVRestBlock: wire.TLVRestBlock{
  1750. TLVList: wire.TLVList{
  1751. wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
  1752. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1753. wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
  1754. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1755. TLVList: wire.TLVList{
  1756. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world!"),
  1757. },
  1758. }),
  1759. },
  1760. },
  1761. },
  1762. result: &wire.SNACMessage{
  1763. Body: wire.SNACError{},
  1764. },
  1765. },
  1766. },
  1767. },
  1768. },
  1769. wantMsg: []string{cmdInternalSvcErr},
  1770. },
  1771. {
  1772. name: "chat room ID with invalid format",
  1773. givenCmd: []byte(`toc_chat_send zero "Hello world!"`),
  1774. wantMsg: []string{cmdInternalSvcErr},
  1775. },
  1776. {
  1777. name: "missing chat session",
  1778. givenCmd: []byte(`toc_chat_send 0 "Hello world!"`),
  1779. givenChatRegistry: NewChatRegistry(),
  1780. wantMsg: []string{cmdInternalSvcErr},
  1781. },
  1782. {
  1783. name: "bad command",
  1784. me: newTestSession("me"),
  1785. givenCmd: []byte(`toc_chat_send`),
  1786. wantMsg: []string{cmdInternalSvcErr},
  1787. },
  1788. }
  1789. for _, tc := range cases {
  1790. t.Run(tc.name, func(t *testing.T) {
  1791. ctx := context.Background()
  1792. chatSvc := newMockChatService(t)
  1793. for _, params := range tc.mockParams.channelMsgToHostParamsChat {
  1794. chatSvc.EXPECT().
  1795. ChannelMsgToHost(ctx, matchSession(params.sender), wire.SNACFrame{}, params.inBody).
  1796. Return(params.result, params.err)
  1797. }
  1798. svc := OSCARProxy{
  1799. Logger: slog.Default(),
  1800. ChatService: chatSvc,
  1801. }
  1802. msg := svc.RecvClientCmd(ctx, nil, tc.givenChatRegistry, tc.givenCmd, nil, nil)
  1803. assert.Equal(t, tc.wantMsg, msg)
  1804. })
  1805. }
  1806. }
  1807. func TestOSCARProxy_RecvClientCmd_ChatWhisper(t *testing.T) {
  1808. cases := []struct {
  1809. // name is the unit test name
  1810. name string
  1811. // me is the TOC user session
  1812. me *state.SessionInstance
  1813. // givenCmd is the TOC command
  1814. givenCmd []byte
  1815. // givenChatRegistry is the chat registry passed to the function
  1816. givenChatRegistry *ChatRegistry
  1817. // wantMsg is the expected TOC response
  1818. wantMsg []string
  1819. // mockParams is the list of params sent to mocks that satisfy this
  1820. // method's dependencies
  1821. mockParams mockParams
  1822. }{
  1823. {
  1824. name: "successfully send chat whisper",
  1825. me: newTestSession("me"),
  1826. givenCmd: []byte(`toc_chat_whisper 0 them "Hello world! :\)"`),
  1827. givenChatRegistry: func() *ChatRegistry {
  1828. reg := NewChatRegistry()
  1829. reg.RegisterSess(0, newTestSession("me"))
  1830. return reg
  1831. }(),
  1832. mockParams: mockParams{
  1833. chatParams: chatParams{
  1834. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1835. {
  1836. sender: state.NewIdentScreenName("me"),
  1837. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1838. Channel: wire.ICBMChannelMIME,
  1839. TLVRestBlock: wire.TLVRestBlock{
  1840. TLVList: wire.TLVList{
  1841. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1842. wire.NewTLVBE(wire.ChatTLVWhisperToUser, "them"),
  1843. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1844. TLVList: wire.TLVList{
  1845. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world! :)"),
  1846. },
  1847. }),
  1848. },
  1849. },
  1850. },
  1851. result: nil,
  1852. },
  1853. },
  1854. },
  1855. },
  1856. wantMsg: []string{},
  1857. },
  1858. {
  1859. name: "send chat whisper, receive error from chat svc",
  1860. me: newTestSession("me"),
  1861. givenCmd: []byte(`toc_chat_whisper 0 them "Hello world!"`),
  1862. givenChatRegistry: func() *ChatRegistry {
  1863. reg := NewChatRegistry()
  1864. reg.RegisterSess(0, newTestSession("me"))
  1865. return reg
  1866. }(),
  1867. mockParams: mockParams{
  1868. chatParams: chatParams{
  1869. channelMsgToHostParamsChat: channelMsgToHostParamsChat{
  1870. {
  1871. sender: state.NewIdentScreenName("me"),
  1872. inBody: wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
  1873. Channel: wire.ICBMChannelMIME,
  1874. TLVRestBlock: wire.TLVRestBlock{
  1875. TLVList: wire.TLVList{
  1876. wire.NewTLVBE(wire.ChatTLVSenderInformation, newTestSession("me").Session().TLVUserInfo()),
  1877. wire.NewTLVBE(wire.ChatTLVWhisperToUser, "them"),
  1878. wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
  1879. TLVList: wire.TLVList{
  1880. wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello world!"),
  1881. },
  1882. }),
  1883. },
  1884. },
  1885. },
  1886. err: io.EOF,
  1887. },
  1888. },
  1889. },
  1890. },
  1891. wantMsg: []string{cmdInternalSvcErr},
  1892. },
  1893. {
  1894. name: "chat room ID with invalid format",
  1895. givenCmd: []byte(`toc_chat_whisper zero them "Hello world!"`),
  1896. wantMsg: []string{cmdInternalSvcErr},
  1897. },
  1898. {
  1899. name: "missing chat session",
  1900. givenCmd: []byte(`toc_chat_whisper 0 them "Hello world!"`),
  1901. givenChatRegistry: NewChatRegistry(),
  1902. wantMsg: []string{cmdInternalSvcErr},
  1903. },
  1904. {
  1905. name: "bad command",
  1906. me: newTestSession("me"),
  1907. givenCmd: []byte(`toc_chat_whisper`),
  1908. wantMsg: []string{cmdInternalSvcErr},
  1909. },
  1910. }
  1911. for _, tc := range cases {
  1912. t.Run(tc.name, func(t *testing.T) {
  1913. ctx := context.Background()
  1914. chatSvc := newMockChatService(t)
  1915. for _, params := range tc.mockParams.channelMsgToHostParamsChat {
  1916. chatSvc.EXPECT().
  1917. ChannelMsgToHost(ctx, matchSession(params.sender), wire.SNACFrame{}, params.inBody).
  1918. Return(params.result, params.err)
  1919. }
  1920. svc := OSCARProxy{
  1921. Logger: slog.Default(),
  1922. ChatService: chatSvc,
  1923. }
  1924. msg := svc.RecvClientCmd(ctx, nil, tc.givenChatRegistry, tc.givenCmd, nil, nil)
  1925. assert.Equal(t, tc.wantMsg, msg)
  1926. })
  1927. }
  1928. }
  1929. func TestOSCARProxy_RecvClientCmd_Evil(t *testing.T) {
  1930. cases := []struct {
  1931. // name is the unit test name
  1932. name string
  1933. // me is the TOC user session
  1934. me *state.SessionInstance
  1935. // givenCmd is the TOC command
  1936. givenCmd []byte
  1937. // wantMsg is the expected TOC response
  1938. wantMsg []string
  1939. // mockParams is the list of params sent to mocks that satisfy this
  1940. // method's dependencies
  1941. mockParams mockParams
  1942. }{
  1943. {
  1944. name: "successfully warn normally",
  1945. me: newTestSession("me"),
  1946. givenCmd: []byte(`toc_evil them norm`),
  1947. mockParams: mockParams{
  1948. icbmParams: icbmParams{
  1949. evilRequestParams: evilRequestParams{
  1950. {
  1951. me: state.NewIdentScreenName("me"),
  1952. inBody: wire.SNAC_0x04_0x08_ICBMEvilRequest{
  1953. SendAs: 0,
  1954. ScreenName: "them",
  1955. },
  1956. msg: wire.SNACMessage{
  1957. Body: wire.SNAC_0x04_0x09_ICBMEvilReply{},
  1958. },
  1959. },
  1960. },
  1961. },
  1962. },
  1963. wantMsg: []string{},
  1964. },
  1965. {
  1966. name: "successfully warn anonymously",
  1967. me: newTestSession("me"),
  1968. givenCmd: []byte(`toc_evil them anon`),
  1969. mockParams: mockParams{
  1970. icbmParams: icbmParams{
  1971. evilRequestParams: evilRequestParams{
  1972. {
  1973. me: state.NewIdentScreenName("me"),
  1974. inBody: wire.SNAC_0x04_0x08_ICBMEvilRequest{
  1975. SendAs: 1,
  1976. ScreenName: "them",
  1977. },
  1978. msg: wire.SNACMessage{
  1979. Body: wire.SNAC_0x04_0x09_ICBMEvilReply{},
  1980. },
  1981. },
  1982. },
  1983. },
  1984. },
  1985. wantMsg: []string{},
  1986. },
  1987. {
  1988. name: "warn, receive error from ICBM service",
  1989. me: newTestSession("me"),
  1990. givenCmd: []byte(`toc_evil them anon`),
  1991. mockParams: mockParams{
  1992. icbmParams: icbmParams{
  1993. evilRequestParams: evilRequestParams{
  1994. {
  1995. me: state.NewIdentScreenName("me"),
  1996. inBody: wire.SNAC_0x04_0x08_ICBMEvilRequest{
  1997. SendAs: 1,
  1998. ScreenName: "them",
  1999. },
  2000. err: io.EOF,
  2001. },
  2002. },
  2003. },
  2004. },
  2005. wantMsg: []string{cmdInternalSvcErr},
  2006. },
  2007. {
  2008. name: "warn, receive snac err",
  2009. me: newTestSession("me"),
  2010. givenCmd: []byte(`toc_evil them anon`),
  2011. mockParams: mockParams{
  2012. icbmParams: icbmParams{
  2013. evilRequestParams: evilRequestParams{
  2014. {
  2015. me: state.NewIdentScreenName("me"),
  2016. inBody: wire.SNAC_0x04_0x08_ICBMEvilRequest{
  2017. SendAs: 1,
  2018. ScreenName: "them",
  2019. },
  2020. msg: wire.SNACMessage{
  2021. Body: wire.SNACError{},
  2022. },
  2023. },
  2024. },
  2025. },
  2026. },
  2027. wantMsg: []string{"ERROR:902"},
  2028. },
  2029. {
  2030. name: "warn, ICBM svc returns unexpected snac type",
  2031. me: newTestSession("me"),
  2032. givenCmd: []byte(`toc_evil them anon`),
  2033. mockParams: mockParams{
  2034. icbmParams: icbmParams{
  2035. evilRequestParams: evilRequestParams{
  2036. {
  2037. me: state.NewIdentScreenName("me"),
  2038. inBody: wire.SNAC_0x04_0x08_ICBMEvilRequest{
  2039. SendAs: 1,
  2040. ScreenName: "them",
  2041. },
  2042. msg: wire.SNACMessage{
  2043. Body: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{},
  2044. },
  2045. },
  2046. },
  2047. },
  2048. },
  2049. wantMsg: []string{cmdInternalSvcErr},
  2050. },
  2051. {
  2052. name: "warn with incorrect type",
  2053. me: newTestSession("me"),
  2054. givenCmd: []byte(`toc_evil them blah`),
  2055. wantMsg: []string{cmdInternalSvcErr},
  2056. },
  2057. {
  2058. name: "bad command",
  2059. me: newTestSession("me"),
  2060. givenCmd: []byte(`toc_evil`),
  2061. wantMsg: []string{cmdInternalSvcErr},
  2062. },
  2063. }
  2064. for _, tc := range cases {
  2065. t.Run(tc.name, func(t *testing.T) {
  2066. ctx := context.Background()
  2067. icbmSvc := newMockICBMService(t)
  2068. for _, params := range tc.mockParams.evilRequestParams {
  2069. icbmSvc.EXPECT().
  2070. EvilRequest(ctx, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  2071. Return(params.msg, params.err)
  2072. }
  2073. svc := OSCARProxy{
  2074. Logger: slog.Default(),
  2075. ICBMService: icbmSvc,
  2076. }
  2077. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2078. assert.Equal(t, tc.wantMsg, msg)
  2079. })
  2080. }
  2081. }
  2082. func TestOSCARProxy_RecvClientCmd_ClientEvent(t *testing.T) {
  2083. cases := []struct {
  2084. name string
  2085. me *state.SessionInstance
  2086. givenCmd []byte
  2087. wantMsg []string
  2088. mockParams mockParams
  2089. }{
  2090. {
  2091. name: "send typing status 0 (no activity)",
  2092. me: newTestSession("me"),
  2093. givenCmd: []byte("toc2_client_event friend 0"),
  2094. mockParams: mockParams{
  2095. icbmParams: icbmParams{
  2096. clientEventParams: clientEventParams{
  2097. {
  2098. sender: state.NewIdentScreenName("me"),
  2099. inBody: wire.SNAC_0x04_0x14_ICBMClientEvent{
  2100. Cookie: 0,
  2101. ChannelID: wire.ICBMChannelIM,
  2102. ScreenName: "friend",
  2103. Event: 0,
  2104. },
  2105. },
  2106. },
  2107. },
  2108. },
  2109. wantMsg: []string{},
  2110. },
  2111. {
  2112. name: "send typing status 1 (typing paused)",
  2113. me: newTestSession("me"),
  2114. givenCmd: []byte("toc2_client_event buddy 1"),
  2115. mockParams: mockParams{
  2116. icbmParams: icbmParams{
  2117. clientEventParams: clientEventParams{
  2118. {
  2119. sender: state.NewIdentScreenName("me"),
  2120. inBody: wire.SNAC_0x04_0x14_ICBMClientEvent{
  2121. Cookie: 0,
  2122. ChannelID: wire.ICBMChannelIM,
  2123. ScreenName: "buddy",
  2124. Event: 1,
  2125. },
  2126. },
  2127. },
  2128. },
  2129. },
  2130. wantMsg: []string{},
  2131. },
  2132. {
  2133. name: "send typing status 2 (currently typing)",
  2134. me: newTestSession("me"),
  2135. givenCmd: []byte("toc2_client_event chatter 2"),
  2136. mockParams: mockParams{
  2137. icbmParams: icbmParams{
  2138. clientEventParams: clientEventParams{
  2139. {
  2140. sender: state.NewIdentScreenName("me"),
  2141. inBody: wire.SNAC_0x04_0x14_ICBMClientEvent{
  2142. Cookie: 0,
  2143. ChannelID: wire.ICBMChannelIM,
  2144. ScreenName: "chatter",
  2145. Event: 2,
  2146. },
  2147. },
  2148. },
  2149. },
  2150. },
  2151. wantMsg: []string{},
  2152. },
  2153. {
  2154. name: "ICBM ClientEvent returns error",
  2155. me: newTestSession("me"),
  2156. givenCmd: []byte("toc2_client_event friend 2"),
  2157. mockParams: mockParams{
  2158. icbmParams: icbmParams{
  2159. clientEventParams: clientEventParams{
  2160. {
  2161. sender: state.NewIdentScreenName("me"),
  2162. inBody: wire.SNAC_0x04_0x14_ICBMClientEvent{
  2163. Cookie: 0,
  2164. ChannelID: wire.ICBMChannelIM,
  2165. ScreenName: "friend",
  2166. Event: 2,
  2167. },
  2168. err: io.EOF,
  2169. },
  2170. },
  2171. },
  2172. },
  2173. wantMsg: []string{cmdInternalSvcErr},
  2174. },
  2175. {
  2176. name: "missing args",
  2177. me: newTestSession("me"),
  2178. givenCmd: []byte("toc2_client_event"),
  2179. wantMsg: []string{cmdInternalSvcErr},
  2180. },
  2181. {
  2182. name: "missing typing status",
  2183. me: newTestSession("me"),
  2184. givenCmd: []byte("toc2_client_event friend"),
  2185. wantMsg: []string{cmdInternalSvcErr},
  2186. },
  2187. {
  2188. name: "invalid typing status (must be 0, 1, or 2)",
  2189. me: newTestSession("me"),
  2190. givenCmd: []byte("toc2_client_event friend 3"),
  2191. wantMsg: []string{cmdInternalSvcErr},
  2192. },
  2193. {
  2194. name: "invalid typing status non-numeric",
  2195. me: newTestSession("me"),
  2196. givenCmd: []byte("toc2_client_event friend typing"),
  2197. wantMsg: []string{cmdInternalSvcErr},
  2198. },
  2199. }
  2200. for _, tc := range cases {
  2201. t.Run(tc.name, func(t *testing.T) {
  2202. ctx := context.Background()
  2203. icbmSvc := newMockICBMService(t)
  2204. for _, params := range tc.mockParams.clientEventParams {
  2205. icbmSvc.EXPECT().
  2206. ClientEvent(ctx, matchSession(params.sender), wire.SNACFrame{}, params.inBody).
  2207. Return(params.err)
  2208. }
  2209. svc := OSCARProxy{
  2210. Logger: slog.Default(),
  2211. ICBMService: icbmSvc,
  2212. }
  2213. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2214. assert.Equal(t, tc.wantMsg, msg)
  2215. })
  2216. }
  2217. }
  2218. func TestOSCARProxy_RecvClientCmd_ChangePassword(t *testing.T) {
  2219. cases := []struct {
  2220. // name is the unit test name
  2221. name string
  2222. // me is the TOC user session
  2223. me *state.SessionInstance
  2224. // givenCmd is the TOC command
  2225. givenCmd []byte
  2226. // wantMsg is the expected TOC response
  2227. wantMsg []string
  2228. // mockParams is the list of params sent to mocks that satisfy this
  2229. // method's dependencies
  2230. mockParams mockParams
  2231. }{
  2232. {
  2233. name: "successfully change password",
  2234. me: newTestSession("me"),
  2235. givenCmd: []byte("toc_change_passwd oldpa\\$\\$ newpa\\$\\$"),
  2236. mockParams: mockParams{
  2237. adminParams: adminParams{
  2238. infoChangeRequestParams: infoChangeRequestParams{
  2239. {
  2240. me: state.NewIdentScreenName("me"),
  2241. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  2242. TLVRestBlock: wire.TLVRestBlock{
  2243. TLVList: wire.TLVList{
  2244. wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpa$$"),
  2245. wire.NewTLVBE(wire.AdminTLVNewPassword, "newpa$$"),
  2246. },
  2247. },
  2248. },
  2249. msg: wire.SNACMessage{
  2250. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  2251. Permissions: wire.AdminInfoPermissionsReadWrite,
  2252. TLVBlock: wire.TLVBlock{
  2253. TLVList: wire.TLVList{
  2254. wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
  2255. },
  2256. },
  2257. },
  2258. },
  2259. },
  2260. },
  2261. },
  2262. },
  2263. wantMsg: []string{"ADMIN_PASSWD_STATUS:0"},
  2264. },
  2265. {
  2266. name: "change password - invalid password length",
  2267. me: newTestSession("me"),
  2268. givenCmd: []byte("toc_change_passwd oldpass np"),
  2269. mockParams: mockParams{
  2270. adminParams: adminParams{
  2271. infoChangeRequestParams: infoChangeRequestParams{
  2272. {
  2273. me: state.NewIdentScreenName("me"),
  2274. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  2275. TLVRestBlock: wire.TLVRestBlock{
  2276. TLVList: wire.TLVList{
  2277. wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
  2278. wire.NewTLVBE(wire.AdminTLVNewPassword, "np"),
  2279. },
  2280. },
  2281. },
  2282. msg: wire.SNACMessage{
  2283. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  2284. Permissions: wire.AdminInfoPermissionsReadWrite,
  2285. TLVBlock: wire.TLVBlock{
  2286. TLVList: wire.TLVList{
  2287. wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
  2288. wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorInvalidPasswordLength),
  2289. },
  2290. },
  2291. },
  2292. },
  2293. },
  2294. },
  2295. },
  2296. },
  2297. wantMsg: []string{"ERROR:911"},
  2298. },
  2299. {
  2300. name: "change password - incorrect password",
  2301. me: newTestSession("me"),
  2302. givenCmd: []byte("toc_change_passwd oldpass baddpass"),
  2303. mockParams: mockParams{
  2304. adminParams: adminParams{
  2305. infoChangeRequestParams: infoChangeRequestParams{
  2306. {
  2307. me: state.NewIdentScreenName("me"),
  2308. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  2309. TLVRestBlock: wire.TLVRestBlock{
  2310. TLVList: wire.TLVList{
  2311. wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
  2312. wire.NewTLVBE(wire.AdminTLVNewPassword, "baddpass"),
  2313. },
  2314. },
  2315. },
  2316. msg: wire.SNACMessage{
  2317. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  2318. Permissions: wire.AdminInfoPermissionsReadWrite,
  2319. TLVBlock: wire.TLVBlock{
  2320. TLVList: wire.TLVList{
  2321. wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
  2322. wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorValidatePassword),
  2323. },
  2324. },
  2325. },
  2326. },
  2327. },
  2328. },
  2329. },
  2330. },
  2331. wantMsg: []string{"ERROR:980"},
  2332. },
  2333. {
  2334. name: "change password - catch-all error response",
  2335. me: newTestSession("me"),
  2336. givenCmd: []byte("toc_change_passwd oldpass baddpass"),
  2337. mockParams: mockParams{
  2338. adminParams: adminParams{
  2339. infoChangeRequestParams: infoChangeRequestParams{
  2340. {
  2341. me: state.NewIdentScreenName("me"),
  2342. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  2343. TLVRestBlock: wire.TLVRestBlock{
  2344. TLVList: wire.TLVList{
  2345. wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
  2346. wire.NewTLVBE(wire.AdminTLVNewPassword, "baddpass"),
  2347. },
  2348. },
  2349. },
  2350. msg: wire.SNACMessage{
  2351. Body: wire.SNAC_0x07_0x05_AdminChangeReply{
  2352. Permissions: wire.AdminInfoPermissionsReadWrite,
  2353. TLVBlock: wire.TLVBlock{
  2354. TLVList: wire.TLVList{
  2355. wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}),
  2356. wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorAllOtherErrors),
  2357. },
  2358. },
  2359. },
  2360. },
  2361. },
  2362. },
  2363. },
  2364. },
  2365. wantMsg: []string{"ERROR:913"},
  2366. },
  2367. {
  2368. name: "change password - runtime error from admin svc",
  2369. me: newTestSession("me"),
  2370. givenCmd: []byte("toc_change_passwd oldpass baddpass"),
  2371. mockParams: mockParams{
  2372. adminParams: adminParams{
  2373. infoChangeRequestParams: infoChangeRequestParams{
  2374. {
  2375. me: state.NewIdentScreenName("me"),
  2376. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  2377. TLVRestBlock: wire.TLVRestBlock{
  2378. TLVList: wire.TLVList{
  2379. wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
  2380. wire.NewTLVBE(wire.AdminTLVNewPassword, "baddpass"),
  2381. },
  2382. },
  2383. },
  2384. err: io.EOF,
  2385. },
  2386. },
  2387. },
  2388. },
  2389. wantMsg: []string{cmdInternalSvcErr},
  2390. },
  2391. {
  2392. name: "change password - unexpected response from admin svc",
  2393. me: newTestSession("me"),
  2394. givenCmd: []byte("toc_change_passwd oldpass baddpass"),
  2395. mockParams: mockParams{
  2396. adminParams: adminParams{
  2397. infoChangeRequestParams: infoChangeRequestParams{
  2398. {
  2399. me: state.NewIdentScreenName("me"),
  2400. inBody: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
  2401. TLVRestBlock: wire.TLVRestBlock{
  2402. TLVList: wire.TLVList{
  2403. wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
  2404. wire.NewTLVBE(wire.AdminTLVNewPassword, "baddpass"),
  2405. },
  2406. },
  2407. },
  2408. msg: wire.SNACMessage{
  2409. Body: wire.SNACError{},
  2410. },
  2411. },
  2412. },
  2413. },
  2414. },
  2415. wantMsg: []string{cmdInternalSvcErr},
  2416. },
  2417. {
  2418. name: "bad command",
  2419. me: newTestSession("me"),
  2420. givenCmd: []byte(`toc_change_passwd`),
  2421. wantMsg: []string{cmdInternalSvcErr},
  2422. },
  2423. }
  2424. for _, tc := range cases {
  2425. t.Run(tc.name, func(t *testing.T) {
  2426. ctx := context.Background()
  2427. adminSvc := newMockAdminService(t)
  2428. for _, params := range tc.mockParams.infoChangeRequestParams {
  2429. adminSvc.EXPECT().
  2430. InfoChangeRequest(ctx, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  2431. Return(params.msg, params.err)
  2432. }
  2433. svc := OSCARProxy{
  2434. Logger: slog.Default(),
  2435. AdminService: adminSvc,
  2436. }
  2437. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2438. assert.Equal(t, tc.wantMsg, msg)
  2439. })
  2440. }
  2441. }
  2442. func TestOSCARProxy_RecvClientCmd_GetDirSearchURL(t *testing.T) {
  2443. cases := []struct {
  2444. // name is the unit test name
  2445. name string
  2446. // me is the TOC user session
  2447. me *state.SessionInstance
  2448. // givenCmd is the TOC command
  2449. givenCmd []byte
  2450. // wantMsg is the expected TOC response
  2451. wantMsg []string
  2452. // mockParams is the list of params sent to mocks that satisfy this
  2453. // method's dependencies
  2454. mockParams mockParams
  2455. }{
  2456. {
  2457. name: "successfully request user info",
  2458. me: newTestSession("me"),
  2459. givenCmd: []byte(`toc_dir_search "first \[name\]":"middle name":"last name":"maiden name":"city":"state":"country":"email"`),
  2460. mockParams: mockParams{
  2461. cookieBakerParams: cookieBakerParams{
  2462. issueParams: issueParams{
  2463. {
  2464. data: []byte("me"),
  2465. returnData: []byte("monster"),
  2466. },
  2467. },
  2468. },
  2469. },
  2470. wantMsg: []string{"GOTO_URL:search results:dir_search?city=city&cookie=6d6f6e73746572&country=country&email=email&first_name=first+%5Bname%5D&last_name=last+name&maiden_name=maiden+name&middle_name=middle+name&state=state"},
  2471. },
  2472. {
  2473. name: "successfully request user info by keywords",
  2474. me: newTestSession("me"),
  2475. givenCmd: []byte(`toc_dir_search ::::::::::"searchkw"`),
  2476. mockParams: mockParams{
  2477. cookieBakerParams: cookieBakerParams{
  2478. issueParams: issueParams{
  2479. {
  2480. data: []byte("me"),
  2481. returnData: []byte("monster"),
  2482. },
  2483. },
  2484. },
  2485. },
  2486. wantMsg: []string{"GOTO_URL:search results:dir_search?cookie=6d6f6e73746572&keyword=searchkw"},
  2487. },
  2488. {
  2489. name: "request user info with too many params",
  2490. me: newTestSession("me"),
  2491. givenCmd: []byte(`toc_dir_search ::::::::::::::::::::"searchkw"`),
  2492. wantMsg: []string{cmdInternalSvcErr},
  2493. },
  2494. {
  2495. name: "request user info, get cookie issue error",
  2496. me: newTestSession("me"),
  2497. givenCmd: []byte(`toc_dir_search them`),
  2498. mockParams: mockParams{
  2499. cookieBakerParams: cookieBakerParams{
  2500. issueParams: issueParams{
  2501. {
  2502. data: []byte("me"),
  2503. returnErr: io.EOF,
  2504. },
  2505. },
  2506. },
  2507. },
  2508. wantMsg: []string{cmdInternalSvcErr},
  2509. },
  2510. {
  2511. name: "bad command",
  2512. me: newTestSession("me"),
  2513. givenCmd: []byte(`toc_dir_search`),
  2514. wantMsg: []string{cmdInternalSvcErr},
  2515. },
  2516. }
  2517. for _, tc := range cases {
  2518. t.Run(tc.name, func(t *testing.T) {
  2519. ctx := context.Background()
  2520. cookieBaker := newMockCookieBaker(t)
  2521. for _, params := range tc.mockParams.issueParams {
  2522. cookieBaker.EXPECT().
  2523. Issue(params.data, state.DefaultCookieTTL).
  2524. Return(params.returnData, params.returnErr)
  2525. }
  2526. svc := OSCARProxy{
  2527. Logger: slog.Default(),
  2528. CookieBaker: cookieBaker,
  2529. SNACRateLimits: wire.DefaultSNACRateLimits(),
  2530. }
  2531. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2532. assert.Equal(t, tc.wantMsg, msg)
  2533. })
  2534. }
  2535. }
  2536. func TestOSCARProxy_RecvClientCmd_GetDirURL(t *testing.T) {
  2537. cases := []struct {
  2538. // name is the unit test name
  2539. name string
  2540. // me is the TOC user session
  2541. me *state.SessionInstance
  2542. // givenCmd is the TOC command
  2543. givenCmd []byte
  2544. // wantMsg is the expected TOC response
  2545. wantMsg []string
  2546. // mockParams is the list of params sent to mocks that satisfy this
  2547. // method's dependencies
  2548. mockParams mockParams
  2549. }{
  2550. {
  2551. name: "successfully request user dir info",
  2552. me: newTestSession("me"),
  2553. givenCmd: []byte(`toc_get_dir them`),
  2554. mockParams: mockParams{
  2555. cookieBakerParams: cookieBakerParams{
  2556. issueParams: issueParams{
  2557. {
  2558. data: []byte("me"),
  2559. returnData: []byte("monster"),
  2560. },
  2561. },
  2562. },
  2563. },
  2564. wantMsg: []string{"GOTO_URL:directory info:dir_info?cookie=6d6f6e73746572&user=them"},
  2565. },
  2566. {
  2567. name: "request user info, get cookie issue error",
  2568. me: newTestSession("me"),
  2569. givenCmd: []byte(`toc_get_dir them`),
  2570. mockParams: mockParams{
  2571. cookieBakerParams: cookieBakerParams{
  2572. issueParams: issueParams{
  2573. {
  2574. data: []byte("me"),
  2575. returnErr: io.EOF,
  2576. },
  2577. },
  2578. },
  2579. },
  2580. wantMsg: []string{cmdInternalSvcErr},
  2581. },
  2582. {
  2583. name: "bad command",
  2584. me: newTestSession("me"),
  2585. givenCmd: []byte(`toc_get_dir`),
  2586. wantMsg: []string{cmdInternalSvcErr},
  2587. },
  2588. }
  2589. for _, tc := range cases {
  2590. t.Run(tc.name, func(t *testing.T) {
  2591. ctx := context.Background()
  2592. cookieBaker := newMockCookieBaker(t)
  2593. for _, params := range tc.mockParams.issueParams {
  2594. cookieBaker.EXPECT().
  2595. Issue(params.data, state.DefaultCookieTTL).
  2596. Return(params.returnData, params.returnErr)
  2597. }
  2598. svc := OSCARProxy{
  2599. Logger: slog.Default(),
  2600. CookieBaker: cookieBaker,
  2601. }
  2602. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2603. assert.Equal(t, tc.wantMsg, msg)
  2604. })
  2605. }
  2606. }
  2607. func TestOSCARProxy_RecvClientCmd_GetInfoURL(t *testing.T) {
  2608. cases := []struct {
  2609. // name is the unit test name
  2610. name string
  2611. // me is the TOC user session
  2612. me *state.SessionInstance
  2613. // givenCmd is the TOC command
  2614. givenCmd []byte
  2615. // wantMsg is the expected TOC response
  2616. wantMsg []string
  2617. // mockParams is the list of params sent to mocks that satisfy this
  2618. // method's dependencies
  2619. mockParams mockParams
  2620. }{
  2621. {
  2622. name: "successfully request user info",
  2623. me: newTestSession("me"),
  2624. givenCmd: []byte(`toc_get_info them`),
  2625. mockParams: mockParams{
  2626. cookieBakerParams: cookieBakerParams{
  2627. issueParams: issueParams{
  2628. {
  2629. data: []byte("me"),
  2630. returnData: []byte("monster"),
  2631. },
  2632. },
  2633. },
  2634. },
  2635. wantMsg: []string{"GOTO_URL:profile:info?cookie=6d6f6e73746572&from=me&user=them"},
  2636. },
  2637. {
  2638. name: "request user info, get cookie issue error",
  2639. me: newTestSession("me"),
  2640. givenCmd: []byte(`toc_get_info them`),
  2641. mockParams: mockParams{
  2642. cookieBakerParams: cookieBakerParams{
  2643. issueParams: issueParams{
  2644. {
  2645. data: []byte("me"),
  2646. returnErr: io.EOF,
  2647. },
  2648. },
  2649. },
  2650. },
  2651. wantMsg: []string{cmdInternalSvcErr},
  2652. },
  2653. {
  2654. name: "bad command",
  2655. me: newTestSession("me"),
  2656. givenCmd: []byte(`toc_get_info`),
  2657. wantMsg: []string{cmdInternalSvcErr},
  2658. },
  2659. }
  2660. for _, tc := range cases {
  2661. t.Run(tc.name, func(t *testing.T) {
  2662. ctx := context.Background()
  2663. cookieBaker := newMockCookieBaker(t)
  2664. for _, params := range tc.mockParams.issueParams {
  2665. cookieBaker.EXPECT().
  2666. Issue(params.data, state.DefaultCookieTTL).
  2667. Return(params.returnData, params.returnErr)
  2668. }
  2669. svc := OSCARProxy{
  2670. Logger: slog.Default(),
  2671. CookieBaker: cookieBaker,
  2672. }
  2673. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2674. assert.Equal(t, tc.wantMsg, msg)
  2675. })
  2676. }
  2677. }
  2678. func TestOSCARProxy_RecvClientCmd_GetStatus(t *testing.T) {
  2679. cases := []struct {
  2680. // name is the unit test name
  2681. name string
  2682. // me is the TOC user session
  2683. me *state.SessionInstance
  2684. // givenCmd is the TOC command
  2685. givenCmd []byte
  2686. // wantMsg is the expected TOC response
  2687. wantMsg []string
  2688. // mockParams is the list of params sent to mocks that satisfy this
  2689. // method's dependencies
  2690. mockParams mockParams
  2691. }{
  2692. {
  2693. name: "successfully request status",
  2694. me: newTestSession("me"),
  2695. givenCmd: []byte("toc_get_status them"),
  2696. mockParams: mockParams{
  2697. locateParams: locateParams{
  2698. userInfoQueryParams: userInfoQueryParams{
  2699. {
  2700. me: state.NewIdentScreenName("me"),
  2701. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  2702. ScreenName: "them",
  2703. },
  2704. msg: wire.SNACMessage{
  2705. Body: wire.SNAC_0x02_0x06_LocateUserInfoReply{
  2706. TLVUserInfo: wire.TLVUserInfo{
  2707. ScreenName: "them",
  2708. WarningLevel: 0,
  2709. TLVBlock: wire.TLVBlock{
  2710. TLVList: wire.TLVList{
  2711. wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
  2712. wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
  2713. wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree),
  2714. },
  2715. },
  2716. },
  2717. },
  2718. },
  2719. },
  2720. },
  2721. },
  2722. },
  2723. wantMsg: []string{"UPDATE_BUDDY:them:T:0:1234:5678: O "},
  2724. },
  2725. {
  2726. name: "request status, receive err from locate svc",
  2727. me: newTestSession("me"),
  2728. givenCmd: []byte("toc_get_status them"),
  2729. mockParams: mockParams{
  2730. locateParams: locateParams{
  2731. userInfoQueryParams: userInfoQueryParams{
  2732. {
  2733. me: state.NewIdentScreenName("me"),
  2734. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  2735. ScreenName: "them",
  2736. },
  2737. err: io.EOF,
  2738. },
  2739. },
  2740. },
  2741. },
  2742. wantMsg: []string{cmdInternalSvcErr},
  2743. },
  2744. {
  2745. name: "request status, user not online",
  2746. me: newTestSession("me"),
  2747. givenCmd: []byte("toc_get_status them"),
  2748. mockParams: mockParams{
  2749. locateParams: locateParams{
  2750. userInfoQueryParams: userInfoQueryParams{
  2751. {
  2752. me: state.NewIdentScreenName("me"),
  2753. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  2754. ScreenName: "them",
  2755. },
  2756. msg: wire.SNACMessage{
  2757. Body: wire.SNACError{
  2758. Code: wire.ErrorCodeNotLoggedOn,
  2759. },
  2760. },
  2761. },
  2762. },
  2763. },
  2764. },
  2765. wantMsg: []string{"ERROR:901:them"},
  2766. },
  2767. {
  2768. name: "request status, receive unexpected error code",
  2769. me: newTestSession("me"),
  2770. givenCmd: []byte("toc_get_status them"),
  2771. mockParams: mockParams{
  2772. locateParams: locateParams{
  2773. userInfoQueryParams: userInfoQueryParams{
  2774. {
  2775. me: state.NewIdentScreenName("me"),
  2776. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  2777. ScreenName: "them",
  2778. },
  2779. msg: wire.SNACMessage{
  2780. Body: wire.SNACError{
  2781. Code: wire.ErrorCodeInvalidSnac,
  2782. },
  2783. },
  2784. },
  2785. },
  2786. },
  2787. },
  2788. wantMsg: []string{cmdInternalSvcErr},
  2789. },
  2790. {
  2791. name: "request status, unexpected response from locate svc",
  2792. me: newTestSession("me"),
  2793. givenCmd: []byte("toc_get_status them"),
  2794. mockParams: mockParams{
  2795. locateParams: locateParams{
  2796. userInfoQueryParams: userInfoQueryParams{
  2797. {
  2798. me: state.NewIdentScreenName("me"),
  2799. inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
  2800. ScreenName: "them",
  2801. },
  2802. msg: wire.SNACMessage{
  2803. Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{},
  2804. },
  2805. },
  2806. },
  2807. },
  2808. },
  2809. wantMsg: []string{cmdInternalSvcErr},
  2810. },
  2811. {
  2812. name: "bad command",
  2813. me: newTestSession("me"),
  2814. givenCmd: []byte(`toc_get_status`),
  2815. wantMsg: []string{cmdInternalSvcErr},
  2816. },
  2817. }
  2818. for _, tc := range cases {
  2819. t.Run(tc.name, func(t *testing.T) {
  2820. ctx := context.Background()
  2821. locateSvc := newMockLocateService(t)
  2822. for _, params := range tc.mockParams.userInfoQueryParams {
  2823. locateSvc.EXPECT().
  2824. UserInfoQuery(mock.Anything, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  2825. Return(params.msg, params.err)
  2826. }
  2827. svc := OSCARProxy{
  2828. Logger: slog.Default(),
  2829. LocateService: locateSvc,
  2830. }
  2831. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2832. assert.Equal(t, tc.wantMsg, msg)
  2833. })
  2834. }
  2835. }
  2836. func TestOSCARProxy_RecvClientCmd_InitDone(t *testing.T) {
  2837. cases := []struct {
  2838. // name is the unit test name
  2839. name string
  2840. // me is the TOC user session
  2841. me *state.SessionInstance
  2842. // givenCmd is the TOC command
  2843. givenCmd []byte
  2844. // wantMsg is the expected TOC response
  2845. wantMsg []string
  2846. // mockParams is the list of params sent to mocks that satisfy this
  2847. // method's dependencies
  2848. mockParams mockParams
  2849. }{
  2850. {
  2851. name: "successfully initialize connection",
  2852. me: newTestSession("me"),
  2853. givenCmd: []byte(`toc_init_done`),
  2854. mockParams: mockParams{
  2855. oServiceParams: oServiceParams{
  2856. clientOnlineParams: clientOnlineParams{
  2857. {
  2858. me: state.NewIdentScreenName("me"),
  2859. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2860. service: wire.BOS,
  2861. },
  2862. },
  2863. },
  2864. feedBagParams: feedBagParams{
  2865. useFeedbagParams: useFeedbagParams{
  2866. {
  2867. me: state.NewIdentScreenName("me"),
  2868. },
  2869. },
  2870. },
  2871. },
  2872. wantMsg: []string{},
  2873. },
  2874. {
  2875. name: "initialize connection, receive err from BOS oservice svc",
  2876. me: newTestSession("me"),
  2877. givenCmd: []byte(`toc_init_done`),
  2878. mockParams: mockParams{
  2879. oServiceParams: oServiceParams{
  2880. clientOnlineParams: clientOnlineParams{
  2881. {
  2882. me: state.NewIdentScreenName("me"),
  2883. body: wire.SNAC_0x01_0x02_OServiceClientOnline{},
  2884. service: wire.BOS,
  2885. err: io.EOF,
  2886. },
  2887. },
  2888. },
  2889. feedBagParams: feedBagParams{
  2890. useFeedbagParams: useFeedbagParams{
  2891. {
  2892. me: state.NewIdentScreenName("me"),
  2893. },
  2894. },
  2895. },
  2896. },
  2897. wantMsg: []string{cmdInternalSvcErr},
  2898. },
  2899. }
  2900. for _, tc := range cases {
  2901. t.Run(tc.name, func(t *testing.T) {
  2902. ctx := context.Background()
  2903. oSvc := newMockOServiceService(t)
  2904. for _, params := range tc.mockParams.clientOnlineParams {
  2905. oSvc.EXPECT().
  2906. ClientOnline(ctx, params.service, params.body, matchSession(params.me)).
  2907. Return(params.err)
  2908. }
  2909. fbMgr := newMockFeedbagManager(t)
  2910. for _, params := range tc.mockParams.useFeedbagParams {
  2911. fbMgr.EXPECT().
  2912. UseFeedbag(ctx, params.me).
  2913. Return(params.err)
  2914. }
  2915. svc := OSCARProxy{
  2916. Logger: slog.Default(),
  2917. OServiceService: oSvc,
  2918. FeedbagManager: fbMgr,
  2919. }
  2920. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  2921. assert.Equal(t, tc.wantMsg, msg)
  2922. })
  2923. }
  2924. }
  2925. func TestOSCARProxy_RecvClientCmd_RemoveBuddy(t *testing.T) {
  2926. cases := []struct {
  2927. // name is the unit test name
  2928. name string
  2929. // me is the TOC user session
  2930. me *state.SessionInstance
  2931. // givenCmd is the TOC command
  2932. givenCmd []byte
  2933. // wantMsg is the expected TOC response
  2934. wantMsg []string
  2935. // mockParams is the list of params sent to mocks that satisfy this
  2936. // method's dependencies
  2937. mockParams mockParams
  2938. }{
  2939. {
  2940. name: "successfully remove buddies",
  2941. me: newTestSession("me"),
  2942. givenCmd: []byte("toc_remove_buddy friend1 friend2 friend3"),
  2943. mockParams: mockParams{
  2944. buddyParams: buddyParams{
  2945. delBuddiesParams: delBuddiesParams{
  2946. {
  2947. me: state.NewIdentScreenName("me"),
  2948. inBody: wire.SNAC_0x03_0x05_BuddyDelBuddies{
  2949. Buddies: []struct {
  2950. ScreenName string `oscar:"len_prefix=uint8"`
  2951. }{
  2952. {ScreenName: "friend1"},
  2953. {ScreenName: "friend2"},
  2954. {ScreenName: "friend3"},
  2955. },
  2956. },
  2957. },
  2958. },
  2959. },
  2960. },
  2961. wantMsg: []string{},
  2962. },
  2963. {
  2964. name: "remove buddies with empty list",
  2965. me: newTestSession("me"),
  2966. givenCmd: []byte("toc_remove_buddy"),
  2967. mockParams: mockParams{
  2968. buddyParams: buddyParams{
  2969. delBuddiesParams: delBuddiesParams{
  2970. {
  2971. me: state.NewIdentScreenName("me"),
  2972. inBody: wire.SNAC_0x03_0x05_BuddyDelBuddies{},
  2973. },
  2974. },
  2975. },
  2976. },
  2977. wantMsg: []string{},
  2978. },
  2979. {
  2980. name: "remove buddies, receive error from buddy service",
  2981. me: newTestSession("me"),
  2982. givenCmd: []byte("toc_remove_buddy friend1"),
  2983. mockParams: mockParams{
  2984. buddyParams: buddyParams{
  2985. delBuddiesParams: delBuddiesParams{
  2986. {
  2987. me: state.NewIdentScreenName("me"),
  2988. inBody: wire.SNAC_0x03_0x05_BuddyDelBuddies{
  2989. Buddies: []struct {
  2990. ScreenName string `oscar:"len_prefix=uint8"`
  2991. }{
  2992. {ScreenName: "friend1"},
  2993. },
  2994. },
  2995. err: io.EOF,
  2996. },
  2997. },
  2998. },
  2999. },
  3000. wantMsg: []string{cmdInternalSvcErr},
  3001. },
  3002. }
  3003. for _, tc := range cases {
  3004. t.Run(tc.name, func(t *testing.T) {
  3005. ctx := context.Background()
  3006. buddySvc := newMockBuddyService(t)
  3007. for _, params := range tc.mockParams.delBuddiesParams {
  3008. buddySvc.EXPECT().
  3009. DelBuddies(ctx, matchSession(params.me), params.inBody).
  3010. Return(params.err)
  3011. }
  3012. svc := OSCARProxy{
  3013. Logger: slog.Default(),
  3014. BuddyService: buddySvc,
  3015. }
  3016. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  3017. assert.Equal(t, tc.wantMsg, msg)
  3018. })
  3019. }
  3020. }
  3021. func TestOSCARProxy_RecvClientCmd_NewBuddies(t *testing.T) {
  3022. cases := []struct {
  3023. name string
  3024. me *state.SessionInstance
  3025. givenCmd []byte
  3026. wantMsg []string
  3027. mockParams mockParams
  3028. }{
  3029. {
  3030. name: "add new users to existing group",
  3031. me: newTestSession("me"),
  3032. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\ng:Family\nb:alice\nb:bob\n}"),
  3033. wantMsg: []string{"NEW_BUDDY_REPLY2:mike:added", "NEW_BUDDY_REPLY2:mk6i:added", "NEW_BUDDY_REPLY2:alice:added", "NEW_BUDDY_REPLY2:bob:added"},
  3034. mockParams: mockParams{
  3035. feedBagParams: feedBagParams{
  3036. feedbagParams: feedbagParams{
  3037. {
  3038. screenName: state.NewIdentScreenName("me"),
  3039. results: []wire.FeedbagItem{
  3040. {
  3041. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3042. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 21827, 29709})}},
  3043. },
  3044. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3045. {Name: "Co-Workers", GroupID: 21827, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3046. {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3047. },
  3048. err: nil,
  3049. },
  3050. },
  3051. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3052. {
  3053. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3054. items: []wire.FeedbagItem{
  3055. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
  3056. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
  3057. },
  3058. msg: nil, err: nil,
  3059. },
  3060. {
  3061. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3062. items: []wire.FeedbagItem{
  3063. {
  3064. Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3065. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}},
  3066. },
  3067. },
  3068. msg: nil, err: nil,
  3069. },
  3070. {
  3071. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3072. items: []wire.FeedbagItem{
  3073. {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 29709, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  3074. {ItemID: 4, ClassID: wire.FeedbagClassIdBuddy, GroupID: 29709, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  3075. },
  3076. msg: nil, err: nil,
  3077. },
  3078. {
  3079. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3080. items: []wire.FeedbagItem{
  3081. {
  3082. Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3083. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{3, 4})}},
  3084. },
  3085. },
  3086. msg: nil, err: nil,
  3087. },
  3088. },
  3089. },
  3090. },
  3091. },
  3092. {
  3093. name: "empty feedbag: add new group and buddies",
  3094. me: newTestSession("me"),
  3095. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\n}"),
  3096. wantMsg: []string{"NEW_BUDDY_REPLY2:mike:added", "NEW_BUDDY_REPLY2:mk6i:added"},
  3097. mockParams: mockParams{
  3098. feedBagParams: feedBagParams{
  3099. feedbagParams: feedbagParams{
  3100. {
  3101. screenName: state.NewIdentScreenName("me"),
  3102. results: []wire.FeedbagItem{},
  3103. err: nil,
  3104. },
  3105. },
  3106. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3107. // First: insert the two buddy items (deterministic randIntn gives GroupID 1, ItemIDs 2 and 3)
  3108. {
  3109. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3110. items: []wire.FeedbagItem{
  3111. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
  3112. {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
  3113. },
  3114. msg: nil, err: nil,
  3115. },
  3116. // Second: insert the new Buddies group with Order listing the buddy item IDs
  3117. {
  3118. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3119. items: []wire.FeedbagItem{
  3120. {
  3121. Name: "Buddies", GroupID: 1, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3122. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3})}},
  3123. },
  3124. },
  3125. msg: nil, err: nil,
  3126. },
  3127. // Third: insert the root group so its Order lists the new group ID
  3128. {
  3129. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3130. items: []wire.FeedbagItem{
  3131. {
  3132. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3133. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
  3134. },
  3135. },
  3136. msg: nil, err: nil,
  3137. },
  3138. },
  3139. },
  3140. },
  3141. },
  3142. {
  3143. name: "add new group and buddies",
  3144. me: newTestSession("me"),
  3145. givenCmd: []byte("toc2_new_buddies {g:Co-Workers\nb:carol\nb:dan\ng:Family\nb:alice\nb:bob\n}"),
  3146. wantMsg: []string{"NEW_BUDDY_REPLY2:carol:added", "NEW_BUDDY_REPLY2:dan:added", "NEW_BUDDY_REPLY2:alice:added", "NEW_BUDDY_REPLY2:bob:added"},
  3147. mockParams: mockParams{
  3148. feedBagParams: feedBagParams{
  3149. feedbagParams: feedbagParams{
  3150. {
  3151. screenName: state.NewIdentScreenName("me"),
  3152. results: []wire.FeedbagItem{
  3153. {
  3154. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3155. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3156. },
  3157. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3158. },
  3159. err: nil,
  3160. },
  3161. },
  3162. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3163. {
  3164. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3165. items: []wire.FeedbagItem{
  3166. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "carol", TLVLBlock: wire.TLVLBlock{}},
  3167. {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "dan", TLVLBlock: wire.TLVLBlock{}},
  3168. },
  3169. msg: nil, err: nil,
  3170. },
  3171. {
  3172. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3173. items: []wire.FeedbagItem{
  3174. {
  3175. Name: "Co-Workers", GroupID: 1, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3176. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3})}},
  3177. },
  3178. },
  3179. msg: nil, err: nil,
  3180. },
  3181. {
  3182. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3183. items: []wire.FeedbagItem{
  3184. {ItemID: 5, ClassID: wire.FeedbagClassIdBuddy, GroupID: 4, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  3185. {ItemID: 6, ClassID: wire.FeedbagClassIdBuddy, GroupID: 4, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  3186. },
  3187. msg: nil, err: nil,
  3188. },
  3189. {
  3190. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3191. items: []wire.FeedbagItem{
  3192. {
  3193. Name: "Family", GroupID: 4, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3194. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{5, 6})}},
  3195. },
  3196. },
  3197. msg: nil, err: nil,
  3198. },
  3199. // Root updated once at end
  3200. {
  3201. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3202. items: []wire.FeedbagItem{
  3203. {
  3204. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3205. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 1, 4})}},
  3206. },
  3207. },
  3208. msg: nil, err: nil,
  3209. },
  3210. },
  3211. },
  3212. },
  3213. },
  3214. {
  3215. name: "empty config returns error",
  3216. me: newTestSession("me"),
  3217. givenCmd: []byte("toc2_new_buddies "),
  3218. wantMsg: []string{cmdInternalSvcErr},
  3219. mockParams: mockParams{},
  3220. },
  3221. {
  3222. name: "whitespace-only config returns error",
  3223. me: newTestSession("me"),
  3224. givenCmd: []byte("toc2_new_buddies "),
  3225. wantMsg: []string{cmdInternalSvcErr},
  3226. mockParams: mockParams{},
  3227. },
  3228. {
  3229. name: "buddy without group returns parse error",
  3230. me: newTestSession("me"),
  3231. givenCmd: []byte("toc2_new_buddies {b:alice\n}"),
  3232. wantMsg: []string{cmdInternalSvcErr},
  3233. mockParams: mockParams{},
  3234. },
  3235. {
  3236. name: "empty group name returns parse error",
  3237. me: newTestSession("me"),
  3238. givenCmd: []byte("toc2_new_buddies {g:\nb:alice\n}"),
  3239. wantMsg: []string{cmdInternalSvcErr},
  3240. mockParams: mockParams{},
  3241. },
  3242. {
  3243. name: "empty buddy name returns parse error",
  3244. me: newTestSession("me"),
  3245. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:\n}"),
  3246. wantMsg: []string{cmdInternalSvcErr},
  3247. mockParams: mockParams{},
  3248. },
  3249. {
  3250. name: "group with no buddies does not call UpsertItem",
  3251. me: newTestSession("me"),
  3252. givenCmd: []byte("toc2_new_buddies {g:EmptyGroup\n}"),
  3253. wantMsg: nil,
  3254. mockParams: mockParams{
  3255. feedBagParams: feedBagParams{
  3256. feedbagParams: feedbagParams{
  3257. {
  3258. screenName: state.NewIdentScreenName("me"),
  3259. results: []wire.FeedbagItem{
  3260. {
  3261. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3262. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3263. },
  3264. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3265. },
  3266. err: nil,
  3267. },
  3268. },
  3269. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  3270. },
  3271. },
  3272. },
  3273. {
  3274. name: "FeedbagManager.Feedbag error returns internal error",
  3275. me: newTestSession("me"),
  3276. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:alice\n}"),
  3277. wantMsg: []string{cmdInternalSvcErr},
  3278. mockParams: mockParams{
  3279. feedBagParams: feedBagParams{
  3280. feedbagParams: feedbagParams{
  3281. {
  3282. screenName: state.NewIdentScreenName("me"),
  3283. results: nil,
  3284. err: errors.New("EOF"),
  3285. },
  3286. },
  3287. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  3288. },
  3289. },
  3290. },
  3291. {
  3292. name: "FeedbagService.UpsertItem error on first insert returns internal error",
  3293. me: newTestSession("me"),
  3294. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:alice\n}"),
  3295. wantMsg: []string{cmdInternalSvcErr},
  3296. mockParams: mockParams{
  3297. feedBagParams: feedBagParams{
  3298. feedbagParams: feedbagParams{
  3299. {
  3300. screenName: state.NewIdentScreenName("me"),
  3301. results: []wire.FeedbagItem{
  3302. {
  3303. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3304. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
  3305. },
  3306. {
  3307. Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3308. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
  3309. },
  3310. },
  3311. err: nil,
  3312. },
  3313. },
  3314. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3315. {
  3316. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3317. items: []wire.FeedbagItem{
  3318. {
  3319. Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3320. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
  3321. },
  3322. },
  3323. msg: nil, err: nil,
  3324. },
  3325. {
  3326. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3327. items: []wire.FeedbagItem{
  3328. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  3329. },
  3330. msg: nil, err: errors.New("EOF"),
  3331. },
  3332. },
  3333. },
  3334. },
  3335. },
  3336. {
  3337. name: "buddy already in group skips and adds only new buddy",
  3338. me: newTestSession("me"),
  3339. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\n}"),
  3340. wantMsg: []string{"NEW_BUDDY_REPLY2:mk6i:added"},
  3341. mockParams: mockParams{
  3342. feedBagParams: feedBagParams{
  3343. feedbagParams: feedbagParams{
  3344. {
  3345. screenName: state.NewIdentScreenName("me"),
  3346. results: []wire.FeedbagItem{
  3347. {
  3348. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3349. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3350. },
  3351. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3352. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
  3353. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
  3354. },
  3355. err: nil,
  3356. },
  3357. },
  3358. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3359. {
  3360. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3361. items: []wire.FeedbagItem{
  3362. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
  3363. },
  3364. msg: nil, err: nil,
  3365. },
  3366. {
  3367. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3368. items: []wire.FeedbagItem{
  3369. {
  3370. Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3371. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}},
  3372. },
  3373. },
  3374. msg: nil, err: nil,
  3375. },
  3376. },
  3377. },
  3378. },
  3379. },
  3380. {
  3381. name: "all buddies already in group returns no replies and no UpsertItem",
  3382. me: newTestSession("me"),
  3383. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\n}"),
  3384. wantMsg: nil,
  3385. mockParams: mockParams{
  3386. feedBagParams: feedBagParams{
  3387. feedbagParams: feedbagParams{
  3388. {
  3389. screenName: state.NewIdentScreenName("me"),
  3390. results: []wire.FeedbagItem{
  3391. {
  3392. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3393. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3394. },
  3395. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3396. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}}},
  3397. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
  3398. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
  3399. },
  3400. err: nil,
  3401. },
  3402. },
  3403. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  3404. },
  3405. },
  3406. },
  3407. {
  3408. name: "single buddy in new group",
  3409. me: newTestSession("me"),
  3410. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:alice\n}"),
  3411. wantMsg: []string{"NEW_BUDDY_REPLY2:alice:added"},
  3412. mockParams: mockParams{
  3413. feedBagParams: feedBagParams{
  3414. feedbagParams: feedbagParams{
  3415. {
  3416. screenName: state.NewIdentScreenName("me"),
  3417. results: []wire.FeedbagItem{},
  3418. err: nil,
  3419. },
  3420. },
  3421. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3422. {
  3423. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3424. items: []wire.FeedbagItem{
  3425. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  3426. },
  3427. msg: nil, err: nil,
  3428. },
  3429. {
  3430. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3431. items: []wire.FeedbagItem{
  3432. {
  3433. Name: "Buddies", GroupID: 1, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3434. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2})}},
  3435. },
  3436. },
  3437. msg: nil, err: nil,
  3438. },
  3439. {
  3440. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3441. items: []wire.FeedbagItem{
  3442. {
  3443. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3444. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
  3445. },
  3446. },
  3447. msg: nil, err: nil,
  3448. },
  3449. },
  3450. },
  3451. },
  3452. },
  3453. {
  3454. name: "buddy with alias and note",
  3455. me: newTestSession("me"),
  3456. givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:bob:Bob Smith:::::Friend from work\n}"),
  3457. wantMsg: []string{"NEW_BUDDY_REPLY2:bob:added"},
  3458. mockParams: mockParams{
  3459. feedBagParams: feedBagParams{
  3460. feedbagParams: feedbagParams{
  3461. {
  3462. screenName: state.NewIdentScreenName("me"),
  3463. results: []wire.FeedbagItem{
  3464. {
  3465. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3466. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
  3467. },
  3468. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3469. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}}},
  3470. },
  3471. err: nil,
  3472. },
  3473. },
  3474. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3475. {
  3476. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3477. items: []wire.FeedbagItem{
  3478. {
  3479. ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob",
  3480. TLVLBlock: wire.TLVLBlock{
  3481. TLVList: wire.TLVList{
  3482. wire.NewTLVBE(wire.FeedbagAttributesAlias, "Bob Smith"),
  3483. wire.NewTLVBE(wire.FeedbagAttributesNote, "Friend from work"),
  3484. },
  3485. },
  3486. },
  3487. },
  3488. msg: nil, err: nil,
  3489. },
  3490. {
  3491. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  3492. items: []wire.FeedbagItem{
  3493. {
  3494. Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3495. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
  3496. },
  3497. },
  3498. msg: nil, err: nil,
  3499. },
  3500. },
  3501. },
  3502. },
  3503. },
  3504. }
  3505. for _, tc := range cases {
  3506. t.Run(tc.name, func(t *testing.T) {
  3507. ctx := context.Background()
  3508. fbMgr := newMockFeedbagManager(t)
  3509. for _, params := range tc.mockParams.feedbagParams {
  3510. fbMgr.EXPECT().
  3511. Feedbag(ctx, params.screenName).
  3512. Return(params.results, params.err)
  3513. }
  3514. fbSvc := newMockFeedbagService(t)
  3515. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  3516. fbSvc.EXPECT().
  3517. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  3518. Return(params.msg, params.err)
  3519. }
  3520. // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
  3521. var randCall int
  3522. randIntn := func(n int) int {
  3523. randCall++
  3524. return randCall
  3525. }
  3526. svc := OSCARProxy{
  3527. Logger: slog.Default(),
  3528. FeedbagManager: fbMgr,
  3529. FeedbagService: fbSvc,
  3530. RandIntn: randIntn,
  3531. }
  3532. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  3533. assert.Equal(t, tc.wantMsg, msg)
  3534. })
  3535. }
  3536. }
  3537. func TestOSCARProxy_RecvClientCmd_NewGroup(t *testing.T) {
  3538. cases := []struct {
  3539. name string
  3540. me *state.SessionInstance
  3541. givenCmd []byte
  3542. wantMsg []string
  3543. mockParams mockParams
  3544. }{
  3545. {
  3546. name: "add new group when feedbag has root and existing groups",
  3547. me: newTestSession("me"),
  3548. givenCmd: []byte("toc2_new_group Family"),
  3549. wantMsg: []string{},
  3550. mockParams: mockParams{
  3551. feedBagParams: feedBagParams{
  3552. feedbagParams: feedbagParams{
  3553. {
  3554. screenName: state.NewIdentScreenName("me"),
  3555. results: []wire.FeedbagItem{
  3556. {
  3557. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3558. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3559. },
  3560. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3561. },
  3562. err: nil,
  3563. },
  3564. },
  3565. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3566. {
  3567. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3568. items: []wire.FeedbagItem{
  3569. {
  3570. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3571. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 1})}},
  3572. },
  3573. {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Family"},
  3574. },
  3575. msg: nil, err: nil,
  3576. },
  3577. },
  3578. },
  3579. },
  3580. },
  3581. {
  3582. name: "add new group when feedbag is empty creates root and group",
  3583. me: newTestSession("me"),
  3584. givenCmd: []byte("toc2_new_group Buddies"),
  3585. wantMsg: []string{},
  3586. mockParams: mockParams{
  3587. feedBagParams: feedBagParams{
  3588. feedbagParams: feedbagParams{
  3589. {
  3590. screenName: state.NewIdentScreenName("me"),
  3591. results: []wire.FeedbagItem{},
  3592. err: nil,
  3593. },
  3594. },
  3595. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3596. {
  3597. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3598. items: []wire.FeedbagItem{
  3599. {
  3600. ClassID: wire.FeedbagClassIdGroup,
  3601. GroupID: 0,
  3602. Name: "",
  3603. TLVLBlock: wire.TLVLBlock{
  3604. TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})},
  3605. },
  3606. },
  3607. {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Buddies"},
  3608. },
  3609. msg: nil, err: nil,
  3610. },
  3611. },
  3612. },
  3613. },
  3614. },
  3615. {
  3616. name: "group already exists returns success idempotently",
  3617. me: newTestSession("me"),
  3618. givenCmd: []byte("toc2_new_group Buddies"),
  3619. wantMsg: []string{},
  3620. mockParams: mockParams{
  3621. feedBagParams: feedBagParams{
  3622. feedbagParams: feedbagParams{
  3623. {
  3624. screenName: state.NewIdentScreenName("me"),
  3625. results: []wire.FeedbagItem{
  3626. {
  3627. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3628. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3629. },
  3630. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3631. },
  3632. err: nil,
  3633. },
  3634. },
  3635. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  3636. },
  3637. },
  3638. },
  3639. {
  3640. name: "empty group name returns error",
  3641. me: newTestSession("me"),
  3642. givenCmd: []byte("toc2_new_group"),
  3643. wantMsg: []string{cmdInternalSvcErr},
  3644. mockParams: mockParams{
  3645. feedBagParams: feedBagParams{
  3646. feedbagParams: feedbagParams{},
  3647. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  3648. },
  3649. },
  3650. },
  3651. {
  3652. name: "FeedbagManager.Feedbag error returns error",
  3653. me: newTestSession("me"),
  3654. givenCmd: []byte("toc2_new_group Family"),
  3655. wantMsg: []string{cmdInternalSvcErr},
  3656. mockParams: mockParams{
  3657. feedBagParams: feedBagParams{
  3658. feedbagParams: feedbagParams{
  3659. {
  3660. screenName: state.NewIdentScreenName("me"),
  3661. results: nil,
  3662. err: assert.AnError,
  3663. },
  3664. },
  3665. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  3666. },
  3667. },
  3668. },
  3669. {
  3670. name: "FeedbagService.UpsertItem error returns error",
  3671. me: newTestSession("me"),
  3672. givenCmd: []byte("toc2_new_group Family"),
  3673. wantMsg: []string{cmdInternalSvcErr},
  3674. mockParams: mockParams{
  3675. feedBagParams: feedBagParams{
  3676. feedbagParams: feedbagParams{
  3677. {
  3678. screenName: state.NewIdentScreenName("me"),
  3679. results: []wire.FeedbagItem{
  3680. {
  3681. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3682. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3683. },
  3684. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3685. },
  3686. err: nil,
  3687. },
  3688. },
  3689. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3690. {
  3691. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3692. items: []wire.FeedbagItem{
  3693. {
  3694. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3695. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 1})}},
  3696. },
  3697. {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Family"},
  3698. },
  3699. msg: nil, err: assert.AnError,
  3700. },
  3701. },
  3702. },
  3703. },
  3704. },
  3705. }
  3706. for _, tc := range cases {
  3707. t.Run(tc.name, func(t *testing.T) {
  3708. ctx := context.Background()
  3709. fbMgr := newMockFeedbagManager(t)
  3710. for _, params := range tc.mockParams.feedbagParams {
  3711. fbMgr.EXPECT().
  3712. Feedbag(ctx, params.screenName).
  3713. Return(params.results, params.err)
  3714. }
  3715. fbSvc := newMockFeedbagService(t)
  3716. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  3717. fbSvc.EXPECT().
  3718. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  3719. Return(params.msg, params.err)
  3720. }
  3721. var randCall int
  3722. randIntn := func(n int) int {
  3723. randCall++
  3724. return randCall
  3725. }
  3726. svc := OSCARProxy{
  3727. Logger: slog.Default(),
  3728. FeedbagManager: fbMgr,
  3729. FeedbagService: fbSvc,
  3730. RandIntn: randIntn,
  3731. }
  3732. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  3733. assert.Equal(t, tc.wantMsg, msg)
  3734. })
  3735. }
  3736. }
  3737. func TestOSCARProxy_RecvClientCmd_DelGroup(t *testing.T) {
  3738. cases := []struct {
  3739. name string
  3740. me *state.SessionInstance
  3741. givenCmd []byte
  3742. wantMsg []string
  3743. mockParams mockParams
  3744. }{
  3745. {
  3746. name: "delete existing group",
  3747. me: newTestSession("me"),
  3748. givenCmd: []byte("toc2_del_group Family"),
  3749. wantMsg: []string{},
  3750. mockParams: mockParams{
  3751. feedBagParams: feedBagParams{
  3752. feedbagParams: feedbagParams{
  3753. {
  3754. screenName: state.NewIdentScreenName("me"),
  3755. results: []wire.FeedbagItem{
  3756. {
  3757. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3758. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 29709})}},
  3759. },
  3760. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3761. TLVLBlock: wire.TLVLBlock{
  3762. TLVList: wire.TLVList{
  3763. wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1}),
  3764. },
  3765. },
  3766. },
  3767. {Name: "Jane", GroupID: 17724, ItemID: 1, ClassID: wire.FeedbagClassIdBuddy},
  3768. {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3769. TLVLBlock: wire.TLVLBlock{
  3770. TLVList: wire.TLVList{
  3771. wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3}),
  3772. },
  3773. },
  3774. },
  3775. {Name: "Joe", GroupID: 29709, ItemID: 2, ClassID: wire.FeedbagClassIdBuddy},
  3776. {Name: "Fred", GroupID: 29709, ItemID: 3, ClassID: wire.FeedbagClassIdBuddy},
  3777. },
  3778. err: nil,
  3779. },
  3780. },
  3781. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  3782. {
  3783. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  3784. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  3785. Items: []wire.FeedbagItem{
  3786. {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3787. TLVLBlock: wire.TLVLBlock{
  3788. TLVList: wire.TLVList{
  3789. wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3}),
  3790. },
  3791. },
  3792. },
  3793. {Name: "Joe", GroupID: 29709, ItemID: 2, ClassID: wire.FeedbagClassIdBuddy},
  3794. {Name: "Fred", GroupID: 29709, ItemID: 3, ClassID: wire.FeedbagClassIdBuddy},
  3795. },
  3796. },
  3797. msg: nil, err: nil,
  3798. },
  3799. },
  3800. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3801. {
  3802. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3803. items: []wire.FeedbagItem{
  3804. {
  3805. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3806. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3807. },
  3808. },
  3809. msg: nil, err: nil,
  3810. },
  3811. },
  3812. },
  3813. },
  3814. },
  3815. {
  3816. name: "empty group name returns error",
  3817. me: newTestSession("me"),
  3818. givenCmd: []byte("toc2_del_group"),
  3819. wantMsg: []string{cmdInternalSvcErr},
  3820. mockParams: mockParams{
  3821. feedBagParams: feedBagParams{
  3822. feedbagParams: feedbagParams{},
  3823. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  3824. },
  3825. },
  3826. },
  3827. {
  3828. name: "group not found returns error",
  3829. me: newTestSession("me"),
  3830. givenCmd: []byte("toc2_del_group Nonexistent"),
  3831. wantMsg: []string{cmdInternalSvcErr},
  3832. mockParams: mockParams{
  3833. feedBagParams: feedBagParams{
  3834. feedbagParams: feedbagParams{
  3835. {
  3836. screenName: state.NewIdentScreenName("me"),
  3837. results: []wire.FeedbagItem{
  3838. {
  3839. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3840. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
  3841. },
  3842. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3843. },
  3844. err: nil,
  3845. },
  3846. },
  3847. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  3848. },
  3849. },
  3850. },
  3851. {
  3852. name: "FeedbagManager.Feedbag error returns error",
  3853. me: newTestSession("me"),
  3854. givenCmd: []byte("toc2_del_group Family"),
  3855. wantMsg: []string{cmdInternalSvcErr},
  3856. mockParams: mockParams{
  3857. feedBagParams: feedBagParams{
  3858. feedbagParams: feedbagParams{
  3859. {
  3860. screenName: state.NewIdentScreenName("me"),
  3861. results: nil,
  3862. err: assert.AnError,
  3863. },
  3864. },
  3865. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  3866. },
  3867. },
  3868. },
  3869. {
  3870. name: "FeedbagService.DeleteItem error returns error",
  3871. me: newTestSession("me"),
  3872. givenCmd: []byte("toc2_del_group Family"),
  3873. wantMsg: []string{cmdInternalSvcErr},
  3874. mockParams: mockParams{
  3875. feedBagParams: feedBagParams{
  3876. feedbagParams: feedbagParams{
  3877. {
  3878. screenName: state.NewIdentScreenName("me"),
  3879. results: []wire.FeedbagItem{
  3880. {
  3881. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  3882. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 29709})}},
  3883. },
  3884. {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3885. {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3886. },
  3887. err: nil,
  3888. },
  3889. },
  3890. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  3891. {
  3892. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  3893. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  3894. Items: []wire.FeedbagItem{
  3895. {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  3896. },
  3897. },
  3898. msg: nil, err: assert.AnError,
  3899. },
  3900. },
  3901. },
  3902. },
  3903. },
  3904. }
  3905. for _, tc := range cases {
  3906. t.Run(tc.name, func(t *testing.T) {
  3907. ctx := context.Background()
  3908. fbMgr := newMockFeedbagManager(t)
  3909. for _, params := range tc.mockParams.feedbagParams {
  3910. fbMgr.EXPECT().
  3911. Feedbag(ctx, params.screenName).
  3912. Return(params.results, params.err)
  3913. }
  3914. fbSvc := newMockFeedbagService(t)
  3915. for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
  3916. fbSvc.EXPECT().
  3917. DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
  3918. Return(params.msg, params.err)
  3919. }
  3920. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  3921. fbSvc.EXPECT().
  3922. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  3923. Return(params.msg, params.err)
  3924. }
  3925. svc := OSCARProxy{
  3926. Logger: slog.Default(),
  3927. FeedbagManager: fbMgr,
  3928. FeedbagService: fbSvc,
  3929. }
  3930. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  3931. assert.Equal(t, tc.wantMsg, msg)
  3932. })
  3933. }
  3934. }
  3935. func TestOSCARProxy_RecvClientCmd_SetPDMode(t *testing.T) {
  3936. cases := []struct {
  3937. name string
  3938. me *state.SessionInstance
  3939. givenCmd []byte
  3940. wantMsg []string
  3941. mockParams mockParams
  3942. }{
  3943. {
  3944. name: "empty feedbag creates new Pdinfo with mode 4",
  3945. me: newTestSession("me"),
  3946. givenCmd: []byte("toc2_set_pdmode 4"),
  3947. wantMsg: []string{},
  3948. mockParams: mockParams{
  3949. feedBagParams: feedBagParams{
  3950. feedbagParams: feedbagParams{
  3951. {
  3952. screenName: state.NewIdentScreenName("me"),
  3953. results: []wire.FeedbagItem{},
  3954. err: nil,
  3955. },
  3956. },
  3957. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  3958. {
  3959. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  3960. items: []wire.FeedbagItem{
  3961. {
  3962. ClassID: wire.FeedbagClassIdPdinfo,
  3963. GroupID: 0,
  3964. ItemID: 1,
  3965. TLVLBlock: wire.TLVLBlock{
  3966. TLVList: wire.TLVList{
  3967. wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(4)),
  3968. },
  3969. },
  3970. },
  3971. },
  3972. msg: nil, err: nil,
  3973. },
  3974. },
  3975. },
  3976. },
  3977. },
  3978. {
  3979. name: "pdinfo already exists but does not have FeedbagAttributesPdMode",
  3980. me: newTestSession("me"),
  3981. givenCmd: []byte("toc2_set_pdmode 2"),
  3982. wantMsg: []string{},
  3983. mockParams: mockParams{
  3984. feedBagParams: feedBagParams{
  3985. feedbagParams: feedbagParams{
  3986. {
  3987. screenName: state.NewIdentScreenName("me"),
  3988. results: []wire.FeedbagItem{
  3989. {
  3990. ClassID: wire.FeedbagClassIdPdinfo,
  3991. GroupID: 0,
  3992. ItemID: 99,
  3993. TLVLBlock: wire.TLVLBlock{
  3994. TLVList: wire.TLVList{},
  3995. },
  3996. },
  3997. },
  3998. err: nil,
  3999. },
  4000. },
  4001. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4002. {
  4003. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4004. items: []wire.FeedbagItem{
  4005. {
  4006. ClassID: wire.FeedbagClassIdPdinfo,
  4007. GroupID: 0,
  4008. ItemID: 99,
  4009. TLVLBlock: wire.TLVLBlock{
  4010. TLVList: wire.TLVList{
  4011. wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(2)),
  4012. },
  4013. },
  4014. },
  4015. },
  4016. msg: nil, err: nil,
  4017. },
  4018. },
  4019. },
  4020. },
  4021. },
  4022. {
  4023. name: "pdinfo already exists and already has FeedbagAttributesPdMode with same mode",
  4024. me: newTestSession("me"),
  4025. givenCmd: []byte("toc2_set_pdmode 3"),
  4026. wantMsg: []string{},
  4027. mockParams: mockParams{
  4028. feedBagParams: feedBagParams{
  4029. feedbagParams: feedbagParams{
  4030. {
  4031. screenName: state.NewIdentScreenName("me"),
  4032. results: []wire.FeedbagItem{
  4033. {
  4034. ClassID: wire.FeedbagClassIdPdinfo,
  4035. GroupID: 0,
  4036. ItemID: 99,
  4037. TLVLBlock: wire.TLVLBlock{
  4038. TLVList: wire.TLVList{
  4039. wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(3)),
  4040. },
  4041. },
  4042. },
  4043. },
  4044. err: nil,
  4045. },
  4046. },
  4047. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4048. },
  4049. },
  4050. },
  4051. {
  4052. name: "pdinfo already exists and already has FeedbagAttributesPdMode with different mode",
  4053. me: newTestSession("me"),
  4054. givenCmd: []byte("toc2_set_pdmode 3"),
  4055. wantMsg: []string{},
  4056. mockParams: mockParams{
  4057. feedBagParams: feedBagParams{
  4058. feedbagParams: feedbagParams{
  4059. {
  4060. screenName: state.NewIdentScreenName("me"),
  4061. results: []wire.FeedbagItem{
  4062. {
  4063. ClassID: wire.FeedbagClassIdPdinfo,
  4064. GroupID: 0,
  4065. ItemID: 99,
  4066. TLVLBlock: wire.TLVLBlock{
  4067. TLVList: wire.TLVList{
  4068. wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(1)),
  4069. },
  4070. },
  4071. },
  4072. },
  4073. err: nil,
  4074. },
  4075. },
  4076. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4077. {
  4078. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4079. items: []wire.FeedbagItem{
  4080. {
  4081. ClassID: wire.FeedbagClassIdPdinfo,
  4082. GroupID: 0,
  4083. ItemID: 99,
  4084. TLVLBlock: wire.TLVLBlock{
  4085. TLVList: wire.TLVList{
  4086. wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(3)),
  4087. },
  4088. },
  4089. },
  4090. },
  4091. msg: nil, err: nil,
  4092. },
  4093. },
  4094. },
  4095. },
  4096. },
  4097. }
  4098. for _, tc := range cases {
  4099. t.Run(tc.name, func(t *testing.T) {
  4100. ctx := context.Background()
  4101. fbMgr := newMockFeedbagManager(t)
  4102. for _, params := range tc.mockParams.feedbagParams {
  4103. fbMgr.EXPECT().
  4104. Feedbag(ctx, params.screenName).
  4105. Return(params.results, params.err)
  4106. }
  4107. fbSvc := newMockFeedbagService(t)
  4108. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  4109. fbSvc.EXPECT().
  4110. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  4111. Return(params.msg, params.err)
  4112. }
  4113. var randCall int
  4114. randIntn := func(n int) int {
  4115. randCall++
  4116. return randCall
  4117. }
  4118. svc := OSCARProxy{
  4119. Logger: slog.Default(),
  4120. FeedbagManager: fbMgr,
  4121. FeedbagService: fbSvc,
  4122. RandIntn: randIntn,
  4123. }
  4124. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  4125. assert.Equal(t, tc.wantMsg, msg)
  4126. })
  4127. }
  4128. }
  4129. func TestOSCARProxy_RecvClientCmd_AddPermit2(t *testing.T) {
  4130. cases := []struct {
  4131. name string
  4132. me *state.SessionInstance
  4133. givenCmd []byte
  4134. wantMsg []string
  4135. mockParams mockParams
  4136. }{
  4137. {
  4138. name: "no screennames provided returns internal error",
  4139. me: newTestSession("me"),
  4140. givenCmd: []byte("toc2_add_permit"),
  4141. wantMsg: []string{cmdInternalSvcErr},
  4142. mockParams: mockParams{},
  4143. },
  4144. {
  4145. name: "FeedbagManager.Feedbag error returns internal error",
  4146. me: newTestSession("me"),
  4147. givenCmd: []byte("toc2_add_permit alice"),
  4148. wantMsg: []string{cmdInternalSvcErr},
  4149. mockParams: mockParams{
  4150. feedBagParams: feedBagParams{
  4151. feedbagParams: feedbagParams{
  4152. {
  4153. screenName: state.NewIdentScreenName("me"),
  4154. results: nil,
  4155. err: errors.New("EOF"),
  4156. },
  4157. },
  4158. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4159. },
  4160. },
  4161. },
  4162. {
  4163. name: "FeedbagService.UpsertItem error returns internal error",
  4164. me: newTestSession("me"),
  4165. givenCmd: []byte("toc2_add_permit alice"),
  4166. wantMsg: []string{cmdInternalSvcErr},
  4167. mockParams: mockParams{
  4168. feedBagParams: feedBagParams{
  4169. feedbagParams: feedbagParams{
  4170. {
  4171. screenName: state.NewIdentScreenName("me"),
  4172. results: []wire.FeedbagItem{},
  4173. err: nil,
  4174. },
  4175. },
  4176. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4177. {
  4178. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4179. items: []wire.FeedbagItem{
  4180. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4181. },
  4182. msg: nil, err: errors.New("EOF"),
  4183. },
  4184. },
  4185. },
  4186. },
  4187. },
  4188. {
  4189. name: "add permits to empty feedbag",
  4190. me: newTestSession("me"),
  4191. givenCmd: []byte("toc2_add_permit alice bob"),
  4192. wantMsg: []string{},
  4193. mockParams: mockParams{
  4194. feedBagParams: feedBagParams{
  4195. feedbagParams: feedbagParams{
  4196. {
  4197. screenName: state.NewIdentScreenName("me"),
  4198. results: []wire.FeedbagItem{},
  4199. err: nil,
  4200. },
  4201. },
  4202. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4203. {
  4204. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4205. items: []wire.FeedbagItem{
  4206. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4207. {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4208. },
  4209. msg: nil, err: nil,
  4210. },
  4211. },
  4212. },
  4213. },
  4214. },
  4215. {
  4216. name: "skip existing permit, add only new",
  4217. me: newTestSession("me"),
  4218. givenCmd: []byte("toc2_add_permit alice bob"),
  4219. wantMsg: []string{},
  4220. mockParams: mockParams{
  4221. feedBagParams: feedBagParams{
  4222. feedbagParams: feedbagParams{
  4223. {
  4224. screenName: state.NewIdentScreenName("me"),
  4225. results: []wire.FeedbagItem{
  4226. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4227. },
  4228. err: nil,
  4229. },
  4230. },
  4231. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4232. {
  4233. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4234. items: []wire.FeedbagItem{
  4235. {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4236. },
  4237. msg: nil, err: nil,
  4238. },
  4239. },
  4240. },
  4241. },
  4242. },
  4243. {
  4244. name: "no-op when permit already in feedbag",
  4245. me: newTestSession("me"),
  4246. givenCmd: []byte("toc2_add_permit alice"),
  4247. wantMsg: []string{},
  4248. mockParams: mockParams{
  4249. feedBagParams: feedBagParams{
  4250. feedbagParams: feedbagParams{
  4251. {
  4252. screenName: state.NewIdentScreenName("me"),
  4253. results: []wire.FeedbagItem{
  4254. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4255. },
  4256. err: nil,
  4257. },
  4258. },
  4259. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4260. },
  4261. },
  4262. },
  4263. }
  4264. for _, tc := range cases {
  4265. t.Run(tc.name, func(t *testing.T) {
  4266. ctx := context.Background()
  4267. fbMgr := newMockFeedbagManager(t)
  4268. for _, params := range tc.mockParams.feedbagParams {
  4269. fbMgr.EXPECT().
  4270. Feedbag(ctx, params.screenName).
  4271. Return(params.results, params.err)
  4272. }
  4273. fbSvc := newMockFeedbagService(t)
  4274. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  4275. fbSvc.EXPECT().
  4276. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  4277. Return(params.msg, params.err)
  4278. }
  4279. var randCall int
  4280. randIntn := func(n int) int {
  4281. randCall++
  4282. return randCall
  4283. }
  4284. svc := OSCARProxy{
  4285. Logger: slog.Default(),
  4286. FeedbagManager: fbMgr,
  4287. FeedbagService: fbSvc,
  4288. RandIntn: randIntn,
  4289. }
  4290. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  4291. assert.Equal(t, tc.wantMsg, msg)
  4292. })
  4293. }
  4294. }
  4295. func TestOSCARProxy_RecvClientCmd_AddDeny2(t *testing.T) {
  4296. cases := []struct {
  4297. name string
  4298. me *state.SessionInstance
  4299. givenCmd []byte
  4300. wantMsg []string
  4301. mockParams mockParams
  4302. }{
  4303. {
  4304. name: "no screennames provided returns internal error",
  4305. me: newTestSession("me"),
  4306. givenCmd: []byte("toc2_add_deny"),
  4307. wantMsg: []string{cmdInternalSvcErr},
  4308. mockParams: mockParams{},
  4309. },
  4310. {
  4311. name: "FeedbagManager.Feedbag error returns internal error",
  4312. me: newTestSession("me"),
  4313. givenCmd: []byte("toc2_add_deny alice"),
  4314. wantMsg: []string{cmdInternalSvcErr},
  4315. mockParams: mockParams{
  4316. feedBagParams: feedBagParams{
  4317. feedbagParams: feedbagParams{
  4318. {
  4319. screenName: state.NewIdentScreenName("me"),
  4320. results: nil,
  4321. err: errors.New("EOF"),
  4322. },
  4323. },
  4324. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4325. },
  4326. },
  4327. },
  4328. {
  4329. name: "FeedbagService.UpsertItem error returns internal error",
  4330. me: newTestSession("me"),
  4331. givenCmd: []byte("toc2_add_deny alice"),
  4332. wantMsg: []string{cmdInternalSvcErr},
  4333. mockParams: mockParams{
  4334. feedBagParams: feedBagParams{
  4335. feedbagParams: feedbagParams{
  4336. {
  4337. screenName: state.NewIdentScreenName("me"),
  4338. results: []wire.FeedbagItem{},
  4339. err: nil,
  4340. },
  4341. },
  4342. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4343. {
  4344. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4345. items: []wire.FeedbagItem{
  4346. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4347. },
  4348. msg: nil, err: errors.New("EOF"),
  4349. },
  4350. },
  4351. },
  4352. },
  4353. },
  4354. {
  4355. name: "add deny to empty feedbag",
  4356. me: newTestSession("me"),
  4357. givenCmd: []byte("toc2_add_deny alice"),
  4358. wantMsg: []string{},
  4359. mockParams: mockParams{
  4360. feedBagParams: feedBagParams{
  4361. feedbagParams: feedbagParams{
  4362. {
  4363. screenName: state.NewIdentScreenName("me"),
  4364. results: []wire.FeedbagItem{},
  4365. err: nil,
  4366. },
  4367. },
  4368. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4369. {
  4370. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4371. items: []wire.FeedbagItem{
  4372. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4373. },
  4374. msg: nil, err: nil,
  4375. },
  4376. },
  4377. },
  4378. },
  4379. },
  4380. {
  4381. name: "skip existing deny, add only new",
  4382. me: newTestSession("me"),
  4383. givenCmd: []byte("toc2_add_deny alice bob"),
  4384. wantMsg: []string{},
  4385. mockParams: mockParams{
  4386. feedBagParams: feedBagParams{
  4387. feedbagParams: feedbagParams{
  4388. {
  4389. screenName: state.NewIdentScreenName("me"),
  4390. results: []wire.FeedbagItem{
  4391. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4392. },
  4393. err: nil,
  4394. },
  4395. },
  4396. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4397. {
  4398. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
  4399. items: []wire.FeedbagItem{
  4400. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4401. },
  4402. msg: nil, err: nil,
  4403. },
  4404. },
  4405. },
  4406. },
  4407. },
  4408. {
  4409. name: "no-op when deny already in feedbag",
  4410. me: newTestSession("me"),
  4411. givenCmd: []byte("toc2_add_deny alice"),
  4412. wantMsg: []string{},
  4413. mockParams: mockParams{
  4414. feedBagParams: feedBagParams{
  4415. feedbagParams: feedbagParams{
  4416. {
  4417. screenName: state.NewIdentScreenName("me"),
  4418. results: []wire.FeedbagItem{
  4419. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4420. },
  4421. err: nil,
  4422. },
  4423. },
  4424. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4425. },
  4426. },
  4427. },
  4428. }
  4429. for _, tc := range cases {
  4430. t.Run(tc.name, func(t *testing.T) {
  4431. ctx := context.Background()
  4432. fbMgr := newMockFeedbagManager(t)
  4433. for _, params := range tc.mockParams.feedbagParams {
  4434. fbMgr.EXPECT().
  4435. Feedbag(ctx, params.screenName).
  4436. Return(params.results, params.err)
  4437. }
  4438. fbSvc := newMockFeedbagService(t)
  4439. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  4440. fbSvc.EXPECT().
  4441. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  4442. Return(params.msg, params.err)
  4443. }
  4444. var randCall int
  4445. randIntn := func(n int) int {
  4446. randCall++
  4447. return randCall
  4448. }
  4449. svc := OSCARProxy{
  4450. Logger: slog.Default(),
  4451. FeedbagManager: fbMgr,
  4452. FeedbagService: fbSvc,
  4453. RandIntn: randIntn,
  4454. }
  4455. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  4456. assert.Equal(t, tc.wantMsg, msg)
  4457. })
  4458. }
  4459. }
  4460. func TestOSCARProxy_RecvClientCmd_RemoveBuddy2(t *testing.T) {
  4461. cases := []struct {
  4462. name string
  4463. me *state.SessionInstance
  4464. givenCmd []byte
  4465. wantMsg []string
  4466. mockParams mockParams
  4467. }{
  4468. {
  4469. name: "removes buddy from group order TLV and upserts group",
  4470. me: newTestSession("me"),
  4471. givenCmd: []byte("toc2_remove_buddy friend2 Buddies"),
  4472. wantMsg: []string{},
  4473. mockParams: mockParams{
  4474. feedBagParams: feedBagParams{
  4475. feedbagParams: feedbagParams{
  4476. {
  4477. screenName: state.NewIdentScreenName("me"),
  4478. results: []wire.FeedbagItem{
  4479. {
  4480. Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  4481. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2, 3})}},
  4482. },
  4483. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend1", TLVLBlock: wire.TLVLBlock{}},
  4484. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend2", TLVLBlock: wire.TLVLBlock{}},
  4485. {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend3", TLVLBlock: wire.TLVLBlock{}},
  4486. },
  4487. err: nil,
  4488. },
  4489. },
  4490. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4491. {
  4492. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4493. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4494. Items: []wire.FeedbagItem{
  4495. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend2", TLVLBlock: wire.TLVLBlock{}},
  4496. },
  4497. },
  4498. msg: nil, err: nil,
  4499. },
  4500. },
  4501. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{
  4502. {
  4503. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
  4504. items: []wire.FeedbagItem{
  4505. {
  4506. Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  4507. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 3})}},
  4508. },
  4509. },
  4510. msg: nil, err: nil,
  4511. },
  4512. },
  4513. },
  4514. },
  4515. },
  4516. {
  4517. name: "parseArgs error returns internal error",
  4518. me: newTestSession("me"),
  4519. givenCmd: []byte("toc2_remove_buddy \"unclosed"),
  4520. wantMsg: []string{cmdInternalSvcErr},
  4521. mockParams: mockParams{},
  4522. },
  4523. {
  4524. name: "missing params returns internal error",
  4525. me: newTestSession("me"),
  4526. givenCmd: []byte("toc2_remove_buddy friend1"),
  4527. wantMsg: []string{cmdInternalSvcErr},
  4528. mockParams: mockParams{},
  4529. },
  4530. {
  4531. name: "group not found returns internal error",
  4532. me: newTestSession("me"),
  4533. givenCmd: []byte("toc2_remove_buddy friend1 NoSuchGroup"),
  4534. wantMsg: []string{cmdInternalSvcErr},
  4535. mockParams: mockParams{
  4536. feedBagParams: feedBagParams{
  4537. feedbagParams: feedbagParams{
  4538. {
  4539. screenName: state.NewIdentScreenName("me"),
  4540. results: []wire.FeedbagItem{
  4541. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
  4542. },
  4543. err: nil,
  4544. },
  4545. },
  4546. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4547. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4548. },
  4549. },
  4550. },
  4551. {
  4552. name: "FeedbagManager.Feedbag error returns internal error",
  4553. me: newTestSession("me"),
  4554. givenCmd: []byte("toc2_remove_buddy friend1 Buddies"),
  4555. wantMsg: []string{cmdInternalSvcErr},
  4556. mockParams: mockParams{
  4557. feedBagParams: feedBagParams{
  4558. feedbagParams: feedbagParams{
  4559. {
  4560. screenName: state.NewIdentScreenName("me"),
  4561. results: nil,
  4562. err: errors.New("EOF"),
  4563. },
  4564. },
  4565. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4566. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4567. },
  4568. },
  4569. },
  4570. {
  4571. name: "no-op when buddy not in group",
  4572. me: newTestSession("me"),
  4573. givenCmd: []byte("toc2_remove_buddy stranger Buddies"),
  4574. wantMsg: []string{},
  4575. mockParams: mockParams{
  4576. feedBagParams: feedBagParams{
  4577. feedbagParams: feedbagParams{
  4578. {
  4579. screenName: state.NewIdentScreenName("me"),
  4580. results: []wire.FeedbagItem{
  4581. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  4582. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
  4583. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend1", TLVLBlock: wire.TLVLBlock{}},
  4584. },
  4585. err: nil,
  4586. },
  4587. },
  4588. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4589. feedbagServiceUpsertItemParams: feedbagServiceUpsertItemParams{},
  4590. },
  4591. },
  4592. },
  4593. }
  4594. for _, tc := range cases {
  4595. t.Run(tc.name, func(t *testing.T) {
  4596. ctx := context.Background()
  4597. fbMgr := newMockFeedbagManager(t)
  4598. for _, params := range tc.mockParams.feedbagParams {
  4599. fbMgr.EXPECT().
  4600. Feedbag(ctx, params.screenName).
  4601. Return(params.results, params.err)
  4602. }
  4603. fbSvc := newMockFeedbagService(t)
  4604. for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
  4605. fbSvc.EXPECT().
  4606. DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
  4607. Return(params.msg, params.err)
  4608. }
  4609. for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
  4610. fbSvc.EXPECT().
  4611. UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
  4612. Return(params.msg, params.err)
  4613. }
  4614. svc := OSCARProxy{
  4615. Logger: slog.Default(),
  4616. FeedbagManager: fbMgr,
  4617. FeedbagService: fbSvc,
  4618. }
  4619. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  4620. assert.Equal(t, tc.wantMsg, msg)
  4621. })
  4622. }
  4623. }
  4624. func TestOSCARProxy_RecvClientCmd_RemovePermit2(t *testing.T) {
  4625. cases := []struct {
  4626. name string
  4627. me *state.SessionInstance
  4628. givenCmd []byte
  4629. wantMsg []string
  4630. mockParams mockParams
  4631. }{
  4632. {
  4633. name: "no screennames provided returns internal error",
  4634. me: newTestSession("me"),
  4635. givenCmd: []byte("toc2_remove_permit"),
  4636. wantMsg: []string{cmdInternalSvcErr},
  4637. mockParams: mockParams{},
  4638. },
  4639. {
  4640. name: "FeedbagManager.Feedbag error returns internal error",
  4641. me: newTestSession("me"),
  4642. givenCmd: []byte("toc2_remove_permit alice"),
  4643. wantMsg: []string{cmdInternalSvcErr},
  4644. mockParams: mockParams{
  4645. feedBagParams: feedBagParams{
  4646. feedbagParams: feedbagParams{
  4647. {
  4648. screenName: state.NewIdentScreenName("me"),
  4649. results: nil,
  4650. err: errors.New("EOF"),
  4651. },
  4652. },
  4653. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4654. },
  4655. },
  4656. },
  4657. {
  4658. name: "FeedbagService.DeleteItem error returns internal error",
  4659. me: newTestSession("me"),
  4660. givenCmd: []byte("toc2_remove_permit alice"),
  4661. wantMsg: []string{cmdInternalSvcErr},
  4662. mockParams: mockParams{
  4663. feedBagParams: feedBagParams{
  4664. feedbagParams: feedbagParams{
  4665. {
  4666. screenName: state.NewIdentScreenName("me"),
  4667. results: []wire.FeedbagItem{
  4668. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4669. },
  4670. err: nil,
  4671. },
  4672. },
  4673. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4674. {
  4675. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4676. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4677. Items: []wire.FeedbagItem{
  4678. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4679. },
  4680. },
  4681. msg: nil, err: errors.New("EOF"),
  4682. },
  4683. },
  4684. },
  4685. },
  4686. },
  4687. {
  4688. name: "remove permits from feedbag",
  4689. me: newTestSession("me"),
  4690. givenCmd: []byte("toc2_remove_permit alice bob"),
  4691. wantMsg: []string{},
  4692. mockParams: mockParams{
  4693. feedBagParams: feedBagParams{
  4694. feedbagParams: feedbagParams{
  4695. {
  4696. screenName: state.NewIdentScreenName("me"),
  4697. results: []wire.FeedbagItem{
  4698. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4699. {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4700. },
  4701. err: nil,
  4702. },
  4703. },
  4704. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4705. {
  4706. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4707. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4708. Items: []wire.FeedbagItem{
  4709. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4710. {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4711. },
  4712. },
  4713. msg: nil, err: nil,
  4714. },
  4715. },
  4716. },
  4717. },
  4718. },
  4719. {
  4720. name: "remove only existing permits",
  4721. me: newTestSession("me"),
  4722. givenCmd: []byte("toc2_remove_permit alice bob"),
  4723. wantMsg: []string{},
  4724. mockParams: mockParams{
  4725. feedBagParams: feedBagParams{
  4726. feedbagParams: feedbagParams{
  4727. {
  4728. screenName: state.NewIdentScreenName("me"),
  4729. results: []wire.FeedbagItem{
  4730. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4731. },
  4732. err: nil,
  4733. },
  4734. },
  4735. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4736. {
  4737. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4738. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4739. Items: []wire.FeedbagItem{
  4740. {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4741. },
  4742. },
  4743. msg: nil, err: nil,
  4744. },
  4745. },
  4746. },
  4747. },
  4748. },
  4749. {
  4750. name: "no-op when no permits in feedbag",
  4751. me: newTestSession("me"),
  4752. givenCmd: []byte("toc2_remove_permit alice"),
  4753. wantMsg: []string{},
  4754. mockParams: mockParams{
  4755. feedBagParams: feedBagParams{
  4756. feedbagParams: feedbagParams{
  4757. {
  4758. screenName: state.NewIdentScreenName("me"),
  4759. results: []wire.FeedbagItem{},
  4760. err: nil,
  4761. },
  4762. },
  4763. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4764. },
  4765. },
  4766. },
  4767. }
  4768. for _, tc := range cases {
  4769. t.Run(tc.name, func(t *testing.T) {
  4770. ctx := context.Background()
  4771. fbMgr := newMockFeedbagManager(t)
  4772. for _, params := range tc.mockParams.feedbagParams {
  4773. fbMgr.EXPECT().
  4774. Feedbag(ctx, params.screenName).
  4775. Return(params.results, params.err)
  4776. }
  4777. fbSvc := newMockFeedbagService(t)
  4778. for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
  4779. fbSvc.EXPECT().
  4780. DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
  4781. Return(params.msg, params.err)
  4782. }
  4783. svc := OSCARProxy{
  4784. Logger: slog.Default(),
  4785. FeedbagManager: fbMgr,
  4786. FeedbagService: fbSvc,
  4787. }
  4788. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  4789. assert.Equal(t, tc.wantMsg, msg)
  4790. })
  4791. }
  4792. }
  4793. func TestOSCARProxy_RecvClientCmd_RemoveDeny2(t *testing.T) {
  4794. cases := []struct {
  4795. name string
  4796. me *state.SessionInstance
  4797. givenCmd []byte
  4798. wantMsg []string
  4799. mockParams mockParams
  4800. }{
  4801. {
  4802. name: "no screennames provided returns internal error",
  4803. me: newTestSession("me"),
  4804. givenCmd: []byte("toc2_remove_deny"),
  4805. wantMsg: []string{cmdInternalSvcErr},
  4806. mockParams: mockParams{},
  4807. },
  4808. {
  4809. name: "FeedbagManager.Feedbag error returns internal error",
  4810. me: newTestSession("me"),
  4811. givenCmd: []byte("toc2_remove_deny alice"),
  4812. wantMsg: []string{cmdInternalSvcErr},
  4813. mockParams: mockParams{
  4814. feedBagParams: feedBagParams{
  4815. feedbagParams: feedbagParams{
  4816. {
  4817. screenName: state.NewIdentScreenName("me"),
  4818. results: nil,
  4819. err: errors.New("EOF"),
  4820. },
  4821. },
  4822. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4823. },
  4824. },
  4825. },
  4826. {
  4827. name: "FeedbagService.DeleteItem error returns internal error",
  4828. me: newTestSession("me"),
  4829. givenCmd: []byte("toc2_remove_deny alice"),
  4830. wantMsg: []string{cmdInternalSvcErr},
  4831. mockParams: mockParams{
  4832. feedBagParams: feedBagParams{
  4833. feedbagParams: feedbagParams{
  4834. {
  4835. screenName: state.NewIdentScreenName("me"),
  4836. results: []wire.FeedbagItem{
  4837. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4838. },
  4839. err: nil,
  4840. },
  4841. },
  4842. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4843. {
  4844. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4845. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4846. Items: []wire.FeedbagItem{
  4847. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4848. },
  4849. },
  4850. msg: nil, err: errors.New("EOF"),
  4851. },
  4852. },
  4853. },
  4854. },
  4855. },
  4856. {
  4857. name: "remove denies from feedbag",
  4858. me: newTestSession("me"),
  4859. givenCmd: []byte("toc2_remove_deny alice bob"),
  4860. wantMsg: []string{},
  4861. mockParams: mockParams{
  4862. feedBagParams: feedBagParams{
  4863. feedbagParams: feedbagParams{
  4864. {
  4865. screenName: state.NewIdentScreenName("me"),
  4866. results: []wire.FeedbagItem{
  4867. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4868. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4869. },
  4870. err: nil,
  4871. },
  4872. },
  4873. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4874. {
  4875. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4876. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4877. Items: []wire.FeedbagItem{
  4878. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4879. {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
  4880. },
  4881. },
  4882. msg: nil, err: nil,
  4883. },
  4884. },
  4885. },
  4886. },
  4887. },
  4888. {
  4889. name: "remove only existing denies",
  4890. me: newTestSession("me"),
  4891. givenCmd: []byte("toc2_remove_deny alice bob"),
  4892. wantMsg: []string{},
  4893. mockParams: mockParams{
  4894. feedBagParams: feedBagParams{
  4895. feedbagParams: feedbagParams{
  4896. {
  4897. screenName: state.NewIdentScreenName("me"),
  4898. results: []wire.FeedbagItem{
  4899. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4900. },
  4901. err: nil,
  4902. },
  4903. },
  4904. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{
  4905. {
  4906. frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
  4907. inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
  4908. Items: []wire.FeedbagItem{
  4909. {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
  4910. },
  4911. },
  4912. msg: nil, err: nil,
  4913. },
  4914. },
  4915. },
  4916. },
  4917. },
  4918. {
  4919. name: "no-op when no denies in feedbag",
  4920. me: newTestSession("me"),
  4921. givenCmd: []byte("toc2_remove_deny alice"),
  4922. wantMsg: []string{},
  4923. mockParams: mockParams{
  4924. feedBagParams: feedBagParams{
  4925. feedbagParams: feedbagParams{
  4926. {
  4927. screenName: state.NewIdentScreenName("me"),
  4928. results: []wire.FeedbagItem{},
  4929. err: nil,
  4930. },
  4931. },
  4932. feedbagServiceDeleteItemParams: feedbagServiceDeleteItemParams{},
  4933. },
  4934. },
  4935. },
  4936. }
  4937. for _, tc := range cases {
  4938. t.Run(tc.name, func(t *testing.T) {
  4939. ctx := context.Background()
  4940. fbMgr := newMockFeedbagManager(t)
  4941. for _, params := range tc.mockParams.feedbagParams {
  4942. fbMgr.EXPECT().
  4943. Feedbag(ctx, params.screenName).
  4944. Return(params.results, params.err)
  4945. }
  4946. fbSvc := newMockFeedbagService(t)
  4947. for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
  4948. fbSvc.EXPECT().
  4949. DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
  4950. Return(params.msg, params.err)
  4951. }
  4952. svc := OSCARProxy{
  4953. Logger: slog.Default(),
  4954. FeedbagManager: fbMgr,
  4955. FeedbagService: fbSvc,
  4956. }
  4957. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  4958. assert.Equal(t, tc.wantMsg, msg)
  4959. })
  4960. }
  4961. }
  4962. func TestOSCARProxy_RecvClientCmd_RvousAccept(t *testing.T) {
  4963. cases := []struct {
  4964. // name is the unit test name
  4965. name string
  4966. // me is the TOC user session
  4967. me *state.SessionInstance
  4968. // givenCmd is the TOC command
  4969. givenCmd []byte
  4970. // wantMsg is the expected TOC response
  4971. wantMsg []string
  4972. // mockParams is the list of params sent to mocks that satisfy this
  4973. // method's dependencies
  4974. mockParams mockParams
  4975. }{
  4976. {
  4977. name: "successfully send rendezvous request",
  4978. me: newTestSession("me"),
  4979. givenCmd: []byte(`toc_rvous_accept them aGFoYWhhaGE= 09461343-4C7F-11D1-8222-444553540000`),
  4980. mockParams: mockParams{
  4981. icbmParams: icbmParams{
  4982. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  4983. {
  4984. sender: state.NewIdentScreenName("me"),
  4985. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  4986. ChannelID: wire.ICBMChannelRendezvous,
  4987. ScreenName: "them",
  4988. TLVRestBlock: wire.TLVRestBlock{
  4989. TLVList: wire.TLVList{
  4990. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  4991. Type: wire.ICBMRdvMessageAccept,
  4992. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  4993. Capability: wire.CapFileTransfer,
  4994. }),
  4995. },
  4996. },
  4997. },
  4998. },
  4999. },
  5000. },
  5001. },
  5002. wantMsg: []string{},
  5003. },
  5004. {
  5005. name: "send rendezvous request, receive error from ICBM service",
  5006. me: newTestSession("me"),
  5007. givenCmd: []byte(`toc_rvous_accept them aGFoYWhhaGE= 09461343-4C7F-11D1-8222-444553540000`),
  5008. mockParams: mockParams{
  5009. icbmParams: icbmParams{
  5010. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5011. {
  5012. sender: state.NewIdentScreenName("me"),
  5013. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5014. ChannelID: wire.ICBMChannelRendezvous,
  5015. ScreenName: "them",
  5016. TLVRestBlock: wire.TLVRestBlock{
  5017. TLVList: wire.TLVList{
  5018. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  5019. Type: wire.ICBMRdvMessageAccept,
  5020. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  5021. Capability: wire.CapFileTransfer,
  5022. }),
  5023. },
  5024. },
  5025. },
  5026. err: io.EOF,
  5027. },
  5028. },
  5029. },
  5030. },
  5031. wantMsg: []string{cmdInternalSvcErr},
  5032. },
  5033. {
  5034. name: "bad command",
  5035. me: newTestSession("me"),
  5036. givenCmd: []byte(`toc_rvous_accept`),
  5037. wantMsg: []string{cmdInternalSvcErr},
  5038. },
  5039. }
  5040. for _, tc := range cases {
  5041. t.Run(tc.name, func(t *testing.T) {
  5042. ctx := context.Background()
  5043. icbmSvc := newMockICBMService(t)
  5044. for _, params := range tc.mockParams.channelMsgToHostParamsICBM {
  5045. icbmSvc.EXPECT().
  5046. ChannelMsgToHost(ctx, matchSession(params.sender), params.inFrame, params.inBody).
  5047. Return(params.result, params.err)
  5048. }
  5049. svc := OSCARProxy{
  5050. Logger: slog.Default(),
  5051. ICBMService: icbmSvc,
  5052. }
  5053. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5054. assert.Equal(t, tc.wantMsg, msg)
  5055. })
  5056. }
  5057. }
  5058. func TestOSCARProxy_RecvClientCmd_RvousCancel(t *testing.T) {
  5059. cases := []struct {
  5060. // name is the unit test name
  5061. name string
  5062. // me is the TOC user session
  5063. me *state.SessionInstance
  5064. // givenCmd is the TOC command
  5065. givenCmd []byte
  5066. // wantMsg is the expected TOC response
  5067. wantMsg []string
  5068. // mockParams is the list of params sent to mocks that satisfy this
  5069. // method's dependencies
  5070. mockParams mockParams
  5071. }{
  5072. {
  5073. name: "successfully send rendezvous cancellation",
  5074. me: newTestSession("me"),
  5075. givenCmd: []byte(`toc_rvous_cancel them aGFoYWhhaGE= 09461343-4C7F-11D1-8222-444553540000`),
  5076. mockParams: mockParams{
  5077. icbmParams: icbmParams{
  5078. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5079. {
  5080. sender: state.NewIdentScreenName("me"),
  5081. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5082. ChannelID: wire.ICBMChannelRendezvous,
  5083. ScreenName: "them",
  5084. TLVRestBlock: wire.TLVRestBlock{
  5085. TLVList: wire.TLVList{
  5086. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  5087. Type: wire.ICBMRdvMessageCancel,
  5088. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  5089. Capability: wire.CapFileTransfer,
  5090. TLVRestBlock: wire.TLVRestBlock{
  5091. TLVList: wire.TLVList{
  5092. wire.NewTLVBE(wire.ICBMRdvTLVTagsCancelReason, wire.ICBMRdvCancelReasonsUserCancel),
  5093. },
  5094. },
  5095. }),
  5096. },
  5097. },
  5098. },
  5099. },
  5100. },
  5101. },
  5102. },
  5103. wantMsg: []string{},
  5104. },
  5105. {
  5106. name: "send rendezvous cancellation, receive error from ICBM service",
  5107. me: newTestSession("me"),
  5108. givenCmd: []byte(`toc_rvous_cancel them aGFoYWhhaGE= 09461343-4C7F-11D1-8222-444553540000`),
  5109. mockParams: mockParams{
  5110. icbmParams: icbmParams{
  5111. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5112. {
  5113. sender: state.NewIdentScreenName("me"),
  5114. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5115. ChannelID: wire.ICBMChannelRendezvous,
  5116. ScreenName: "them",
  5117. TLVRestBlock: wire.TLVRestBlock{
  5118. TLVList: wire.TLVList{
  5119. wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
  5120. Type: wire.ICBMRdvMessageCancel,
  5121. Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
  5122. Capability: wire.CapFileTransfer,
  5123. TLVRestBlock: wire.TLVRestBlock{
  5124. TLVList: wire.TLVList{
  5125. wire.NewTLVBE(wire.ICBMRdvTLVTagsCancelReason, wire.ICBMRdvCancelReasonsUserCancel),
  5126. },
  5127. },
  5128. }),
  5129. },
  5130. },
  5131. },
  5132. err: io.EOF,
  5133. },
  5134. },
  5135. },
  5136. },
  5137. wantMsg: []string{cmdInternalSvcErr},
  5138. },
  5139. {
  5140. name: "bad command",
  5141. me: newTestSession("me"),
  5142. givenCmd: []byte(`toc_rvous_cancel`),
  5143. wantMsg: []string{cmdInternalSvcErr},
  5144. },
  5145. }
  5146. for _, tc := range cases {
  5147. t.Run(tc.name, func(t *testing.T) {
  5148. ctx := context.Background()
  5149. icbmSvc := newMockICBMService(t)
  5150. for _, params := range tc.mockParams.channelMsgToHostParamsICBM {
  5151. icbmSvc.EXPECT().
  5152. ChannelMsgToHost(ctx, matchSession(params.sender), params.inFrame, params.inBody).
  5153. Return(params.result, params.err)
  5154. }
  5155. svc := OSCARProxy{
  5156. Logger: slog.Default(),
  5157. ICBMService: icbmSvc,
  5158. }
  5159. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5160. assert.Equal(t, tc.wantMsg, msg)
  5161. })
  5162. }
  5163. }
  5164. func TestOSCARProxy_RecvClientCmd_SendIM(t *testing.T) {
  5165. cases := []struct {
  5166. // name is the unit test name
  5167. name string
  5168. // me is the TOC user session
  5169. me *state.SessionInstance
  5170. // givenCmd is the TOC command
  5171. givenCmd []byte
  5172. // wantMsg is the expected TOC response
  5173. wantMsg []string
  5174. // mockParams is the list of params sent to mocks that satisfy this
  5175. // method's dependencies
  5176. mockParams mockParams
  5177. }{
  5178. {
  5179. name: "successfully send instant message",
  5180. me: newTestSession("me"),
  5181. givenCmd: []byte(`toc_send_im chattingChuck "hello world! :\)"`),
  5182. mockParams: mockParams{
  5183. icbmParams: icbmParams{
  5184. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5185. {
  5186. sender: state.NewIdentScreenName("me"),
  5187. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5188. ChannelID: wire.ICBMChannelIM,
  5189. ScreenName: "chattingChuck",
  5190. TLVRestBlock: wire.TLVRestBlock{
  5191. TLVList: wire.TLVList{
  5192. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  5193. {
  5194. ID: 5,
  5195. Version: 1,
  5196. Payload: []byte{1, 1, 2},
  5197. },
  5198. {
  5199. ID: 1,
  5200. Version: 1,
  5201. Payload: []byte{
  5202. 0x00, 0x00,
  5203. 0x00, 0x00,
  5204. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', ' ', ':', ')',
  5205. },
  5206. },
  5207. }),
  5208. },
  5209. },
  5210. },
  5211. },
  5212. },
  5213. },
  5214. },
  5215. wantMsg: []string{},
  5216. },
  5217. {
  5218. name: "successfully auto-reply send instant message",
  5219. me: newTestSession("me"),
  5220. givenCmd: []byte(`toc_send_im chattingChuck "hello world!" auto`),
  5221. mockParams: mockParams{
  5222. icbmParams: icbmParams{
  5223. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5224. {
  5225. sender: state.NewIdentScreenName("me"),
  5226. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5227. ChannelID: wire.ICBMChannelIM,
  5228. ScreenName: "chattingChuck",
  5229. TLVRestBlock: wire.TLVRestBlock{
  5230. TLVList: wire.TLVList{
  5231. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  5232. {
  5233. ID: 5,
  5234. Version: 1,
  5235. Payload: []byte{1, 1, 2},
  5236. },
  5237. {
  5238. ID: 1,
  5239. Version: 1,
  5240. Payload: []byte{
  5241. 0x00, 0x00,
  5242. 0x00, 0x00,
  5243. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  5244. },
  5245. },
  5246. }),
  5247. wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}),
  5248. },
  5249. },
  5250. },
  5251. },
  5252. },
  5253. },
  5254. },
  5255. wantMsg: []string{},
  5256. },
  5257. {
  5258. name: "send instant message, receive error from ICBM service",
  5259. me: newTestSession("me"),
  5260. givenCmd: []byte(`toc_send_im chattingChuck "hello world!"`),
  5261. mockParams: mockParams{
  5262. icbmParams: icbmParams{
  5263. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5264. {
  5265. sender: state.NewIdentScreenName("me"),
  5266. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5267. ChannelID: wire.ICBMChannelIM,
  5268. ScreenName: "chattingChuck",
  5269. TLVRestBlock: wire.TLVRestBlock{
  5270. TLVList: wire.TLVList{
  5271. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  5272. {
  5273. ID: 5,
  5274. Version: 1,
  5275. Payload: []byte{1, 1, 2},
  5276. },
  5277. {
  5278. ID: 1,
  5279. Version: 1,
  5280. Payload: []byte{
  5281. 0x00, 0x00,
  5282. 0x00, 0x00,
  5283. 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!',
  5284. },
  5285. },
  5286. }),
  5287. },
  5288. },
  5289. },
  5290. err: io.EOF,
  5291. },
  5292. },
  5293. },
  5294. },
  5295. wantMsg: []string{cmdInternalSvcErr},
  5296. },
  5297. {
  5298. name: "bad command",
  5299. me: newTestSession("me"),
  5300. givenCmd: []byte(`toc_send_im`),
  5301. wantMsg: []string{cmdInternalSvcErr},
  5302. },
  5303. }
  5304. for _, tc := range cases {
  5305. t.Run(tc.name, func(t *testing.T) {
  5306. ctx := context.Background()
  5307. icbmSvc := newMockICBMService(t)
  5308. for _, params := range tc.mockParams.channelMsgToHostParamsICBM {
  5309. icbmSvc.EXPECT().
  5310. ChannelMsgToHost(ctx, matchSession(params.sender), params.inFrame, params.inBody).
  5311. Return(params.result, params.err)
  5312. }
  5313. svc := OSCARProxy{
  5314. Logger: slog.Default(),
  5315. ICBMService: icbmSvc,
  5316. }
  5317. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5318. assert.Equal(t, tc.wantMsg, msg)
  5319. })
  5320. }
  5321. }
  5322. func TestOSCARProxy_RecvClientCmd_SendIMEnc(t *testing.T) {
  5323. cases := []struct {
  5324. name string
  5325. me *state.SessionInstance
  5326. givenCmd []byte
  5327. wantMsg []string
  5328. mockParams mockParams
  5329. }{
  5330. {
  5331. name: "successfully send encoded instant message",
  5332. me: newTestSession("me"),
  5333. givenCmd: []byte(`toc2_send_im_enc chattingChuck "F" utf-8 en "hello"`),
  5334. mockParams: mockParams{
  5335. icbmParams: icbmParams{
  5336. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5337. {
  5338. sender: state.NewIdentScreenName("me"),
  5339. inFrame: wire.SNACFrame{},
  5340. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5341. ChannelID: wire.ICBMChannelIM,
  5342. ScreenName: "chattingChuck",
  5343. TLVRestBlock: wire.TLVRestBlock{
  5344. TLVList: wire.TLVList{
  5345. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  5346. {
  5347. ID: 5,
  5348. Version: 1,
  5349. Payload: []byte{1, 1, 2},
  5350. },
  5351. {
  5352. ID: 1,
  5353. Version: 1,
  5354. Payload: []byte{
  5355. 0x00, 0x00,
  5356. 0x00, 0x00,
  5357. 'h', 'e', 'l', 'l', 'o',
  5358. },
  5359. },
  5360. }),
  5361. },
  5362. },
  5363. },
  5364. },
  5365. },
  5366. },
  5367. },
  5368. wantMsg: []string{},
  5369. },
  5370. {
  5371. name: "successfully send encoded instant message with auto",
  5372. me: newTestSession("me"),
  5373. givenCmd: []byte(`toc2_send_im_enc chattingChuck "F" utf-8 en "hello" auto`),
  5374. mockParams: mockParams{
  5375. icbmParams: icbmParams{
  5376. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5377. {
  5378. sender: state.NewIdentScreenName("me"),
  5379. inFrame: wire.SNACFrame{},
  5380. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5381. ChannelID: wire.ICBMChannelIM,
  5382. ScreenName: "chattingChuck",
  5383. TLVRestBlock: wire.TLVRestBlock{
  5384. TLVList: wire.TLVList{
  5385. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  5386. {
  5387. ID: 5,
  5388. Version: 1,
  5389. Payload: []byte{1, 1, 2},
  5390. },
  5391. {
  5392. ID: 1,
  5393. Version: 1,
  5394. Payload: []byte{
  5395. 0x00, 0x00,
  5396. 0x00, 0x00,
  5397. 'h', 'e', 'l', 'l', 'o',
  5398. },
  5399. },
  5400. }),
  5401. wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}),
  5402. },
  5403. },
  5404. },
  5405. },
  5406. },
  5407. },
  5408. },
  5409. wantMsg: []string{},
  5410. },
  5411. {
  5412. name: "send encoded instant message, receive error from ICBM service",
  5413. me: newTestSession("me"),
  5414. givenCmd: []byte(`toc2_send_im_enc chattingChuck "F" utf-8 en "hello"`),
  5415. mockParams: mockParams{
  5416. icbmParams: icbmParams{
  5417. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{
  5418. {
  5419. sender: state.NewIdentScreenName("me"),
  5420. inFrame: wire.SNACFrame{},
  5421. inBody: wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
  5422. ChannelID: wire.ICBMChannelIM,
  5423. ScreenName: "chattingChuck",
  5424. TLVRestBlock: wire.TLVRestBlock{
  5425. TLVList: wire.TLVList{
  5426. wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
  5427. {
  5428. ID: 5,
  5429. Version: 1,
  5430. Payload: []byte{1, 1, 2},
  5431. },
  5432. {
  5433. ID: 1,
  5434. Version: 1,
  5435. Payload: []byte{
  5436. 0x00, 0x00,
  5437. 0x00, 0x00,
  5438. 'h', 'e', 'l', 'l', 'o',
  5439. },
  5440. },
  5441. }),
  5442. },
  5443. },
  5444. },
  5445. err: io.EOF,
  5446. },
  5447. },
  5448. },
  5449. },
  5450. wantMsg: []string{cmdInternalSvcErr},
  5451. },
  5452. {
  5453. name: "invalid args too few",
  5454. me: newTestSession("me"),
  5455. givenCmd: []byte(`toc2_send_im_enc`),
  5456. mockParams: mockParams{
  5457. icbmParams: icbmParams{
  5458. channelMsgToHostParamsICBM: channelMsgToHostParamsICBM{},
  5459. },
  5460. },
  5461. wantMsg: []string{cmdInternalSvcErr},
  5462. },
  5463. }
  5464. for _, tc := range cases {
  5465. t.Run(tc.name, func(t *testing.T) {
  5466. ctx := context.Background()
  5467. icbmSvc := newMockICBMService(t)
  5468. for _, params := range tc.mockParams.channelMsgToHostParamsICBM {
  5469. icbmSvc.EXPECT().
  5470. ChannelMsgToHost(ctx, matchSession(params.sender), params.inFrame, params.inBody).
  5471. Return(params.result, params.err)
  5472. }
  5473. svc := OSCARProxy{
  5474. Logger: slog.Default(),
  5475. ICBMService: icbmSvc,
  5476. }
  5477. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5478. assert.Equal(t, tc.wantMsg, msg)
  5479. })
  5480. }
  5481. }
  5482. func TestOSCARProxy_RecvClientCmd_SetAway(t *testing.T) {
  5483. cases := []struct {
  5484. // name is the unit test name
  5485. name string
  5486. // me is the TOC user session
  5487. me *state.SessionInstance
  5488. // givenCmd is the TOC command
  5489. givenCmd []byte
  5490. // wantMsg is the expected TOC response
  5491. wantMsg []string
  5492. // mockParams is the list of params sent to mocks that satisfy this
  5493. // method's dependencies
  5494. mockParams mockParams
  5495. }{
  5496. {
  5497. name: "successfully set away with message",
  5498. me: newTestSession("me"),
  5499. givenCmd: []byte(`toc_set_away "I'm away from my computer right now. :\)"`),
  5500. mockParams: mockParams{
  5501. locateParams: locateParams{
  5502. setInfoParams: setInfoParams{
  5503. {
  5504. me: state.NewIdentScreenName("me"),
  5505. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5506. TLVRestBlock: wire.TLVRestBlock{
  5507. TLVList: wire.TLVList{
  5508. wire.NewTLVBE(wire.LocateTLVTagsInfoUnavailableData, "I'm away from my computer right now. :)"),
  5509. },
  5510. },
  5511. },
  5512. },
  5513. },
  5514. },
  5515. },
  5516. wantMsg: []string{},
  5517. },
  5518. {
  5519. name: "successfully set away without message",
  5520. me: newTestSession("me"),
  5521. givenCmd: []byte(`toc_set_away`),
  5522. mockParams: mockParams{
  5523. locateParams: locateParams{
  5524. setInfoParams: setInfoParams{
  5525. {
  5526. me: state.NewIdentScreenName("me"),
  5527. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5528. TLVRestBlock: wire.TLVRestBlock{
  5529. TLVList: wire.TLVList{
  5530. wire.NewTLVBE(wire.LocateTLVTagsInfoUnavailableData, ""),
  5531. },
  5532. },
  5533. },
  5534. },
  5535. },
  5536. },
  5537. },
  5538. wantMsg: []string{},
  5539. },
  5540. {
  5541. name: "set away message, receive error from locate service",
  5542. me: newTestSession("me"),
  5543. givenCmd: []byte(`toc_set_away "I'm away from my computer right now."`),
  5544. mockParams: mockParams{
  5545. locateParams: locateParams{
  5546. setInfoParams: setInfoParams{
  5547. {
  5548. me: state.NewIdentScreenName("me"),
  5549. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5550. TLVRestBlock: wire.TLVRestBlock{
  5551. TLVList: wire.TLVList{
  5552. wire.NewTLVBE(wire.LocateTLVTagsInfoUnavailableData, "I'm away from my computer right now."),
  5553. },
  5554. },
  5555. },
  5556. err: io.EOF,
  5557. },
  5558. },
  5559. },
  5560. },
  5561. wantMsg: []string{cmdInternalSvcErr},
  5562. },
  5563. }
  5564. for _, tc := range cases {
  5565. t.Run(tc.name, func(t *testing.T) {
  5566. ctx := context.Background()
  5567. locateSvc := newMockLocateService(t)
  5568. for _, params := range tc.mockParams.setInfoParams {
  5569. locateSvc.EXPECT().
  5570. SetInfo(ctx, matchSession(params.me), params.inBody).
  5571. Return(params.err)
  5572. }
  5573. svc := OSCARProxy{
  5574. Logger: slog.Default(),
  5575. LocateService: locateSvc,
  5576. }
  5577. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5578. assert.Equal(t, tc.wantMsg, msg)
  5579. })
  5580. }
  5581. }
  5582. func TestOSCARProxy_RecvClientCmd_SetCaps(t *testing.T) {
  5583. cases := []struct {
  5584. // name is the unit test name
  5585. name string
  5586. // me is the TOC user session
  5587. me *state.SessionInstance
  5588. // givenCmd is the TOC command
  5589. givenCmd []byte
  5590. // wantMsg is the expected TOC response
  5591. wantMsg []string
  5592. // mockParams is the list of params sent to mocks that satisfy this
  5593. // method's dependencies
  5594. mockParams mockParams
  5595. }{
  5596. {
  5597. name: "successfully set capabilities",
  5598. me: newTestSession("me"),
  5599. givenCmd: []byte(`toc_set_caps 09460000-4C7F-11D1-8222-444553540000 09460001-4C7F-11D1-8222-444553540000`),
  5600. mockParams: mockParams{
  5601. locateParams: locateParams{
  5602. setInfoParams: setInfoParams{
  5603. {
  5604. me: state.NewIdentScreenName("me"),
  5605. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5606. TLVRestBlock: wire.TLVRestBlock{
  5607. TLVList: wire.TLVList{
  5608. wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
  5609. uuid.MustParse("09460000-4C7F-11D1-8222-444553540000"),
  5610. uuid.MustParse("09460001-4C7F-11D1-8222-444553540000"),
  5611. wire.CapChat,
  5612. }),
  5613. },
  5614. },
  5615. },
  5616. },
  5617. },
  5618. },
  5619. },
  5620. wantMsg: []string{},
  5621. },
  5622. {
  5623. name: "set capabilities with empty list",
  5624. me: newTestSession("me"),
  5625. givenCmd: []byte(`toc_set_caps`),
  5626. mockParams: mockParams{
  5627. locateParams: locateParams{
  5628. setInfoParams: setInfoParams{
  5629. {
  5630. me: state.NewIdentScreenName("me"),
  5631. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5632. TLVRestBlock: wire.TLVRestBlock{
  5633. TLVList: wire.TLVList{
  5634. wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
  5635. wire.CapChat,
  5636. }),
  5637. },
  5638. },
  5639. },
  5640. },
  5641. },
  5642. },
  5643. },
  5644. wantMsg: []string{},
  5645. },
  5646. {
  5647. name: "set capability, receive error from locate service",
  5648. me: newTestSession("me"),
  5649. givenCmd: []byte(`toc_set_caps 09460000-4C7F-11D1-8222-444553540000`),
  5650. mockParams: mockParams{
  5651. locateParams: locateParams{
  5652. setInfoParams: setInfoParams{
  5653. {
  5654. me: state.NewIdentScreenName("me"),
  5655. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5656. TLVRestBlock: wire.TLVRestBlock{
  5657. TLVList: wire.TLVList{
  5658. wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
  5659. uuid.MustParse("09460000-4C7F-11D1-8222-444553540000"),
  5660. wire.CapChat,
  5661. }),
  5662. },
  5663. },
  5664. },
  5665. err: io.EOF,
  5666. },
  5667. },
  5668. },
  5669. },
  5670. wantMsg: []string{cmdInternalSvcErr},
  5671. },
  5672. {
  5673. name: "set malformed capability UUID is skipped",
  5674. me: newTestSession("me"),
  5675. givenCmd: []byte(`toc_set_caps 09460000-`),
  5676. mockParams: mockParams{
  5677. locateParams: locateParams{
  5678. setInfoParams: setInfoParams{
  5679. {
  5680. me: state.NewIdentScreenName("me"),
  5681. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5682. TLVRestBlock: wire.TLVRestBlock{
  5683. TLVList: wire.TLVList{
  5684. wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
  5685. wire.CapChat,
  5686. }),
  5687. },
  5688. },
  5689. },
  5690. },
  5691. },
  5692. },
  5693. },
  5694. wantMsg: []string{},
  5695. },
  5696. {
  5697. name: "set capabilities with comma-separated list (TameClone format)",
  5698. me: newTestSession("me"),
  5699. givenCmd: []byte(`toc_set_caps 748F2420-6287-11D1-8222-444553540000,1348,134B,1341,1343,1FF,1345,1346,1347,`),
  5700. mockParams: mockParams{
  5701. locateParams: locateParams{
  5702. setInfoParams: setInfoParams{
  5703. {
  5704. me: state.NewIdentScreenName("me"),
  5705. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  5706. TLVRestBlock: wire.TLVRestBlock{
  5707. TLVList: wire.TLVList{
  5708. wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
  5709. wire.CapChat, // 748F... from client
  5710. wire.CapFileSharing, // 1348
  5711. wire.CapBuddyListTransfer, // 134B
  5712. wire.CapVoiceChat, // 1341
  5713. wire.CapFileTransfer, // 1343
  5714. wire.CapSmartCaps, // 1FF
  5715. wire.CapDirectICBM, // 1345
  5716. wire.CapAvatarService, // 1346
  5717. wire.CapStocksAddins, // 1347
  5718. wire.CapChat, // auto-appended
  5719. }),
  5720. },
  5721. },
  5722. },
  5723. },
  5724. },
  5725. },
  5726. },
  5727. wantMsg: []string{},
  5728. },
  5729. }
  5730. for _, tc := range cases {
  5731. t.Run(tc.name, func(t *testing.T) {
  5732. ctx := context.Background()
  5733. locateSvc := newMockLocateService(t)
  5734. for _, params := range tc.mockParams.setInfoParams {
  5735. locateSvc.EXPECT().
  5736. SetInfo(ctx, matchSession(params.me), params.inBody).
  5737. Return(params.err)
  5738. }
  5739. svc := OSCARProxy{
  5740. Logger: slog.Default(),
  5741. LocateService: locateSvc,
  5742. }
  5743. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5744. assert.Equal(t, tc.wantMsg, msg)
  5745. })
  5746. }
  5747. }
  5748. func TestOSCARProxy_RecvClientCmd_SetConfig(t *testing.T) {
  5749. cases := []struct {
  5750. // name is the unit test name
  5751. name string
  5752. // me is the TOC user session
  5753. me *state.SessionInstance
  5754. // givenCmd is the TOC command
  5755. givenCmd []byte
  5756. // wantMsg is the expected TOC response
  5757. wantMsg []string
  5758. // mockParams is the list of params sent to mocks that satisfy this
  5759. // method's dependencies
  5760. mockParams mockParams
  5761. }{
  5762. {
  5763. name: "successfully set permit all config (unquoted)",
  5764. me: newTestSession("me"),
  5765. givenCmd: []byte("toc_set_config {m 1\ng Buddies\nb friend1\nb friend2\n}\n"),
  5766. mockParams: mockParams{
  5767. tocConfigParams: tocConfigParams{
  5768. setTOCConfigParams: setTOCConfigParams{
  5769. {
  5770. user: state.NewIdentScreenName("me"),
  5771. config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
  5772. },
  5773. },
  5774. },
  5775. },
  5776. wantMsg: []string{},
  5777. },
  5778. {
  5779. name: "successfully set permit all config (double-quoted)",
  5780. me: newTestSession("me"),
  5781. givenCmd: []byte("toc_set_config \"{m 1\ng Buddies\nb friend1\nb friend2\n}\n\""),
  5782. mockParams: mockParams{
  5783. tocConfigParams: tocConfigParams{
  5784. setTOCConfigParams: setTOCConfigParams{
  5785. {
  5786. user: state.NewIdentScreenName("me"),
  5787. config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
  5788. },
  5789. },
  5790. },
  5791. },
  5792. wantMsg: []string{},
  5793. },
  5794. {
  5795. name: "successfully set permit all config (single-quoted)",
  5796. me: newTestSession("me"),
  5797. givenCmd: []byte("toc_set_config '{m 1\ng Buddies\nb friend1\nb friend2\n}\n'"),
  5798. mockParams: mockParams{
  5799. tocConfigParams: tocConfigParams{
  5800. setTOCConfigParams: setTOCConfigParams{
  5801. {
  5802. user: state.NewIdentScreenName("me"),
  5803. config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
  5804. },
  5805. },
  5806. },
  5807. },
  5808. wantMsg: []string{},
  5809. },
  5810. {
  5811. name: "successfully set permit all config (double-quoted with spaces)",
  5812. me: newTestSession("me"),
  5813. givenCmd: []byte("toc_set_config \" {m 1\ng Buddies\nb friend1\nb friend2\n}\n \""),
  5814. mockParams: mockParams{
  5815. tocConfigParams: tocConfigParams{
  5816. setTOCConfigParams: setTOCConfigParams{
  5817. {
  5818. user: state.NewIdentScreenName("me"),
  5819. config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
  5820. },
  5821. },
  5822. },
  5823. },
  5824. wantMsg: []string{},
  5825. },
  5826. {
  5827. name: "set config, receive error from toc config store",
  5828. me: newTestSession("me"),
  5829. givenCmd: []byte("toc_set_config {m 1\ng Buddies\nb friend1\nb friend2\n}\n"),
  5830. mockParams: mockParams{
  5831. tocConfigParams: tocConfigParams{
  5832. setTOCConfigParams: setTOCConfigParams{
  5833. {
  5834. user: state.NewIdentScreenName("me"),
  5835. config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
  5836. err: io.EOF,
  5837. },
  5838. },
  5839. },
  5840. },
  5841. wantMsg: []string{cmdInternalSvcErr},
  5842. },
  5843. {
  5844. name: "bad command",
  5845. me: newTestSession("me"),
  5846. givenCmd: []byte(`toc_set_config`),
  5847. wantMsg: []string{cmdInternalSvcErr},
  5848. },
  5849. }
  5850. for _, tc := range cases {
  5851. t.Run(tc.name, func(t *testing.T) {
  5852. ctx := context.Background()
  5853. pdSvc := newMockPermitDenyService(t)
  5854. for _, params := range tc.mockParams.addDenyListEntriesParams {
  5855. pdSvc.EXPECT().
  5856. AddDenyListEntries(ctx, matchSession(params.me), params.body).
  5857. Return(params.err)
  5858. }
  5859. for _, params := range tc.mockParams.addPermListEntriesParams {
  5860. pdSvc.EXPECT().
  5861. AddPermListEntries(ctx, matchSession(params.me), params.body).
  5862. Return(params.err)
  5863. }
  5864. buddySvc := newMockBuddyService(t)
  5865. for _, params := range tc.mockParams.addBuddiesParams {
  5866. buddySvc.EXPECT().
  5867. AddBuddies(ctx, matchSession(params.me), mock.Anything, params.inBody).
  5868. Return(nil, params.err)
  5869. }
  5870. tocConfigSvc := newMockTOCConfigStore(t)
  5871. for _, params := range tc.mockParams.setTOCConfigParams {
  5872. tocConfigSvc.EXPECT().
  5873. SetTOCConfig(matchContext(), params.user, params.config).
  5874. Return(params.err)
  5875. }
  5876. svc := OSCARProxy{
  5877. BuddyService: buddySvc,
  5878. Logger: slog.Default(),
  5879. PermitDenyService: pdSvc,
  5880. TOCConfigStore: tocConfigSvc,
  5881. }
  5882. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  5883. assert.Equal(t, tc.wantMsg, msg)
  5884. })
  5885. }
  5886. }
  5887. func TestOSCARProxy_RecvClientCmd_SetDir(t *testing.T) {
  5888. cases := []struct {
  5889. // name is the unit test name
  5890. name string
  5891. // me is the TOC user session
  5892. me *state.SessionInstance
  5893. // givenCmd is the TOC command
  5894. givenCmd []byte
  5895. // wantMsg is the expected TOC response
  5896. wantMsg []string
  5897. // mockParams is the list of params sent to mocks that satisfy this
  5898. // method's dependencies
  5899. mockParams mockParams
  5900. }{
  5901. {
  5902. name: "successfully set directory info with quoted fields",
  5903. me: newTestSession("me"),
  5904. givenCmd: []byte(`toc_set_dir "first name\$":"middle name":"last name":"maiden name":"city":"state":"country":"email":"allow web searches"`),
  5905. mockParams: mockParams{
  5906. locateParams: locateParams{
  5907. setDirInfoParams: setDirInfoParams{
  5908. {
  5909. me: state.NewIdentScreenName("me"),
  5910. inBody: wire.SNAC_0x02_0x09_LocateSetDirInfo{
  5911. TLVRestBlock: wire.TLVRestBlock{
  5912. TLVList: wire.TLVList{
  5913. wire.NewTLVBE(wire.ODirTLVFirstName, "first name$"),
  5914. wire.NewTLVBE(wire.ODirTLVMiddleName, "middle name"),
  5915. wire.NewTLVBE(wire.ODirTLVLastName, "last name"),
  5916. wire.NewTLVBE(wire.ODirTLVMaidenName, "maiden name"),
  5917. wire.NewTLVBE(wire.ODirTLVCountry, "country"),
  5918. wire.NewTLVBE(wire.ODirTLVState, "state"),
  5919. wire.NewTLVBE(wire.ODirTLVCity, "city"),
  5920. },
  5921. },
  5922. },
  5923. },
  5924. },
  5925. },
  5926. },
  5927. wantMsg: []string{},
  5928. },
  5929. {
  5930. name: "successfully set directory info with some blank fields",
  5931. me: newTestSession("me"),
  5932. givenCmd: []byte(`toc_set_dir "first name"::"last name"::"city":"state":"country":"email":"allow web searches"`),
  5933. mockParams: mockParams{
  5934. locateParams: locateParams{
  5935. setDirInfoParams: setDirInfoParams{
  5936. {
  5937. me: state.NewIdentScreenName("me"),
  5938. inBody: wire.SNAC_0x02_0x09_LocateSetDirInfo{
  5939. TLVRestBlock: wire.TLVRestBlock{
  5940. TLVList: wire.TLVList{
  5941. wire.NewTLVBE(wire.ODirTLVFirstName, "first name"),
  5942. wire.NewTLVBE(wire.ODirTLVMiddleName, ""),
  5943. wire.NewTLVBE(wire.ODirTLVLastName, "last name"),
  5944. wire.NewTLVBE(wire.ODirTLVMaidenName, ""),
  5945. wire.NewTLVBE(wire.ODirTLVCountry, "country"),
  5946. wire.NewTLVBE(wire.ODirTLVState, "state"),
  5947. wire.NewTLVBE(wire.ODirTLVCity, "city"),
  5948. },
  5949. },
  5950. },
  5951. },
  5952. },
  5953. },
  5954. },
  5955. wantMsg: []string{},
  5956. },
  5957. {
  5958. name: "successfully set directory info with last two fields absent",
  5959. me: newTestSession("me"),
  5960. givenCmd: []byte(`toc_set_dir "first name"::"last name"::"city":"state":"country"`),
  5961. mockParams: mockParams{
  5962. locateParams: locateParams{
  5963. setDirInfoParams: setDirInfoParams{
  5964. {
  5965. me: state.NewIdentScreenName("me"),
  5966. inBody: wire.SNAC_0x02_0x09_LocateSetDirInfo{
  5967. TLVRestBlock: wire.TLVRestBlock{
  5968. TLVList: wire.TLVList{
  5969. wire.NewTLVBE(wire.ODirTLVFirstName, "first name"),
  5970. wire.NewTLVBE(wire.ODirTLVMiddleName, ""),
  5971. wire.NewTLVBE(wire.ODirTLVLastName, "last name"),
  5972. wire.NewTLVBE(wire.ODirTLVMaidenName, ""),
  5973. wire.NewTLVBE(wire.ODirTLVCountry, "country"),
  5974. wire.NewTLVBE(wire.ODirTLVState, "state"),
  5975. wire.NewTLVBE(wire.ODirTLVCity, "city"),
  5976. },
  5977. },
  5978. },
  5979. },
  5980. },
  5981. },
  5982. },
  5983. wantMsg: []string{},
  5984. },
  5985. {
  5986. name: "set directory info, receive error from locate svc",
  5987. me: newTestSession("me"),
  5988. givenCmd: []byte(`toc_set_dir "first name":"middle name":"last name":"maiden name":"city":"state":"country":"email":"allow web searches"`),
  5989. mockParams: mockParams{
  5990. locateParams: locateParams{
  5991. setDirInfoParams: setDirInfoParams{
  5992. {
  5993. me: state.NewIdentScreenName("me"),
  5994. inBody: wire.SNAC_0x02_0x09_LocateSetDirInfo{
  5995. TLVRestBlock: wire.TLVRestBlock{
  5996. TLVList: wire.TLVList{
  5997. wire.NewTLVBE(wire.ODirTLVFirstName, "first name"),
  5998. wire.NewTLVBE(wire.ODirTLVMiddleName, "middle name"),
  5999. wire.NewTLVBE(wire.ODirTLVLastName, "last name"),
  6000. wire.NewTLVBE(wire.ODirTLVMaidenName, "maiden name"),
  6001. wire.NewTLVBE(wire.ODirTLVCountry, "country"),
  6002. wire.NewTLVBE(wire.ODirTLVState, "state"),
  6003. wire.NewTLVBE(wire.ODirTLVCity, "city"),
  6004. },
  6005. },
  6006. },
  6007. err: io.EOF,
  6008. },
  6009. },
  6010. },
  6011. },
  6012. wantMsg: []string{cmdInternalSvcErr},
  6013. },
  6014. {
  6015. name: "set directory with too many fields present",
  6016. me: newTestSession("me"),
  6017. givenCmd: []byte(`toc_set_dir "first name"::"last name"::"city":"state":"country":"email":"allow web searches":"extra":"extra"`),
  6018. wantMsg: []string{cmdInternalSvcErr},
  6019. },
  6020. {
  6021. name: "bad command",
  6022. me: newTestSession("me"),
  6023. givenCmd: []byte(`toc_set_dir`),
  6024. wantMsg: []string{cmdInternalSvcErr},
  6025. },
  6026. }
  6027. for _, tc := range cases {
  6028. t.Run(tc.name, func(t *testing.T) {
  6029. ctx := context.Background()
  6030. locateSvc := newMockLocateService(t)
  6031. for _, params := range tc.mockParams.setDirInfoParams {
  6032. locateSvc.EXPECT().
  6033. SetDirInfo(ctx, matchSession(params.me), wire.SNACFrame{}, params.inBody).
  6034. Return(params.msg, params.err)
  6035. }
  6036. svc := OSCARProxy{
  6037. Logger: slog.Default(),
  6038. LocateService: locateSvc,
  6039. }
  6040. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  6041. assert.Equal(t, tc.wantMsg, msg)
  6042. })
  6043. }
  6044. }
  6045. func TestOSCARProxy_RecvClientCmd_SetIdle(t *testing.T) {
  6046. cases := []struct {
  6047. // name is the unit test name
  6048. name string
  6049. // me is the TOC user session
  6050. me *state.SessionInstance
  6051. // givenCmd is the TOC command
  6052. givenCmd []byte
  6053. // wantMsg is the expected TOC response
  6054. wantMsg []string
  6055. // mockParams is the list of params sent to mocks that satisfy this
  6056. // method's dependencies
  6057. mockParams mockParams
  6058. }{
  6059. {
  6060. name: "successfully set idle status",
  6061. me: newTestSession("me"),
  6062. givenCmd: []byte(`toc_set_idle 10`),
  6063. mockParams: mockParams{
  6064. oServiceParams: oServiceParams{
  6065. idleNotificationParams: idleNotificationParams{
  6066. {
  6067. me: state.NewIdentScreenName("me"),
  6068. bodyIn: wire.SNAC_0x01_0x11_OServiceIdleNotification{
  6069. IdleTime: uint32(10),
  6070. },
  6071. },
  6072. },
  6073. },
  6074. },
  6075. wantMsg: []string{},
  6076. },
  6077. {
  6078. name: "set idle status, receive err from BOS oservice svc",
  6079. me: newTestSession("me"),
  6080. givenCmd: []byte(`toc_set_idle 10`),
  6081. mockParams: mockParams{
  6082. oServiceParams: oServiceParams{
  6083. idleNotificationParams: idleNotificationParams{
  6084. {
  6085. me: state.NewIdentScreenName("me"),
  6086. bodyIn: wire.SNAC_0x01_0x11_OServiceIdleNotification{
  6087. IdleTime: uint32(10),
  6088. },
  6089. err: io.EOF,
  6090. },
  6091. },
  6092. },
  6093. },
  6094. wantMsg: []string{cmdInternalSvcErr},
  6095. },
  6096. {
  6097. name: "bad secs param",
  6098. givenCmd: []byte(`toc_set_idle zero`),
  6099. wantMsg: []string{cmdInternalSvcErr},
  6100. },
  6101. {
  6102. name: "bad command",
  6103. me: newTestSession("me"),
  6104. givenCmd: []byte(`toc_set_idle`),
  6105. wantMsg: []string{cmdInternalSvcErr},
  6106. },
  6107. }
  6108. for _, tc := range cases {
  6109. t.Run(tc.name, func(t *testing.T) {
  6110. ctx := context.Background()
  6111. oServiceSvc := newMockOServiceService(t)
  6112. for _, params := range tc.mockParams.idleNotificationParams {
  6113. oServiceSvc.EXPECT().
  6114. IdleNotification(ctx, matchSession(params.me), params.bodyIn).
  6115. Return(params.err)
  6116. }
  6117. svc := OSCARProxy{
  6118. Logger: slog.Default(),
  6119. OServiceService: oServiceSvc,
  6120. }
  6121. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  6122. assert.Equal(t, tc.wantMsg, msg)
  6123. })
  6124. }
  6125. }
  6126. func TestOSCARProxy_RecvClientCmd_SetInfo(t *testing.T) {
  6127. cases := []struct {
  6128. // name is the unit test name
  6129. name string
  6130. // me is the TOC user session
  6131. me *state.SessionInstance
  6132. // givenCmd is the TOC command
  6133. givenCmd []byte
  6134. // wantMsg is the expected TOC response
  6135. wantMsg []string
  6136. // mockParams is the list of params sent to mocks that satisfy this
  6137. // method's dependencies
  6138. mockParams mockParams
  6139. }{
  6140. {
  6141. name: "successfully set profile",
  6142. me: newTestSession("me"),
  6143. givenCmd: []byte(`toc_set_info "my profile! :\)"`),
  6144. mockParams: mockParams{
  6145. locateParams: locateParams{
  6146. setInfoParams: setInfoParams{
  6147. {
  6148. me: state.NewIdentScreenName("me"),
  6149. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  6150. TLVRestBlock: wire.TLVRestBlock{
  6151. TLVList: wire.TLVList{
  6152. wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "my profile! :)"),
  6153. },
  6154. },
  6155. },
  6156. },
  6157. },
  6158. },
  6159. },
  6160. wantMsg: []string{},
  6161. },
  6162. {
  6163. name: "set profile, receive error from locate svc",
  6164. me: newTestSession("me"),
  6165. givenCmd: []byte(`toc_set_info "my profile!"`),
  6166. mockParams: mockParams{
  6167. locateParams: locateParams{
  6168. setInfoParams: setInfoParams{
  6169. {
  6170. me: state.NewIdentScreenName("me"),
  6171. inBody: wire.SNAC_0x02_0x04_LocateSetInfo{
  6172. TLVRestBlock: wire.TLVRestBlock{
  6173. TLVList: wire.TLVList{
  6174. wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "my profile!"),
  6175. },
  6176. },
  6177. },
  6178. err: io.EOF,
  6179. },
  6180. },
  6181. },
  6182. },
  6183. wantMsg: []string{cmdInternalSvcErr},
  6184. },
  6185. {
  6186. name: "bad command",
  6187. me: newTestSession("me"),
  6188. givenCmd: []byte(`toc_set_info`),
  6189. wantMsg: []string{cmdInternalSvcErr},
  6190. },
  6191. }
  6192. for _, tc := range cases {
  6193. t.Run(tc.name, func(t *testing.T) {
  6194. ctx := context.Background()
  6195. locateSvc := newMockLocateService(t)
  6196. for _, params := range tc.mockParams.setInfoParams {
  6197. locateSvc.EXPECT().
  6198. SetInfo(ctx, matchSession(params.me), params.inBody).
  6199. Return(params.err)
  6200. }
  6201. svc := OSCARProxy{
  6202. Logger: slog.Default(),
  6203. LocateService: locateSvc,
  6204. }
  6205. msg := svc.RecvClientCmd(ctx, tc.me, nil, tc.givenCmd, nil, nil)
  6206. assert.Equal(t, tc.wantMsg, msg)
  6207. })
  6208. }
  6209. }
  6210. func TestOSCARProxy_Signon(t *testing.T) {
  6211. roastedPass := wire.RoastTOCPassword([]byte("thepass"))
  6212. cases := []struct {
  6213. // name is the unit test name
  6214. name string
  6215. // checkSession validates the session returned from Signon when non-nil; leave nil for error cases
  6216. checkSession func(*testing.T, *state.SessionInstance)
  6217. // givenCmd is the TOC command
  6218. givenCmd []byte
  6219. // wantMsg is the expected TOC response
  6220. wantMsg []string
  6221. // mockParams is the list of params sent to mocks that satisfy this
  6222. // method's dependencies
  6223. mockParams mockParams
  6224. }{
  6225. {
  6226. name: "successfully login TOC1",
  6227. checkSession: func(t *testing.T, s *state.SessionInstance) {
  6228. assert.Equal(t, state.NewIdentScreenName("me"), s.IdentScreenName())
  6229. assert.Equal(t, [][16]byte{wire.CapChat}, s.Session().Caps())
  6230. assert.False(t, s.IsTOC2())
  6231. },
  6232. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6233. mockParams: mockParams{
  6234. authParams: authParams{
  6235. flapLoginParams: flapLoginParams{
  6236. {
  6237. frame: wire.FLAPSignonFrame{
  6238. TLVRestBlock: wire.TLVRestBlock{
  6239. TLVList: wire.TLVList{
  6240. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6241. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6242. },
  6243. },
  6244. },
  6245. tlv: wire.TLVRestBlock{
  6246. TLVList: wire.TLVList{
  6247. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6248. },
  6249. },
  6250. },
  6251. },
  6252. crackCookieParams: crackCookieParams{
  6253. {
  6254. cookieIn: []byte("thecookie"),
  6255. cookieOut: state.ServerCookie{Service: wire.BOS},
  6256. },
  6257. },
  6258. registerBOSSessionParams: registerBOSSessionParams{
  6259. {
  6260. authCookie: state.ServerCookie{Service: wire.BOS},
  6261. instance: newTestSession("me"),
  6262. },
  6263. },
  6264. },
  6265. buddyListRegistryParams: buddyListRegistryParams{
  6266. registerBuddyListParams: registerBuddyListParams{
  6267. {
  6268. user: state.NewIdentScreenName("me"),
  6269. },
  6270. },
  6271. },
  6272. tocConfigParams: tocConfigParams{
  6273. userParams: userParams{
  6274. {
  6275. screenName: state.NewIdentScreenName("me"),
  6276. returnedUser: &state.User{
  6277. TOCConfig: "my-toc-config",
  6278. },
  6279. },
  6280. },
  6281. },
  6282. },
  6283. wantMsg: []string{"SIGN_ON:TOC1.0", "CONFIG:my-toc-config", "NICK:me"},
  6284. },
  6285. {
  6286. name: "successfully login TOC2",
  6287. checkSession: func(t *testing.T, s *state.SessionInstance) {
  6288. assert.Equal(t, state.NewIdentScreenName("me"), s.IdentScreenName())
  6289. assert.Equal(t, [][16]byte{wire.CapChat}, s.Session().Caps())
  6290. assert.True(t, s.IsTOC2())
  6291. },
  6292. givenCmd: []byte(`toc2_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6293. mockParams: mockParams{
  6294. authParams: authParams{
  6295. flapLoginParams: flapLoginParams{
  6296. {
  6297. frame: wire.FLAPSignonFrame{
  6298. TLVRestBlock: wire.TLVRestBlock{
  6299. TLVList: wire.TLVList{
  6300. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6301. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6302. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  6303. },
  6304. },
  6305. },
  6306. tlv: wire.TLVRestBlock{
  6307. TLVList: wire.TLVList{
  6308. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6309. },
  6310. },
  6311. },
  6312. },
  6313. crackCookieParams: crackCookieParams{
  6314. {
  6315. cookieIn: []byte("thecookie"),
  6316. cookieOut: state.ServerCookie{Service: wire.BOS},
  6317. },
  6318. },
  6319. registerBOSSessionParams: registerBOSSessionParams{
  6320. {
  6321. authCookie: state.ServerCookie{Service: wire.BOS},
  6322. instance: newTestSession("me"),
  6323. },
  6324. },
  6325. },
  6326. buddyListRegistryParams: buddyListRegistryParams{
  6327. registerBuddyListParams: registerBuddyListParams{
  6328. {
  6329. user: state.NewIdentScreenName("me"),
  6330. },
  6331. },
  6332. },
  6333. feedBagParams: feedBagParams{
  6334. feedbagServiceUseParams: feedbagServiceUseParams{{err: nil}},
  6335. feedbagParams: feedbagParams{
  6336. {
  6337. screenName: state.NewIdentScreenName("me"),
  6338. results: nil,
  6339. err: nil,
  6340. },
  6341. },
  6342. },
  6343. },
  6344. wantMsg: []string{"SIGN_ON:TOC2.0", "NICK:me", "CONFIG2:done:\n"},
  6345. },
  6346. {
  6347. name: "successfully login toc2_login (TOC2 with encoded messaging)",
  6348. checkSession: func(t *testing.T, s *state.SessionInstance) {
  6349. assert.Equal(t, state.NewIdentScreenName("me"), s.IdentScreenName())
  6350. assert.Equal(t, [][16]byte{wire.CapChat}, s.Session().Caps())
  6351. assert.True(t, s.IsTOC2())
  6352. assert.True(t, s.SupportsTOC2MsgEnc())
  6353. },
  6354. givenCmd: []byte(`toc2_login "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6355. mockParams: mockParams{
  6356. authParams: authParams{
  6357. flapLoginParams: flapLoginParams{
  6358. {
  6359. frame: wire.FLAPSignonFrame{
  6360. TLVRestBlock: wire.TLVRestBlock{
  6361. TLVList: wire.TLVList{
  6362. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6363. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6364. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  6365. },
  6366. },
  6367. },
  6368. tlv: wire.TLVRestBlock{
  6369. TLVList: wire.TLVList{
  6370. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6371. },
  6372. },
  6373. },
  6374. },
  6375. crackCookieParams: crackCookieParams{
  6376. {
  6377. cookieIn: []byte("thecookie"),
  6378. cookieOut: state.ServerCookie{Service: wire.BOS},
  6379. },
  6380. },
  6381. registerBOSSessionParams: registerBOSSessionParams{
  6382. {
  6383. authCookie: state.ServerCookie{Service: wire.BOS},
  6384. instance: newTestSession("me"),
  6385. },
  6386. },
  6387. },
  6388. buddyListRegistryParams: buddyListRegistryParams{
  6389. registerBuddyListParams: registerBuddyListParams{
  6390. {
  6391. user: state.NewIdentScreenName("me"),
  6392. },
  6393. },
  6394. },
  6395. feedBagParams: feedBagParams{
  6396. feedbagServiceUseParams: feedbagServiceUseParams{{err: nil}},
  6397. feedbagParams: feedbagParams{
  6398. {
  6399. screenName: state.NewIdentScreenName("me"),
  6400. results: nil,
  6401. err: nil,
  6402. },
  6403. },
  6404. },
  6405. },
  6406. wantMsg: []string{"SIGN_ON:TOC2.0", "NICK:me", "CONFIG2:done:\n"},
  6407. },
  6408. {
  6409. name: "login TOC2, receive error from FeedbagService.Use",
  6410. // Signon returns (sess, err) on feedbag errors, so session is non-nil; use no-op check
  6411. checkSession: func(*testing.T, *state.SessionInstance) {},
  6412. givenCmd: []byte(`toc2_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6413. mockParams: mockParams{
  6414. authParams: authParams{
  6415. flapLoginParams: flapLoginParams{
  6416. {
  6417. frame: wire.FLAPSignonFrame{
  6418. TLVRestBlock: wire.TLVRestBlock{
  6419. TLVList: wire.TLVList{
  6420. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6421. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6422. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  6423. },
  6424. },
  6425. },
  6426. tlv: wire.TLVRestBlock{
  6427. TLVList: wire.TLVList{
  6428. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6429. },
  6430. },
  6431. },
  6432. },
  6433. crackCookieParams: crackCookieParams{
  6434. {
  6435. cookieIn: []byte("thecookie"),
  6436. cookieOut: state.ServerCookie{Service: wire.BOS},
  6437. },
  6438. },
  6439. registerBOSSessionParams: registerBOSSessionParams{
  6440. {
  6441. authCookie: state.ServerCookie{Service: wire.BOS},
  6442. instance: newTestSession("me"),
  6443. },
  6444. },
  6445. },
  6446. buddyListRegistryParams: buddyListRegistryParams{
  6447. registerBuddyListParams: registerBuddyListParams{
  6448. {user: state.NewIdentScreenName("me")},
  6449. },
  6450. },
  6451. feedBagParams: feedBagParams{
  6452. feedbagServiceUseParams: feedbagServiceUseParams{{err: io.EOF}},
  6453. },
  6454. },
  6455. wantMsg: []string{cmdInternalSvcErr},
  6456. },
  6457. {
  6458. name: "login TOC2, receive error from FeedbagManager.Feedbag",
  6459. // Signon returns (sess, err) on feedbag errors, so session is non-nil; use no-op check
  6460. checkSession: func(*testing.T, *state.SessionInstance) {},
  6461. givenCmd: []byte(`toc2_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6462. mockParams: mockParams{
  6463. authParams: authParams{
  6464. flapLoginParams: flapLoginParams{
  6465. {
  6466. frame: wire.FLAPSignonFrame{
  6467. TLVRestBlock: wire.TLVRestBlock{
  6468. TLVList: wire.TLVList{
  6469. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6470. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6471. wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient),
  6472. },
  6473. },
  6474. },
  6475. tlv: wire.TLVRestBlock{
  6476. TLVList: wire.TLVList{
  6477. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6478. },
  6479. },
  6480. },
  6481. },
  6482. crackCookieParams: crackCookieParams{
  6483. {
  6484. cookieIn: []byte("thecookie"),
  6485. cookieOut: state.ServerCookie{Service: wire.BOS},
  6486. },
  6487. },
  6488. registerBOSSessionParams: registerBOSSessionParams{
  6489. {
  6490. authCookie: state.ServerCookie{Service: wire.BOS},
  6491. instance: newTestSession("me"),
  6492. },
  6493. },
  6494. },
  6495. buddyListRegistryParams: buddyListRegistryParams{
  6496. registerBuddyListParams: registerBuddyListParams{
  6497. {user: state.NewIdentScreenName("me")},
  6498. },
  6499. },
  6500. feedBagParams: feedBagParams{
  6501. feedbagServiceUseParams: feedbagServiceUseParams{{err: nil}},
  6502. feedbagParams: feedbagParams{
  6503. {
  6504. screenName: state.NewIdentScreenName("me"),
  6505. results: nil,
  6506. err: io.EOF,
  6507. },
  6508. },
  6509. },
  6510. },
  6511. wantMsg: []string{cmdInternalSvcErr},
  6512. },
  6513. {
  6514. name: "login, receive error from auth svc FLAP login",
  6515. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6516. mockParams: mockParams{
  6517. authParams: authParams{
  6518. flapLoginParams: flapLoginParams{
  6519. {
  6520. frame: wire.FLAPSignonFrame{
  6521. TLVRestBlock: wire.TLVRestBlock{
  6522. TLVList: wire.TLVList{
  6523. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6524. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6525. },
  6526. },
  6527. },
  6528. err: io.EOF,
  6529. },
  6530. },
  6531. },
  6532. },
  6533. wantMsg: []string{cmdInternalSvcErr},
  6534. },
  6535. {
  6536. name: "login, receive error from auth svc registration",
  6537. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6538. mockParams: mockParams{
  6539. authParams: authParams{
  6540. flapLoginParams: flapLoginParams{
  6541. {
  6542. frame: wire.FLAPSignonFrame{
  6543. TLVRestBlock: wire.TLVRestBlock{
  6544. TLVList: wire.TLVList{
  6545. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6546. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6547. },
  6548. },
  6549. },
  6550. tlv: wire.TLVRestBlock{
  6551. TLVList: wire.TLVList{
  6552. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6553. },
  6554. },
  6555. },
  6556. },
  6557. crackCookieParams: crackCookieParams{
  6558. {
  6559. cookieIn: []byte("thecookie"),
  6560. cookieOut: state.ServerCookie{Service: wire.BOS},
  6561. },
  6562. },
  6563. registerBOSSessionParams: registerBOSSessionParams{
  6564. {
  6565. authCookie: state.ServerCookie{Service: wire.BOS},
  6566. err: io.EOF,
  6567. },
  6568. },
  6569. },
  6570. },
  6571. wantMsg: []string{cmdInternalSvcErr},
  6572. },
  6573. {
  6574. name: "login, receive error from buddy list registry",
  6575. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6576. mockParams: mockParams{
  6577. authParams: authParams{
  6578. flapLoginParams: flapLoginParams{
  6579. {
  6580. frame: wire.FLAPSignonFrame{
  6581. TLVRestBlock: wire.TLVRestBlock{
  6582. TLVList: wire.TLVList{
  6583. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6584. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6585. },
  6586. },
  6587. },
  6588. tlv: wire.TLVRestBlock{
  6589. TLVList: wire.TLVList{
  6590. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6591. },
  6592. },
  6593. },
  6594. },
  6595. crackCookieParams: crackCookieParams{
  6596. {
  6597. cookieIn: []byte("thecookie"),
  6598. cookieOut: state.ServerCookie{Service: wire.BOS},
  6599. },
  6600. },
  6601. registerBOSSessionParams: registerBOSSessionParams{
  6602. {
  6603. authCookie: state.ServerCookie{Service: wire.BOS},
  6604. instance: newTestSession("me"),
  6605. },
  6606. },
  6607. },
  6608. buddyListRegistryParams: buddyListRegistryParams{
  6609. registerBuddyListParams: registerBuddyListParams{
  6610. {
  6611. user: state.NewIdentScreenName("me"),
  6612. err: io.EOF,
  6613. },
  6614. },
  6615. },
  6616. },
  6617. wantMsg: []string{cmdInternalSvcErr},
  6618. },
  6619. {
  6620. name: "login, receive error from TOC config store",
  6621. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6622. mockParams: mockParams{
  6623. authParams: authParams{
  6624. flapLoginParams: flapLoginParams{
  6625. {
  6626. frame: wire.FLAPSignonFrame{
  6627. TLVRestBlock: wire.TLVRestBlock{
  6628. TLVList: wire.TLVList{
  6629. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6630. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6631. },
  6632. },
  6633. },
  6634. tlv: wire.TLVRestBlock{
  6635. TLVList: wire.TLVList{
  6636. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6637. },
  6638. },
  6639. },
  6640. },
  6641. crackCookieParams: crackCookieParams{
  6642. {
  6643. cookieIn: []byte("thecookie"),
  6644. cookieOut: state.ServerCookie{Service: wire.BOS},
  6645. },
  6646. },
  6647. registerBOSSessionParams: registerBOSSessionParams{
  6648. {
  6649. authCookie: state.ServerCookie{Service: wire.BOS},
  6650. instance: newTestSession("me"),
  6651. },
  6652. },
  6653. },
  6654. buddyListRegistryParams: buddyListRegistryParams{
  6655. registerBuddyListParams: registerBuddyListParams{
  6656. {
  6657. user: state.NewIdentScreenName("me"),
  6658. },
  6659. },
  6660. },
  6661. tocConfigParams: tocConfigParams{
  6662. userParams: userParams{
  6663. {
  6664. screenName: state.NewIdentScreenName("me"),
  6665. err: io.EOF,
  6666. },
  6667. },
  6668. },
  6669. },
  6670. wantMsg: []string{cmdInternalSvcErr},
  6671. },
  6672. {
  6673. name: "login, user not found after login",
  6674. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6675. mockParams: mockParams{
  6676. authParams: authParams{
  6677. flapLoginParams: flapLoginParams{
  6678. {
  6679. frame: wire.FLAPSignonFrame{
  6680. TLVRestBlock: wire.TLVRestBlock{
  6681. TLVList: wire.TLVList{
  6682. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6683. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6684. },
  6685. },
  6686. },
  6687. tlv: wire.TLVRestBlock{
  6688. TLVList: wire.TLVList{
  6689. wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("thecookie")),
  6690. },
  6691. },
  6692. },
  6693. },
  6694. crackCookieParams: crackCookieParams{
  6695. {
  6696. cookieIn: []byte("thecookie"),
  6697. cookieOut: state.ServerCookie{Service: wire.BOS},
  6698. },
  6699. },
  6700. registerBOSSessionParams: registerBOSSessionParams{
  6701. {
  6702. authCookie: state.ServerCookie{Service: wire.BOS},
  6703. instance: newTestSession("me"),
  6704. },
  6705. },
  6706. },
  6707. buddyListRegistryParams: buddyListRegistryParams{
  6708. registerBuddyListParams: registerBuddyListParams{
  6709. {
  6710. user: state.NewIdentScreenName("me"),
  6711. },
  6712. },
  6713. },
  6714. tocConfigParams: tocConfigParams{
  6715. userParams: userParams{
  6716. {
  6717. screenName: state.NewIdentScreenName("me"),
  6718. returnedUser: nil,
  6719. },
  6720. },
  6721. },
  6722. },
  6723. wantMsg: []string{cmdInternalSvcErr},
  6724. },
  6725. {
  6726. name: "login with bad credentials",
  6727. givenCmd: []byte(`toc_signon "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6728. mockParams: mockParams{
  6729. authParams: authParams{
  6730. flapLoginParams: flapLoginParams{
  6731. {
  6732. frame: wire.FLAPSignonFrame{
  6733. TLVRestBlock: wire.TLVRestBlock{
  6734. TLVList: wire.TLVList{
  6735. wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
  6736. wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
  6737. },
  6738. },
  6739. },
  6740. tlv: wire.TLVRestBlock{
  6741. TLVList: wire.TLVList{
  6742. wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword),
  6743. },
  6744. },
  6745. },
  6746. },
  6747. },
  6748. },
  6749. wantMsg: []string{"ERROR:980"},
  6750. },
  6751. {
  6752. name: "bad command",
  6753. givenCmd: []byte(`toc_bad "" "" me "0x` + hex.EncodeToString(roastedPass) + `"`),
  6754. wantMsg: []string{cmdInternalSvcErr},
  6755. },
  6756. }
  6757. for _, tc := range cases {
  6758. t.Run(tc.name, func(t *testing.T) {
  6759. ctx := context.Background()
  6760. authSvc := newMockAuthService(t)
  6761. for _, params := range tc.mockParams.flapLoginParams {
  6762. authSvc.EXPECT().
  6763. FLAPLogin(matchContext(), params.frame, config.Endpoint{}).
  6764. Return(params.tlv, params.err)
  6765. }
  6766. for _, params := range tc.mockParams.crackCookieParams {
  6767. authSvc.EXPECT().
  6768. CrackCookie(params.cookieIn).
  6769. Return(params.cookieOut, time.Now().Add(time.Minute), params.err)
  6770. }
  6771. for _, params := range tc.mockParams.registerBOSSessionParams {
  6772. authSvc.EXPECT().
  6773. RegisterBOSSession(matchContext(), params.authCookie, mock.Anything).
  6774. Return(params.instance, params.err)
  6775. }
  6776. buddyRegistry := newMockBuddyListRegistry(t)
  6777. for _, params := range tc.mockParams.registerBuddyListParams {
  6778. buddyRegistry.EXPECT().
  6779. RegisterBuddyList(matchContext(), params.user).
  6780. Return(params.err)
  6781. }
  6782. tocCfg := newMockTOCConfigStore(t)
  6783. for _, params := range tc.mockParams.userParams {
  6784. tocCfg.EXPECT().
  6785. User(matchContext(), params.screenName).
  6786. Return(params.returnedUser, params.err)
  6787. }
  6788. fbSvc := newMockFeedbagService(t)
  6789. for _, params := range tc.mockParams.feedbagServiceUseParams {
  6790. fbSvc.EXPECT().
  6791. Use(matchContext(), mock.Anything).
  6792. Return(params.err)
  6793. }
  6794. fbMgr := newMockFeedbagManager(t)
  6795. for _, params := range tc.mockParams.feedbagParams {
  6796. fbMgr.EXPECT().
  6797. Feedbag(matchContext(), params.screenName).
  6798. Return(params.results, params.err)
  6799. }
  6800. buddySvc := newMockBuddyService(t)
  6801. chatSessionMgr := newMockChatSessionManager(t)
  6802. for _, params := range tc.mockParams.removeUserFromAllChatsParams {
  6803. chatSessionMgr.EXPECT().
  6804. RemoveUserFromAllChats(params.user)
  6805. }
  6806. svc := OSCARProxy{
  6807. AuthService: authSvc,
  6808. BuddyListRegistry: buddyRegistry,
  6809. BuddyService: buddySvc,
  6810. ChatSessionManager: chatSessionMgr,
  6811. Logger: slog.Default(),
  6812. TOCConfigStore: tocCfg,
  6813. FeedbagService: fbSvc,
  6814. FeedbagManager: fbMgr,
  6815. OServiceService: nopOServiceService{},
  6816. }
  6817. sess, msg := svc.Signon(ctx, tc.givenCmd,
  6818. func(ctx context.Context, instance *state.SessionInstance) error { return nil },
  6819. func(ctx context.Context, instance *state.SessionInstance) {},
  6820. NewChatRegistry(),
  6821. )
  6822. assert.Equal(t, tc.wantMsg, msg)
  6823. if tc.checkSession != nil {
  6824. tc.checkSession(t, sess)
  6825. } else {
  6826. assert.Nil(t, sess)
  6827. }
  6828. })
  6829. }
  6830. }
  6831. func TestOSCARProxy_RecvClientCmd_UnknownCmd(t *testing.T) {
  6832. ctx := context.Background()
  6833. svc := OSCARProxy{
  6834. Logger: slog.Default(),
  6835. }
  6836. cmd := []byte("toc_unknown_cmd")
  6837. msg := svc.RecvClientCmd(ctx, nil, nil, cmd, nil, nil)
  6838. assert.Equal(t, cmdInternalSvcErr, msg[0])
  6839. }
  6840. func Test_parseArgs(t *testing.T) {
  6841. type testCase struct {
  6842. name string
  6843. givenPayload string
  6844. givenArgs []*string
  6845. wantVarArgs []string
  6846. wantArgs []string
  6847. wantErrMsg string
  6848. }
  6849. tests := []testCase{
  6850. {
  6851. name: "no positional args or varargs",
  6852. givenPayload: ``,
  6853. givenArgs: nil,
  6854. wantVarArgs: []string{},
  6855. },
  6856. {
  6857. name: "positional args with varargs",
  6858. givenPayload: `1234 "Join me!" user1 user2 user3`,
  6859. givenArgs: []*string{new(string), new(string)},
  6860. wantVarArgs: []string{"user1", "user2", "user3"},
  6861. wantArgs: []string{"1234", "Join me!"},
  6862. },
  6863. {
  6864. name: "nil positional argument placeholders should get skipped",
  6865. givenPayload: `1234 "Join me!" user1 user2 user3`,
  6866. givenArgs: []*string{nil, nil}, // still 2 placeholders, both nil
  6867. wantVarArgs: []string{"user1", "user2", "user3"},
  6868. wantArgs: []string{"", ""},
  6869. },
  6870. {
  6871. name: "positional args with no varargs",
  6872. givenPayload: `1234 "Join me!"`,
  6873. givenArgs: []*string{new(string), new(string)}, // roomID + msg
  6874. wantVarArgs: []string{},
  6875. wantArgs: []string{"1234", "Join me!"},
  6876. },
  6877. {
  6878. name: "varargs only",
  6879. givenPayload: `user1 user2 user3`,
  6880. givenArgs: nil,
  6881. wantVarArgs: []string{"user1", "user2", "user3"},
  6882. },
  6883. {
  6884. name: "too many positional arg placeholders",
  6885. givenPayload: `toc_chat_invite`,
  6886. givenArgs: []*string{new(string), new(string)},
  6887. wantVarArgs: []string{},
  6888. wantErrMsg: "command contains fewer arguments than expected",
  6889. },
  6890. {
  6891. name: "CSV parser error",
  6892. givenPayload: ``,
  6893. givenArgs: []*string{nil},
  6894. wantVarArgs: []string{},
  6895. wantErrMsg: "CSV reader error",
  6896. },
  6897. }
  6898. for _, tt := range tests {
  6899. t.Run(tt.name, func(t *testing.T) {
  6900. varArgs, err := parseArgs([]byte(tt.givenPayload), tt.givenArgs...)
  6901. if tt.wantErrMsg != "" {
  6902. assert.ErrorContains(t, err, tt.wantErrMsg)
  6903. return
  6904. }
  6905. assert.NoError(t, err)
  6906. // verify the placeholder pointers got populated
  6907. for i, want := range tt.wantArgs {
  6908. if want == "" {
  6909. assert.Nil(t, tt.givenArgs[i])
  6910. } else {
  6911. got := *tt.givenArgs[i]
  6912. assert.Equal(t, want, got)
  6913. }
  6914. }
  6915. // verify we have the same varargs
  6916. assert.Equal(t, tt.wantVarArgs, varArgs)
  6917. assert.Equal(t, len(tt.wantArgs), len(tt.givenArgs))
  6918. })
  6919. }
  6920. }
  6921. func TestBuildToc2Config(t *testing.T) {
  6922. tests := []struct {
  6923. name string
  6924. fb []wire.FeedbagItem
  6925. want []string // CONFIG2 command string(s), each starts with "CONFIG2:"
  6926. wantErr string
  6927. }{
  6928. {
  6929. name: "root group missing order attribute returns error",
  6930. fb: []wire.FeedbagItem{
  6931. {Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup, TLVLBlock: wire.TLVLBlock{}},
  6932. },
  6933. wantErr: "root group missing order attribute",
  6934. },
  6935. {
  6936. name: "empty buddylist yields only done",
  6937. fb: []wire.FeedbagItem{
  6938. {
  6939. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6940. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
  6941. },
  6942. },
  6943. want: []string{"CONFIG2:done:\n"},
  6944. },
  6945. {
  6946. name: "single group with buddies",
  6947. fb: []wire.FeedbagItem{
  6948. {
  6949. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6950. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
  6951. },
  6952. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6953. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}}},
  6954. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "user1", TLVLBlock: wire.TLVLBlock{}},
  6955. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "user2", TLVLBlock: wire.TLVLBlock{}},
  6956. },
  6957. want: []string{"CONFIG2:g:Buddies\nb:user1\nb:user2\ndone:\n"},
  6958. },
  6959. {
  6960. name: "blocked user (d:) and private user (p:)",
  6961. fb: []wire.FeedbagItem{
  6962. {
  6963. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6964. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
  6965. },
  6966. {ClassID: wire.FeedbagClassIDDeny, Name: "blockeduser"},
  6967. {ClassID: wire.FeedbagClassIDPermit, Name: "allowuser"},
  6968. },
  6969. want: []string{"CONFIG2:d:blockeduser\np:allowuser\ndone:\n"},
  6970. },
  6971. {
  6972. name: "privacy level (m:) from pdinfo",
  6973. fb: []wire.FeedbagItem{
  6974. {
  6975. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6976. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
  6977. },
  6978. {
  6979. ClassID: wire.FeedbagClassIdPdinfo,
  6980. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(3))}},
  6981. },
  6982. },
  6983. want: []string{"CONFIG2:m:3\ndone:\n"},
  6984. },
  6985. {
  6986. name: "buddy with alias",
  6987. fb: []wire.FeedbagItem{
  6988. {
  6989. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6990. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
  6991. },
  6992. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  6993. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
  6994. {
  6995. ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob",
  6996. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesAlias, "Bob Smith")}},
  6997. },
  6998. },
  6999. want: []string{"CONFIG2:g:Buddies\nb:bob:Bob Smith\ndone:\n"},
  7000. },
  7001. {
  7002. name: "buddy with note (comment) uses five colons before note",
  7003. fb: []wire.FeedbagItem{
  7004. {
  7005. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  7006. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
  7007. },
  7008. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  7009. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
  7010. {
  7011. ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice",
  7012. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesNote, "Friend from work")}},
  7013. },
  7014. },
  7015. want: []string{"CONFIG2:g:Buddies\nb:alice:::::Friend from work\ndone:\n"},
  7016. },
  7017. {
  7018. name: "multiple groups in root order",
  7019. fb: []wire.FeedbagItem{
  7020. {
  7021. Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  7022. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100, 200})}},
  7023. },
  7024. {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  7025. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
  7026. {Name: "Family", GroupID: 200, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
  7027. TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2})}}},
  7028. {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend1", TLVLBlock: wire.TLVLBlock{}},
  7029. {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 200, Name: "mom", TLVLBlock: wire.TLVLBlock{}},
  7030. },
  7031. want: []string{"CONFIG2:g:Buddies\nb:friend1\ng:Family\nb:mom\ndone:\n"},
  7032. },
  7033. }
  7034. for _, tt := range tests {
  7035. t.Run(tt.name, func(t *testing.T) {
  7036. got, err := buildToc2Config(tt.fb)
  7037. if tt.wantErr != "" {
  7038. assert.ErrorContains(t, err, tt.wantErr)
  7039. assert.Empty(t, got)
  7040. return
  7041. }
  7042. assert.NoError(t, err)
  7043. assert.Equal(t, tt.want, got)
  7044. })
  7045. }
  7046. }
  7047. // TestUnescape tests the unescape function
  7048. func TestUnescape(t *testing.T) {
  7049. tests := []struct {
  7050. name string
  7051. input string
  7052. expected string
  7053. }{
  7054. {"No Escapes", "Hello World", "Hello World"},
  7055. {"Escaped Brace", "Hello \\{World\\}", "Hello {World}"},
  7056. {"Escaped Parentheses", "Test\\(123\\)", "Test(123)"},
  7057. {"Escaped Brackets", "\\[List\\]", "[List]"},
  7058. {"Escaped Dollar", "Price: \\$100", "Price: $100"},
  7059. {"Escaped Quote", "She said \\\"Hello\\\"", "She said \"Hello\""},
  7060. {"Multiple Escapes", "One\\, Two\\, Three", "One, Two, Three"},
  7061. {"Consecutive Escapes", "\\\\\\$100", "\\$100"},
  7062. {"Only Escape Character", "\\", ""},
  7063. {"Empty Input", "", ""},
  7064. }
  7065. for _, tt := range tests {
  7066. t.Run(tt.name, func(t *testing.T) {
  7067. result := unescape(tt.input)
  7068. if result != tt.expected {
  7069. t.Errorf("unescape(%q) = %q; want %q", tt.input, result, tt.expected)
  7070. }
  7071. })
  7072. }
  7073. }