cmd_client_test.go 212 KB

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