| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103 |
- package toc
- import (
- "context"
- "errors"
- "io"
- "log/slog"
- "net"
- "strings"
- "testing"
- "time"
- "github.com/google/uuid"
- "github.com/stretchr/testify/assert"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // cmdTestCase is a TOC command and the SNACs and replies it should produce.
- type cmdTestCase struct {
- // name is the unit test name
- name string
- // givenCmd is the TOC command
- givenCmd string
- // sendErrs are the errors returned by each SNAC send, in order
- sendErrs []error
- // wantSNACs are the SNACs the command sends to the BOS server
- wantSNACs []wire.SNACMessage
- // wantCacheData is the request context cached with the first SNAC
- wantCacheData any
- // wantMsg is the expected TOC response
- wantMsg []string
- }
- // runCmdTests runs TOC commands that only send SNACs over the BOS connection.
- // TOC2 commands run on a TOC2 connection.
- func runCmdTests(t *testing.T, cases []cmdTestCase) {
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- me.isTOC2 = strings.HasPrefix(tc.givenCmd, "toc2_")
- bos.sendErrs = tc.sendErrs
- svc := OSCARProxy{
- Logger: slog.Default(),
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, tc.wantSNACs, bos.sentSNACs())
- if tc.wantCacheData != nil {
- req, ok := me.cache.take(wire.SNACFrame{FoodGroup: tc.wantSNACs[0].Frame.FoodGroup, RequestID: 1})
- assert.True(t, ok)
- assert.Equal(t, tc.wantCacheData, req.data)
- }
- })
- }
- }
- func TestOSCARProxy_send(t *testing.T) {
- t.Run("caches the request until its reply arrives", func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- svc := OSCARProxy{Logger: slog.Default()}
- msg := svc.send(context.Background(), me, bos, wire.Locate, wire.LocateUserInfoQuery, struct{}{}, getStatusReqCtx{screenName: "them"})
- assert.Nil(t, msg)
- req, ok := me.cache.take(wire.SNACFrame{FoodGroup: wire.Locate, RequestID: 1})
- assert.True(t, ok)
- assert.Equal(t, wire.LocateUserInfoQuery, req.frame.SubGroup)
- assert.Equal(t, getStatusReqCtx{screenName: "them"}, req.data)
- })
- t.Run("drops the cached request when the send fails", func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- bos.sendErrs = []error{io.EOF}
- svc := OSCARProxy{Logger: slog.Default()}
- msg := svc.send(context.Background(), me, bos, wire.Locate, wire.LocateUserInfoQuery, struct{}{}, nil)
- assert.Equal(t, []string{cmdInternalSvcErr}, msg)
- _, ok := me.cache.take(wire.SNACFrame{FoodGroup: wire.Locate, RequestID: 1})
- assert.False(t, ok)
- })
- }
- func TestOSCARProxy_RecvClientCmd_AddBuddy(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully add buddies",
- givenCmd: "toc_add_buddy friend1 friend2 friend3",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyAddBuddies, 1, wire.SNAC_0x03_0x04_BuddyAddBuddies{
- Buddies: []struct {
- ScreenName string `oscar:"len_prefix=uint8"`
- }{
- {ScreenName: "friend1"},
- {ScreenName: "friend2"},
- {ScreenName: "friend3"},
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "add buddies with empty list",
- givenCmd: "toc_add_buddy",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyAddBuddies, 1, wire.SNAC_0x03_0x04_BuddyAddBuddies{}),
- },
- wantMsg: []string{},
- },
- {
- name: "add buddies, send fails",
- givenCmd: "toc_add_buddy friend1",
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyAddBuddies, 1, wire.SNAC_0x03_0x04_BuddyAddBuddies{
- Buddies: []struct {
- ScreenName string `oscar:"len_prefix=uint8"`
- }{
- {ScreenName: "friend1"},
- },
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_AddPermit(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully permit buddies",
- givenCmd: "toc_add_permit friend1 friend2",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.PermitDeny, wire.PermitDenyAddPermListEntries, 1, wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries{
- Users: []struct {
- ScreenName string `oscar:"len_prefix=uint8"`
- }{
- {ScreenName: "friend1"},
- {ScreenName: "friend2"},
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "permit buddies with empty list",
- givenCmd: "toc_add_permit",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.PermitDeny, wire.PermitDenyAddPermListEntries, 1, wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries{}),
- },
- wantMsg: []string{},
- },
- {
- name: "permit buddies, send fails",
- givenCmd: "toc_add_permit",
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.PermitDeny, wire.PermitDenyAddPermListEntries, 1, wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries{}),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_AddDeny(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully deny buddies",
- givenCmd: "toc_add_deny friend1 friend2",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.PermitDeny, wire.PermitDenyAddDenyListEntries, 1, wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries{
- Users: []struct {
- ScreenName string `oscar:"len_prefix=uint8"`
- }{
- {ScreenName: "friend1"},
- {ScreenName: "friend2"},
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "deny buddies with empty list",
- givenCmd: "toc_add_deny",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.PermitDeny, wire.PermitDenyAddDenyListEntries, 1, wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries{}),
- },
- wantMsg: []string{},
- },
- {
- name: "deny buddies, send fails",
- givenCmd: "toc_add_deny",
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.PermitDeny, wire.PermitDenyAddDenyListEntries, 1, wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries{}),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_RemoveBuddy(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully remove buddies",
- givenCmd: "toc_remove_buddy friend1 friend2",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyDelBuddies, 1, wire.SNAC_0x03_0x05_BuddyDelBuddies{
- Buddies: []struct {
- ScreenName string `oscar:"len_prefix=uint8"`
- }{
- {ScreenName: "friend1"},
- {ScreenName: "friend2"},
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "remove buddies with empty list",
- givenCmd: "toc_remove_buddy",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyDelBuddies, 1, wire.SNAC_0x03_0x05_BuddyDelBuddies{}),
- },
- wantMsg: []string{},
- },
- {
- name: "remove buddies, send fails",
- givenCmd: "toc_remove_buddy",
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyDelBuddies, 1, wire.SNAC_0x03_0x05_BuddyDelBuddies{}),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_ChangePassword(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully request password change",
- givenCmd: `toc_change_passwd "old\$pass" newpass`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Admin, wire.AdminInfoChangeRequest, 1, wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.AdminTLVOldPassword, "old$pass"),
- wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
- },
- },
- }),
- },
- wantCacheData: changePasswdReqCtx{},
- wantMsg: []string{},
- },
- {
- name: "request password change, send fails",
- givenCmd: `toc_change_passwd oldpass newpass`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Admin, wire.AdminInfoChangeRequest, 1, wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.AdminTLVOldPassword, "oldpass"),
- wire.NewTLVBE(wire.AdminTLVNewPassword, "newpass"),
- },
- },
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_change_passwd`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_FormatNickname(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully request screen name format change",
- givenCmd: `toc_format_nickname "{M E}"`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Admin, wire.AdminInfoChangeRequest, 1, wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "M E"),
- },
- },
- }),
- },
- wantCacheData: formatNickReqCtx{},
- wantMsg: []string{},
- },
- {
- name: "bad command",
- givenCmd: `toc_format_nickname`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_Evil(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully warn normally",
- givenCmd: `toc_evil them norm`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ICBM, wire.ICBMEvilRequest, 1, wire.SNAC_0x04_0x08_ICBMEvilRequest{
- SendAs: 0,
- ScreenName: "them",
- }),
- },
- wantCacheData: evilReqCtx{},
- wantMsg: []string{},
- },
- {
- name: "successfully warn anonymously",
- givenCmd: `toc_evil them anon`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ICBM, wire.ICBMEvilRequest, 1, wire.SNAC_0x04_0x08_ICBMEvilRequest{
- SendAs: 1,
- ScreenName: "them",
- }),
- },
- wantCacheData: evilReqCtx{},
- wantMsg: []string{},
- },
- {
- name: "warn with incorrect type",
- givenCmd: `toc_evil them blah`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_evil`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_GetStatus(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully request status",
- givenCmd: `toc_get_status them`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Locate, wire.LocateUserInfoQuery, 1, wire.SNAC_0x02_0x05_LocateUserInfoQuery{
- ScreenName: "them",
- }),
- },
- wantCacheData: getStatusReqCtx{screenName: "them"},
- wantMsg: []string{},
- },
- {
- name: "request status, send fails",
- givenCmd: `toc_get_status them`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Locate, wire.LocateUserInfoQuery, 1, wire.SNAC_0x02_0x05_LocateUserInfoQuery{
- ScreenName: "them",
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_get_status`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_ChatJoin(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully request room creation",
- givenCmd: `toc_chat_join 4 "my \$room"`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ChatNav, wire.ChatNavCreateRoom, 1, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
- Exchange: 4,
- Cookie: "create",
- TLVBlock: wire.TLVBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatRoomTLVRoomName, "my $room"),
- },
- },
- }),
- },
- wantCacheData: chatJoinReqCtx{},
- wantMsg: []string{},
- },
- {
- name: "request room creation, send fails",
- givenCmd: `toc_chat_join 4 room`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ChatNav, wire.ChatNavCreateRoom, 1, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
- Exchange: 4,
- Cookie: "create",
- TLVBlock: wire.TLVBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatRoomTLVRoomName, "room"),
- },
- },
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_chat_join`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad exchange number",
- givenCmd: `toc_chat_join four room`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_ChatAccept(t *testing.T) {
- room := wire.ICBMRoomInfo{
- Exchange: 4,
- Cookie: "4-0-the room",
- Instance: 0,
- }
- cases := []struct {
- name string
- givenCmd string
- wantSNACs []wire.SNACMessage
- wantCacheData any
- wantMsg []string
- }{
- {
- name: "successfully request room info",
- givenCmd: `toc_chat_accept 0`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ChatNav, wire.ChatNavRequestRoomInfo, 1, wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
- Cookie: "4-0-the room",
- Exchange: 4,
- InstanceNumber: 0,
- }),
- },
- wantCacheData: chatAcceptReqCtx{chatID: 0},
- wantMsg: []string{},
- },
- {
- name: "accept invitation to unknown room",
- givenCmd: `toc_chat_accept 1`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad chat room ID",
- givenCmd: `toc_chat_accept zero`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_chat_accept`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- me.chats.Add(room)
- svc := OSCARProxy{
- Logger: slog.Default(),
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, tc.wantSNACs, bos.sentSNACs())
- if tc.wantCacheData != nil {
- req, ok := me.cache.take(wire.SNACFrame{FoodGroup: wire.ChatNav, RequestID: 1})
- assert.True(t, ok)
- assert.Equal(t, tc.wantCacheData, req.data)
- }
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_ChatInvite(t *testing.T) {
- room := wire.ICBMRoomInfo{
- Exchange: 4,
- Cookie: "4-0-the room",
- Instance: 0,
- }
- invite := func(reqID uint32, guest string) wire.SNACMessage {
- return newSNAC(wire.ICBM, wire.ICBMChannelMsgToHost, reqID, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
- ChannelID: wire.ICBMChannelRendezvous,
- ScreenName: guest,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
- Type: wire.ICBMRdvMessagePropose,
- Capability: wire.CapChat,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMRdvTLVTagsSeqNum, uint16(1)),
- wire.NewTLVBE(wire.ICBMRdvTLVTagsInvitation, "join $us"),
- wire.NewTLVBE(wire.ICBMRdvTLVTagsInviteMIMECharset, "us-ascii"),
- wire.NewTLVBE(wire.ICBMRdvTLVTagsInviteMIMELang, "en"),
- wire.NewTLVBE(wire.ICBMRdvTLVTagsSvcData, room),
- },
- },
- }),
- },
- },
- })
- }
- cases := []struct {
- name string
- givenCmd string
- sendErrs []error
- wantSNACs []wire.SNACMessage
- wantMsg []string
- }{
- {
- name: "successfully send chat invitations",
- givenCmd: `toc_chat_invite 0 "join \$us" friend1 friend2`,
- wantSNACs: []wire.SNACMessage{
- invite(1, "friend1"),
- invite(2, "friend2"),
- },
- wantMsg: []string{},
- },
- {
- name: "send chat invitations, first send fails",
- givenCmd: `toc_chat_invite 0 "join \$us" friend1 friend2`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- invite(1, "friend1"),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "send chat invitation to unknown room",
- givenCmd: `toc_chat_invite 1 "join us" friend1`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad chat room ID",
- givenCmd: `toc_chat_invite zero "join us" friend1`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_chat_invite`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- bos.sendErrs = tc.sendErrs
- me.chats.Add(room)
- svc := OSCARProxy{
- Logger: slog.Default(),
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, tc.wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_ChatLeave(t *testing.T) {
- cases := []struct {
- name string
- givenCmd string
- wantMsg []string
- wantConnClose bool
- }{
- {
- name: "successfully leave chat",
- givenCmd: `toc_chat_leave 0`,
- wantMsg: []string{"CHAT_LEFT:0"},
- wantConnClose: true,
- },
- {
- name: "chat room ID with invalid format",
- givenCmd: `toc_chat_leave zero`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "missing chat session",
- givenCmd: `toc_chat_leave 1`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_chat_leave`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, _ := newTestTOCConn(newTestSession("me"))
- chatConn := newFakeOSCARConn()
- me.chats.RegisterConn(0, chatConn)
- svc := OSCARProxy{
- Logger: slog.Default(),
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, tc.wantConnClose, chatConn.isClosed())
- if tc.wantConnClose {
- assert.Nil(t, me.chats.RetrieveConn(0))
- }
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_ChatSend(t *testing.T) {
- cases := []struct {
- name string
- givenCmd string
- sendErrs []error
- wantSNACs []wire.SNACMessage
- wantMsg []string
- }{
- {
- name: "successfully send chat message",
- givenCmd: `toc_chat_send 0 "Hello \$world"`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Chat, wire.ChatChannelMsgToHost, 1, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
- Channel: wire.ICBMChannelMIME,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
- wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
- wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello $world"),
- },
- }),
- },
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "send chat message, send fails",
- givenCmd: `toc_chat_send 0 "Hello"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Chat, wire.ChatChannelMsgToHost, 1, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
- Channel: wire.ICBMChannelMIME,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVEnableReflectionFlag, uint8(1)),
- wire.NewTLVBE(wire.ChatTLVPublicWhisperFlag, []byte{}),
- wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello"),
- },
- }),
- },
- },
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "chat room ID with invalid format",
- givenCmd: `toc_chat_send zero "Hello"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "missing chat session",
- givenCmd: `toc_chat_send 1 "Hello"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_chat_send`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- chatConn := newFakeOSCARConn()
- chatConn.sendErrs = tc.sendErrs
- me.chats.RegisterConn(0, chatConn)
- svc := OSCARProxy{
- Logger: slog.Default(),
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, tc.wantSNACs, chatConn.sentSNACs())
- assert.Empty(t, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_ChatWhisper(t *testing.T) {
- cases := []struct {
- name string
- givenCmd string
- sendErrs []error
- wantSNACs []wire.SNACMessage
- wantMsg []string
- }{
- {
- name: "successfully send chat whisper",
- givenCmd: `toc_chat_whisper 0 them "Hello \$world"`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Chat, wire.ChatChannelMsgToHost, 1, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
- Channel: wire.ICBMChannelMIME,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVWhisperToUser, "them"),
- wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello $world"),
- },
- }),
- },
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "send chat whisper, send fails",
- givenCmd: `toc_chat_whisper 0 them "Hello"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.Chat, wire.ChatChannelMsgToHost, 1, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost{
- Channel: wire.ICBMChannelMIME,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVWhisperToUser, "them"),
- wire.NewTLVBE(wire.ChatTLVMessageInfo, wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ChatTLVMessageInfoText, "Hello"),
- },
- }),
- },
- },
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "chat room ID with invalid format",
- givenCmd: `toc_chat_whisper zero them "Hello"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "missing chat session",
- givenCmd: `toc_chat_whisper 1 them "Hello"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_chat_whisper`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, _ := newTestTOCConn(newTestSession("me"))
- chatConn := newFakeOSCARConn()
- chatConn.sendErrs = tc.sendErrs
- me.chats.RegisterConn(0, chatConn)
- svc := OSCARProxy{
- Logger: slog.Default(),
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, tc.wantSNACs, chatConn.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_ClientEvent(t *testing.T) {
- event := func(status uint16) wire.SNACMessage {
- return newSNAC(wire.ICBM, wire.ICBMClientEvent, 1, wire.SNAC_0x04_0x14_ICBMClientEvent{
- ChannelID: wire.ICBMChannelIM,
- ScreenName: "them",
- Event: status,
- })
- }
- runCmdTests(t, []cmdTestCase{
- {
- name: "send typing status 0 (no activity)",
- givenCmd: `toc2_client_event them 0`,
- wantSNACs: []wire.SNACMessage{event(0)},
- wantMsg: []string{},
- },
- {
- name: "send typing status 1 (typing paused)",
- givenCmd: `toc2_client_event them 1`,
- wantSNACs: []wire.SNACMessage{event(1)},
- wantMsg: []string{},
- },
- {
- name: "send typing status 2 (currently typing)",
- givenCmd: `toc2_client_event them 2`,
- wantSNACs: []wire.SNACMessage{event(2)},
- wantMsg: []string{},
- },
- {
- name: "send typing status, send fails",
- givenCmd: `toc2_client_event them 2`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{event(2)},
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "missing args",
- givenCmd: `toc2_client_event`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "missing typing status",
- givenCmd: `toc2_client_event them`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "invalid typing status (must be 0, 1, or 2)",
- givenCmd: `toc2_client_event them 3`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "invalid typing status non-numeric",
- givenCmd: `toc2_client_event them x`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_InitDone(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully initialize connection",
- givenCmd: "toc_init_done",
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.OService, wire.OServiceClientOnline, 1, wire.SNAC_0x01_0x02_OServiceClientOnline{}),
- },
- wantMsg: []string{},
- },
- {
- name: "initialize connection, send fails",
- givenCmd: "toc_init_done",
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.OService, wire.OServiceClientOnline, 1, wire.SNAC_0x01_0x02_OServiceClientOnline{}),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_RvousAccept(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully send rendezvous accept",
- givenCmd: `toc_rvous_accept them aGFoYWhhaGE= 09461343-4C7F-11D1-8222-444553540000`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ICBM, wire.ICBMChannelMsgToHost, 1, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
- ChannelID: wire.ICBMChannelRendezvous,
- ScreenName: "them",
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
- Type: wire.ICBMRdvMessageAccept,
- Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
- Capability: wire.CapFileTransfer,
- }),
- },
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "bad cookie",
- givenCmd: `toc_rvous_accept them @@@ 09461343-4C7F-11D1-8222-444553540000`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad capability",
- givenCmd: `toc_rvous_accept them aGFoYWhhaGE= notauuid`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_rvous_accept`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_RvousCancel(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully send rendezvous cancellation",
- givenCmd: `toc_rvous_cancel them aGFoYWhhaGE= 09461343-4C7F-11D1-8222-444553540000`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ICBM, wire.ICBMChannelMsgToHost, 1, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
- ChannelID: wire.ICBMChannelRendezvous,
- ScreenName: "them",
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMTLVData, wire.ICBMCh2Fragment{
- Type: wire.ICBMRdvMessageCancel,
- Cookie: [8]byte{'h', 'a', 'h', 'a', 'h', 'a', 'h', 'a'},
- Capability: wire.CapFileTransfer,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMRdvTLVTagsCancelReason, wire.ICBMRdvCancelReasonsUserCancel),
- },
- },
- }),
- },
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "bad cookie",
- givenCmd: `toc_rvous_cancel them @@@ 09461343-4C7F-11D1-8222-444553540000`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_rvous_cancel`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- // asciiIM returns the ICBM channel 1 message a TOC IM command sends for text.
- func asciiIM(reqID uint32, recip string, text string, autoReply bool) wire.SNACMessage {
- payload := append([]byte{0x00, 0x00, 0x00, 0x00}, text...)
- body := wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
- ChannelID: wire.ICBMChannelIM,
- ScreenName: recip,
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
- {
- ID: 5,
- Version: 1,
- Payload: []byte{1, 1, 2},
- },
- {
- ID: 1,
- Version: 1,
- Payload: payload,
- },
- }),
- },
- },
- }
- if autoReply {
- body.Append(wire.NewTLVBE(wire.ICBMTLVAutoResponse, []byte{}))
- }
- return newSNAC(wire.ICBM, wire.ICBMChannelMsgToHost, reqID, body)
- }
- func TestOSCARProxy_RecvClientCmd_SendIM(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully send instant message",
- givenCmd: `toc_send_im chattingChuck "hello \$world"`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello $world", false)},
- wantMsg: []string{},
- },
- {
- name: "successfully send TOC2 instant message",
- givenCmd: `toc2_send_im chattingChuck "hello"`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", false)},
- wantMsg: []string{},
- },
- {
- name: "successfully auto-reply send instant message",
- givenCmd: `toc_send_im chattingChuck "hello" auto`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", true)},
- wantMsg: []string{},
- },
- {
- name: "send instant message, send fails",
- givenCmd: `toc_send_im chattingChuck "hello"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", false)},
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_send_im`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_SendIMEnc(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully send encoded instant message",
- givenCmd: `toc2_send_im_enc chattingChuck "F" utf-8 en "hello"`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", false)},
- wantMsg: []string{},
- },
- {
- name: "successfully send ASCII-encoded instant message",
- givenCmd: `toc2_send_im_enc chattingChuck "F" A en "hello"`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", false)},
- wantMsg: []string{},
- },
- {
- name: "successfully send UTF-8-encoded non-ASCII instant message as unicode",
- givenCmd: `toc2_send_im_enc chattingChuck "F" U en "привет"`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.ICBM, wire.ICBMChannelMsgToHost, 1, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost{
- ChannelID: wire.ICBMChannelIM,
- ScreenName: "chattingChuck",
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ICBMTLVAOLIMData, []wire.ICBMCh1Fragment{
- {
- ID: 5,
- Version: 1,
- Payload: []byte{1, 6},
- },
- {
- ID: 1,
- Version: 1,
- Payload: []byte{
- 0x00, 0x02,
- 0x00, 0x00,
- 0x04, 0x3F, 0x04, 0x40, 0x04, 0x38, 0x04, 0x32, 0x04, 0x35, 0x04, 0x42,
- },
- },
- }),
- },
- },
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "successfully send UTF-8-encoded ASCII instant message as ASCII",
- givenCmd: `toc2_send_im_enc chattingChuck "F" U en "hello"`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", false)},
- wantMsg: []string{},
- },
- {
- name: "successfully send encoded instant message with auto",
- givenCmd: `toc2_send_im_enc chattingChuck "F" utf-8 en "hello" auto`,
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", true)},
- wantMsg: []string{},
- },
- {
- name: "send encoded instant message, send fails",
- givenCmd: `toc2_send_im_enc chattingChuck "F" utf-8 en "hello"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{asciiIM(1, "chattingChuck", "hello", false)},
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "invalid args too few",
- givenCmd: `toc2_send_im_enc`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- // setInfoSNAC returns the locate set info SNAC a TOC command sends.
- func setInfoSNAC(reqID uint32, tlv wire.TLV) wire.SNACMessage {
- return newSNAC(wire.Locate, wire.LocateSetInfo, reqID, wire.SNAC_0x02_0x04_LocateSetInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{tlv},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_SetAway(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully set away with message",
- givenCmd: `toc_set_away "brb \$5"`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoUnavailableData, "brb $5")),
- },
- wantMsg: []string{},
- },
- {
- name: "successfully set away without message",
- givenCmd: `toc_set_away`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoUnavailableData, "")),
- },
- wantMsg: []string{},
- },
- {
- name: "set away message, send fails",
- givenCmd: `toc_set_away "brb"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoUnavailableData, "brb")),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_SetCaps(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully set capabilities",
- givenCmd: `toc_set_caps 09460000-4C7F-11D1-8222-444553540000 09460001-4C7F-11D1-8222-444553540000`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
- uuid.MustParse("09460000-4C7F-11D1-8222-444553540000"),
- uuid.MustParse("09460001-4C7F-11D1-8222-444553540000"),
- wire.CapChat,
- })),
- },
- wantMsg: []string{},
- },
- {
- name: "set capabilities with empty list",
- givenCmd: `toc_set_caps`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
- wire.CapChat,
- })),
- },
- wantMsg: []string{},
- },
- {
- name: "set capability, send fails",
- givenCmd: `toc_set_caps 09460000-4C7F-11D1-8222-444553540000`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
- uuid.MustParse("09460000-4C7F-11D1-8222-444553540000"),
- wire.CapChat,
- })),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "set malformed capability UUID is skipped",
- givenCmd: `toc_set_caps 09460000-`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
- wire.CapChat,
- })),
- },
- wantMsg: []string{},
- },
- {
- name: "set capabilities with comma-separated list (TameClone format)",
- givenCmd: `toc_set_caps 748F2420-6287-11D1-8222-444553540000,1348,134B,1341,1343,1FF,1345,1346,1347,`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{
- wire.CapChat, // 748F... from client
- wire.CapFileSharing, // 1348
- wire.CapBuddyListTransfer, // 134B
- wire.CapVoiceChat, // 1341
- wire.CapFileTransfer, // 1343
- wire.CapSmartCaps, // 1FF
- wire.CapDirectICBM, // 1345
- wire.CapAvatarService, // 1346
- wire.CapStocksAddins, // 1347
- wire.CapChat, // auto-appended
- })),
- },
- wantMsg: []string{},
- },
- })
- }
- // setDirSNAC returns the locate set dir info SNAC a toc_set_dir command
- // sends for the given fields.
- func setDirSNAC(reqID uint32, first, middle, last, maiden, city, state, country string) wire.SNACMessage {
- return newSNAC(wire.Locate, wire.LocateSetDirInfo, reqID, wire.SNAC_0x02_0x09_LocateSetDirInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.ODirTLVFirstName, first),
- wire.NewTLVBE(wire.ODirTLVMiddleName, middle),
- wire.NewTLVBE(wire.ODirTLVLastName, last),
- wire.NewTLVBE(wire.ODirTLVMaidenName, maiden),
- wire.NewTLVBE(wire.ODirTLVCountry, country),
- wire.NewTLVBE(wire.ODirTLVState, state),
- wire.NewTLVBE(wire.ODirTLVCity, city),
- },
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_SetDir(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully set directory info with quoted fields",
- givenCmd: `toc_set_dir "first name\$":"middle name":"last name":"maiden name":"city":"state":"country":"email":"allow web searches"`,
- wantSNACs: []wire.SNACMessage{
- setDirSNAC(1, "first name$", "middle name", "last name", "maiden name", "city", "state", "country"),
- },
- wantMsg: []string{},
- },
- {
- name: "successfully set directory info with some blank fields",
- givenCmd: `toc_set_dir "first name"::"last name"::"city":"state":"country":"email":"allow web searches"`,
- wantSNACs: []wire.SNACMessage{
- setDirSNAC(1, "first name", "", "last name", "", "city", "state", "country"),
- },
- wantMsg: []string{},
- },
- {
- name: "successfully set directory info with last two fields absent",
- givenCmd: `toc_set_dir "first name"::"last name"::"city":"state":"country"`,
- wantSNACs: []wire.SNACMessage{
- setDirSNAC(1, "first name", "", "last name", "", "city", "state", "country"),
- },
- wantMsg: []string{},
- },
- {
- name: "set directory info, send fails",
- givenCmd: `toc_set_dir "first name":"middle name":"last name":"maiden name":"city":"state":"country":"email":"allow web searches"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- setDirSNAC(1, "first name", "middle name", "last name", "maiden name", "city", "state", "country"),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "set directory with too many fields present",
- givenCmd: `toc_set_dir "first name"::"last name"::"city":"state":"country":"email":"allow web searches":"extra":"extra"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_set_dir`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_SetIdle(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully set idle status",
- givenCmd: `toc_set_idle 10`,
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.OService, wire.OServiceIdleNotification, 1, wire.SNAC_0x01_0x11_OServiceIdleNotification{
- IdleTime: 10,
- }),
- },
- wantMsg: []string{},
- },
- {
- name: "set idle status, send fails",
- givenCmd: `toc_set_idle 10`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- newSNAC(wire.OService, wire.OServiceIdleNotification, 1, wire.SNAC_0x01_0x11_OServiceIdleNotification{
- IdleTime: 10,
- }),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad secs param",
- givenCmd: `toc_set_idle ten`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_set_idle`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_SetInfo(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "successfully set profile",
- givenCmd: `toc_set_info "my profile costs \$5"`,
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "my profile costs $5")),
- },
- wantMsg: []string{},
- },
- {
- name: "set profile, send fails",
- givenCmd: `toc_set_info "my profile"`,
- sendErrs: []error{io.EOF},
- wantSNACs: []wire.SNACMessage{
- setInfoSNAC(1, wire.NewTLVBE(wire.LocateTLVTagsInfoSigData, "my profile")),
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- givenCmd: `toc_set_info`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_UnknownCmd(t *testing.T) {
- runCmdTests(t, []cmdTestCase{
- {
- name: "unknown command",
- givenCmd: `toc_does_not_exist`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- })
- }
- func TestOSCARProxy_RecvClientCmd_NewBuddies(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "add new users to existing group",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\ng:Family\nb:alice\nb:bob\n}"),
- wantMsg: []string{"NEW_BUDDY_REPLY2:mike:added", "NEW_BUDDY_REPLY2:mk6i:added", "NEW_BUDDY_REPLY2:alice:added", "NEW_BUDDY_REPLY2:bob:added"},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 21827, 29709})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- {Name: "Co-Workers", GroupID: 21827, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}},
- },
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 29709, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 4, ClassID: wire.FeedbagClassIdBuddy, GroupID: 29709, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{3, 4})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "empty feedbag: add new group and buddies",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\n}"),
- wantMsg: []string{"NEW_BUDDY_REPLY2:mike:added", "NEW_BUDDY_REPLY2:mk6i:added"},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- // First: insert the two buddy items (deterministic randIntn gives GroupID 1, ItemIDs 2 and 3)
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- // Second: insert the root group so its Order lists the new group ID
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
- },
- },
- err: nil,
- },
- // Third: insert the new Buddies group with Order listing the buddy item IDs
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 1, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "add new group and buddies",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Co-Workers\nb:carol\nb:dan\ng:Family\nb:alice\nb:bob\n}"),
- wantMsg: []string{"NEW_BUDDY_REPLY2:carol:added", "NEW_BUDDY_REPLY2:dan:added", "NEW_BUDDY_REPLY2:alice:added", "NEW_BUDDY_REPLY2:bob:added"},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "carol", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "dan", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- // Root updated once, before the new groups
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 1, 4})}},
- },
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Co-Workers", GroupID: 1, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3})}},
- },
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 5, ClassID: wire.FeedbagClassIdBuddy, GroupID: 4, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 6, ClassID: wire.FeedbagClassIdBuddy, GroupID: 4, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Family", GroupID: 4, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{5, 6})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "empty config returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies "),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "whitespace-only config returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies "),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "buddy without group returns parse error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {b:alice\n}"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "empty group name returns parse error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:\nb:alice\n}"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "empty buddy name returns parse error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:\n}"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "group with no buddies does not call UpsertItem",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:EmptyGroup\n}"),
- wantMsg: nil,
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- {
- name: "feedbag upsert send error on first insert returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:alice\n}"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
- },
- {
- Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
- },
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
- },
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- err: errors.New("EOF"),
- },
- },
- },
- },
- },
- {
- name: "buddy already in group skips and adds only new buddy",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\n}"),
- wantMsg: []string{"NEW_BUDDY_REPLY2:mk6i:added"},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "all buddies already in group returns no replies and no UpsertItem",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:mike\nb:mk6i\n}"),
- wantMsg: nil,
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}}},
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mike", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 17724, Name: "mk6i", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- {
- name: "single buddy in new group",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:alice\n}"),
- wantMsg: []string{"NEW_BUDDY_REPLY2:alice:added"},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
- },
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 1, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "buddy with alias and note",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_buddies {g:Buddies\nb:bob:Bob Smith:::::Friend from work\n}"),
- wantMsg: []string{"NEW_BUDDY_REPLY2:bob:added"},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
- },
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob",
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesAlias, "Bob Smith"),
- wire.NewTLVBE(wire.FeedbagAttributesNote, "Friend from work"),
- },
- },
- },
- },
- err: nil,
- },
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_NewGroup(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "add new group when feedbag has root and existing groups",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_group Family"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 1})}},
- },
- {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Family"},
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "add new group when feedbag is empty creates root and group",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_group Buddies"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdGroup,
- GroupID: 0,
- Name: "",
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})},
- },
- },
- {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Buddies"},
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "group already exists returns success idempotently",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_group Buddies"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- {
- name: "empty group name returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_group"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{},
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- {
- name: "feedbag upsert send error returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_new_group Family"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 1})}},
- },
- {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Family"},
- },
- err: assert.AnError,
- },
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_DelGroup(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "delete existing group",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_del_group Family"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 29709})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1}),
- },
- },
- },
- {Name: "Jane", GroupID: 17724, ItemID: 1, ClassID: wire.FeedbagClassIdBuddy},
- {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3}),
- },
- },
- },
- {Name: "Joe", GroupID: 29709, ItemID: 2, ClassID: wire.FeedbagClassIdBuddy},
- {Name: "Fred", GroupID: 29709, ItemID: 3, ClassID: wire.FeedbagClassIdBuddy},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2, 3}),
- },
- },
- },
- {Name: "Joe", GroupID: 29709, ItemID: 2, ClassID: wire.FeedbagClassIdBuddy},
- {Name: "Fred", GroupID: 29709, ItemID: 3, ClassID: wire.FeedbagClassIdBuddy},
- },
- },
- err: nil,
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "empty group name returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_del_group"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{},
- deleteFeedbagParams: deleteFeedbagParams{},
- },
- },
- },
- {
- name: "group not found returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_del_group Nonexistent"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{},
- },
- },
- },
- {
- name: "feedbag delete send error returns error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_del_group Family"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{17724, 29709})}},
- },
- {Name: "Buddies", GroupID: 17724, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {Name: "Family", GroupID: 29709, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- err: assert.AnError,
- },
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_SetPDMode(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "empty feedbag creates new Pdinfo with mode 4",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_set_pdmode 4"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- GroupID: 0,
- ItemID: 1,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(4)),
- },
- },
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "pdinfo already exists but does not have FeedbagAttributesPdMode",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_set_pdmode 2"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- GroupID: 0,
- ItemID: 99,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{},
- },
- },
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- GroupID: 0,
- ItemID: 99,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(2)),
- },
- },
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "pdinfo already exists and already has FeedbagAttributesPdMode with same mode",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_set_pdmode 3"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- GroupID: 0,
- ItemID: 99,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(3)),
- },
- },
- },
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- {
- name: "pdinfo already exists and already has FeedbagAttributesPdMode with different mode",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_set_pdmode 3"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- GroupID: 0,
- ItemID: 99,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(1)),
- },
- },
- },
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- GroupID: 0,
- ItemID: 99,
- TLVLBlock: wire.TLVLBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(3)),
- },
- },
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_AddPermit2(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "no screennames provided returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_permit"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "feedbag upsert send error returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_permit alice"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- err: errors.New("EOF"),
- },
- },
- },
- },
- },
- {
- name: "add permits to empty feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_permit alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "skip existing permit, add only new",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_permit alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "no-op when permit already in feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_permit alice"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_AddDeny2(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "no screennames provided returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_deny"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "feedbag upsert send error returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_deny alice"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- err: errors.New("EOF"),
- },
- },
- },
- },
- },
- {
- name: "add deny to empty feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_deny alice"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "skip existing deny, add only new",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_deny alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
- items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "no-op when deny already in feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_add_deny alice"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_RemoveBuddy2(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "removes buddy from group order TLV and upserts group",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_buddy friend2 Buddies"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2, 3})}},
- },
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend1", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend2", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 3, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend3", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend2", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: nil,
- },
- },
- upsertFeedbagParams: upsertFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagUpdateItem},
- items: []wire.FeedbagItem{
- {
- Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 3})}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "parseArgs error returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_buddy \"unclosed"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "missing params returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_buddy friend1"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "group not found returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_buddy friend1 NoSuchGroup"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{},
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- {
- name: "no-op when buddy not in group",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_buddy stranger Buddies"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend1", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{},
- upsertFeedbagParams: upsertFeedbagParams{},
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_RemovePermit2(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "no screennames provided returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_permit"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "feedbag delete send error returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_permit alice"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: errors.New("EOF"),
- },
- },
- },
- },
- },
- {
- name: "remove permits from feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_permit alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "remove only existing permits",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_permit alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDPermit, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "no-op when no permits in feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_permit alice"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{},
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_RemoveDeny2(t *testing.T) {
- cases := []struct {
- name string
- me *state.SessionInstance
- givenCmd []byte
- wantMsg []string
- mockParams mockParams
- }{
- {
- name: "no screennames provided returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_deny"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{},
- },
- {
- name: "feedbag delete send error returns internal error",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_deny alice"),
- wantMsg: []string{cmdInternalSvcErr},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: errors.New("EOF"),
- },
- },
- },
- },
- },
- {
- name: "remove denies from feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_deny alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "bob", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "remove only existing denies",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_deny alice bob"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{
- {
- frame: wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagDeleteItem},
- inBody: wire.SNAC_0x13_0x0A_FeedbagDeleteItem{
- Items: []wire.FeedbagItem{
- {ItemID: 1, ClassID: wire.FeedbagClassIDDeny, GroupID: 0, Name: "alice", TLVLBlock: wire.TLVLBlock{}},
- },
- },
- err: nil,
- },
- },
- },
- },
- },
- {
- name: "no-op when no denies in feedbag",
- me: newTestSession("me"),
- givenCmd: []byte("toc2_remove_deny alice"),
- wantMsg: []string{},
- mockParams: mockParams{
- feedBagParams: feedBagParams{
- feedbagParams: feedbagParams{
- {
- results: []wire.FeedbagItem{},
- },
- },
- deleteFeedbagParams: deleteFeedbagParams{},
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- me, bos := newTestTOCConn(tc.me)
- me.isTOC2 = true
- for _, params := range tc.mockParams.feedbagParams {
- me.feedbag.reset(params.results)
- }
- wantSNACs, sendErrs := feedbagSNACs(tc.mockParams.feedBagParams)
- bos.sendErrs = sendErrs
- // Deterministic item ID: increment by 1 per call so IDs are unique and predictable per test.
- var randCall int
- randIntn := func(n int) int {
- randCall++
- return randCall
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: randIntn,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Equal(t, wantSNACs, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_GetDirSearchURL(t *testing.T) {
- cases := []struct {
- // name is the unit test name
- name string
- // me is the TOC user session
- me *state.SessionInstance
- // givenCmd is the TOC command
- givenCmd []byte
- // wantMsg is the expected TOC response
- wantMsg []string
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- }{
- {
- name: "successfully request user info",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_dir_search "first \[name\]":"middle name":"last name":"maiden name":"city":"state":"country":"email"`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnData: []byte("monster"),
- },
- },
- },
- },
- 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"},
- },
- {
- name: "successfully request user info by keywords",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_dir_search ::::::::::"searchkw"`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnData: []byte("monster"),
- },
- },
- },
- },
- wantMsg: []string{"GOTO_URL:search results:dir_search?cookie=6d6f6e73746572&keyword=searchkw"},
- },
- {
- name: "request user info with too many params",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_dir_search ::::::::::::::::::::"searchkw"`),
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "request user info, get cookie issue error",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_dir_search them`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnErr: io.EOF,
- },
- },
- },
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_dir_search`),
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- cookieBaker := newMockCookieBaker(t)
- for _, params := range tc.mockParams.issueParams {
- cookieBaker.EXPECT().
- Issue(params.data, state.DefaultCookieTTL).
- Return(params.returnData, params.returnErr)
- }
- me, bos := newTestTOCConn(tc.me)
- svc := OSCARProxy{
- Logger: slog.Default(),
- CookieBaker: cookieBaker,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Empty(t, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_GetDirURL(t *testing.T) {
- cases := []struct {
- // name is the unit test name
- name string
- // me is the TOC user session
- me *state.SessionInstance
- // givenCmd is the TOC command
- givenCmd []byte
- // wantMsg is the expected TOC response
- wantMsg []string
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- }{
- {
- name: "successfully request user dir info",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_get_dir them`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnData: []byte("monster"),
- },
- },
- },
- },
- wantMsg: []string{"GOTO_URL:directory info:dir_info?cookie=6d6f6e73746572&user=them"},
- },
- {
- name: "request user info, get cookie issue error",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_get_dir them`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnErr: io.EOF,
- },
- },
- },
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_get_dir`),
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- cookieBaker := newMockCookieBaker(t)
- for _, params := range tc.mockParams.issueParams {
- cookieBaker.EXPECT().
- Issue(params.data, state.DefaultCookieTTL).
- Return(params.returnData, params.returnErr)
- }
- me, bos := newTestTOCConn(tc.me)
- svc := OSCARProxy{
- Logger: slog.Default(),
- CookieBaker: cookieBaker,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Empty(t, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_GetInfoURL(t *testing.T) {
- cases := []struct {
- // name is the unit test name
- name string
- // me is the TOC user session
- me *state.SessionInstance
- // givenCmd is the TOC command
- givenCmd []byte
- // wantMsg is the expected TOC response
- wantMsg []string
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- }{
- {
- name: "successfully request user info",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_get_info them`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnData: []byte("monster"),
- },
- },
- },
- },
- wantMsg: []string{"GOTO_URL:profile:info?cookie=6d6f6e73746572&from=me&user=them"},
- },
- {
- name: "request user info, get cookie issue error",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_get_info them`),
- mockParams: mockParams{
- cookieBakerParams: cookieBakerParams{
- issueParams: issueParams{
- {
- data: []byte("me"),
- returnErr: io.EOF,
- },
- },
- },
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_get_info`),
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- cookieBaker := newMockCookieBaker(t)
- for _, params := range tc.mockParams.issueParams {
- cookieBaker.EXPECT().
- Issue(params.data, state.DefaultCookieTTL).
- Return(params.returnData, params.returnErr)
- }
- me, bos := newTestTOCConn(tc.me)
- svc := OSCARProxy{
- Logger: slog.Default(),
- CookieBaker: cookieBaker,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Empty(t, bos.sentSNACs())
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_SetConfig(t *testing.T) {
- cases := []struct {
- // name is the unit test name
- name string
- // me is the TOC user session
- me *state.SessionInstance
- // givenCmd is the TOC command
- givenCmd []byte
- // wantMsg is the expected TOC response
- wantMsg []string
- // mockParams is the list of params sent to mocks that satisfy this
- // method's dependencies
- mockParams mockParams
- }{
- {
- name: "successfully set permit all config (unquoted)",
- me: newTestSession("me"),
- givenCmd: []byte("toc_set_config {m 1\ng Buddies\nb friend1\nb friend2\n}\n"),
- mockParams: mockParams{
- tocConfigParams: tocConfigParams{
- setTOCConfigParams: setTOCConfigParams{
- {
- user: state.NewIdentScreenName("me"),
- config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
- },
- },
- },
- },
- wantMsg: []string{},
- },
- {
- name: "successfully set permit all config (double-quoted)",
- me: newTestSession("me"),
- givenCmd: []byte("toc_set_config \"{m 1\ng Buddies\nb friend1\nb friend2\n}\n\""),
- mockParams: mockParams{
- tocConfigParams: tocConfigParams{
- setTOCConfigParams: setTOCConfigParams{
- {
- user: state.NewIdentScreenName("me"),
- config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
- },
- },
- },
- },
- wantMsg: []string{},
- },
- {
- name: "successfully set permit all config (single-quoted)",
- me: newTestSession("me"),
- givenCmd: []byte("toc_set_config '{m 1\ng Buddies\nb friend1\nb friend2\n}\n'"),
- mockParams: mockParams{
- tocConfigParams: tocConfigParams{
- setTOCConfigParams: setTOCConfigParams{
- {
- user: state.NewIdentScreenName("me"),
- config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
- },
- },
- },
- },
- wantMsg: []string{},
- },
- {
- name: "successfully set permit all config (double-quoted with spaces)",
- me: newTestSession("me"),
- givenCmd: []byte("toc_set_config \" {m 1\ng Buddies\nb friend1\nb friend2\n}\n \""),
- mockParams: mockParams{
- tocConfigParams: tocConfigParams{
- setTOCConfigParams: setTOCConfigParams{
- {
- user: state.NewIdentScreenName("me"),
- config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
- },
- },
- },
- },
- wantMsg: []string{},
- },
- {
- name: "set config, receive error from toc config store",
- me: newTestSession("me"),
- givenCmd: []byte("toc_set_config {m 1\ng Buddies\nb friend1\nb friend2\n}\n"),
- mockParams: mockParams{
- tocConfigParams: tocConfigParams{
- setTOCConfigParams: setTOCConfigParams{
- {
- user: state.NewIdentScreenName("me"),
- config: "{m 1\ng Buddies\nb friend1\nb friend2\n}\n",
- err: io.EOF,
- },
- },
- },
- },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "bad command",
- me: newTestSession("me"),
- givenCmd: []byte(`toc_set_config`),
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- tocConfigSvc := newMockTOCConfigStore(t)
- for _, params := range tc.mockParams.setTOCConfigParams {
- tocConfigSvc.EXPECT().
- SetTOCConfig(matchContext(), params.user, params.config).
- Return(params.err)
- }
- me, bos := newTestTOCConn(tc.me)
- svc := OSCARProxy{
- Logger: slog.Default(),
- TOCConfigStore: tocConfigSvc,
- }
- msg := svc.RecvClientCmd(ctx, me, tc.givenCmd)
- assert.Equal(t, tc.wantMsg, msg)
- assert.Empty(t, bos.sentSNACs())
- })
- }
- }
- func Test_parseArgs(t *testing.T) {
- type testCase struct {
- name string
- givenPayload string
- givenArgs []*string
- wantVarArgs []string
- wantArgs []string
- wantErrMsg string
- }
- tests := []testCase{
- {
- name: "no positional args or varargs",
- givenPayload: ``,
- givenArgs: nil,
- wantVarArgs: []string{},
- },
- {
- name: "positional args with varargs",
- givenPayload: `1234 "Join me!" user1 user2 user3`,
- givenArgs: []*string{new(string), new(string)},
- wantVarArgs: []string{"user1", "user2", "user3"},
- wantArgs: []string{"1234", "Join me!"},
- },
- {
- name: "nil positional argument placeholders should get skipped",
- givenPayload: `1234 "Join me!" user1 user2 user3`,
- givenArgs: []*string{nil, nil}, // still 2 placeholders, both nil
- wantVarArgs: []string{"user1", "user2", "user3"},
- wantArgs: []string{"", ""},
- },
- {
- name: "positional args with no varargs",
- givenPayload: `1234 "Join me!"`,
- givenArgs: []*string{new(string), new(string)}, // roomID + msg
- wantVarArgs: []string{},
- wantArgs: []string{"1234", "Join me!"},
- },
- {
- name: "varargs only",
- givenPayload: `user1 user2 user3`,
- givenArgs: nil,
- wantVarArgs: []string{"user1", "user2", "user3"},
- },
- {
- name: "too many positional arg placeholders",
- givenPayload: `toc_chat_invite`,
- givenArgs: []*string{new(string), new(string)},
- wantVarArgs: []string{},
- wantErrMsg: "command contains fewer arguments than expected",
- },
- {
- name: "CSV parser error",
- givenPayload: ``,
- givenArgs: []*string{nil},
- wantVarArgs: []string{},
- wantErrMsg: "CSV reader error",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- varArgs, err := parseArgs([]byte(tt.givenPayload), tt.givenArgs...)
- if tt.wantErrMsg != "" {
- assert.ErrorContains(t, err, tt.wantErrMsg)
- return
- }
- assert.NoError(t, err)
- // verify the placeholder pointers got populated
- for i, want := range tt.wantArgs {
- if want == "" {
- assert.Nil(t, tt.givenArgs[i])
- } else {
- got := *tt.givenArgs[i]
- assert.Equal(t, want, got)
- }
- }
- // verify we have the same varargs
- assert.Equal(t, tt.wantVarArgs, varArgs)
- assert.Equal(t, len(tt.wantArgs), len(tt.givenArgs))
- })
- }
- }
- func TestBuildToc2Config(t *testing.T) {
- tests := []struct {
- name string
- fb []wire.FeedbagItem
- want []string // CONFIG2 command string(s), each starts with "CONFIG2:"
- wantErr string
- }{
- {
- name: "root group missing order attribute returns error",
- fb: []wire.FeedbagItem{
- {Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup, TLVLBlock: wire.TLVLBlock{}},
- },
- wantErr: "root group missing order attribute",
- },
- {
- name: "empty buddylist yields only done",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
- },
- },
- want: []string{"CONFIG2:done:\n"},
- },
- {
- name: "single group with buddies",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
- },
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}}},
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "user1", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "user2", TLVLBlock: wire.TLVLBlock{}},
- },
- want: []string{"CONFIG2:g:Buddies\nb:user1\nb:user2\ndone:\n"},
- },
- {
- name: "blocked user (d:) and private user (p:)",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
- },
- {ClassID: wire.FeedbagClassIDDeny, Name: "blockeduser"},
- {ClassID: wire.FeedbagClassIDPermit, Name: "allowuser"},
- },
- want: []string{"CONFIG2:d:blockeduser\np:allowuser\ndone:\n"},
- },
- {
- name: "privacy level (m:) from pdinfo",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
- },
- {
- ClassID: wire.FeedbagClassIdPdinfo,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesPdMode, uint8(3))}},
- },
- },
- want: []string{"CONFIG2:m:3\ndone:\n"},
- },
- {
- name: "buddy with alias",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
- },
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
- {
- ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "bob",
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesAlias, "Bob Smith")}},
- },
- },
- want: []string{"CONFIG2:g:Buddies\nb:bob:Bob Smith\ndone:\n"},
- },
- {
- name: "buddy with note (comment) uses five colons before note",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
- },
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
- {
- ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "alice",
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesNote, "Friend from work")}},
- },
- },
- want: []string{"CONFIG2:g:Buddies\nb:alice:::::Friend from work\ndone:\n"},
- },
- {
- name: "multiple groups in root order",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100, 200})}},
- },
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}}},
- {Name: "Family", GroupID: 200, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2})}}},
- {ItemID: 1, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend1", TLVLBlock: wire.TLVLBlock{}},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 200, Name: "mom", TLVLBlock: wire.TLVLBlock{}},
- },
- want: []string{"CONFIG2:g:Buddies\nb:friend1\ng:Family\nb:mom\ndone:\n"},
- },
- {
- name: "group order listing a missing buddy skips it",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100})}},
- },
- {Name: "Buddies", GroupID: 100, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1, 2})}}},
- {ItemID: 2, ClassID: wire.FeedbagClassIdBuddy, GroupID: 100, Name: "friend2", TLVLBlock: wire.TLVLBlock{}},
- },
- want: []string{"CONFIG2:g:Buddies\nb:friend2\ndone:\n"},
- },
- {
- name: "root order listing a missing group skips it",
- fb: []wire.FeedbagItem{
- {
- Name: "", GroupID: 0, ItemID: 0, ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{100, 200})}},
- },
- {Name: "Family", GroupID: 200, ItemID: 0, ClassID: wire.FeedbagClassIdGroup},
- },
- want: []string{"CONFIG2:g:Family\ndone:\n"},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := buildToc2Config(tt.fb)
- if tt.wantErr != "" {
- assert.ErrorContains(t, err, tt.wantErr)
- assert.Empty(t, got)
- return
- }
- assert.NoError(t, err)
- assert.Equal(t, tt.want, got)
- })
- }
- }
- // TestUnescape tests the unescape function
- func TestUnescape(t *testing.T) {
- tests := []struct {
- name string
- input string
- expected string
- }{
- {"No Escapes", "Hello World", "Hello World"},
- {"Escaped Brace", "Hello \\{World\\}", "Hello {World}"},
- {"Escaped Parentheses", "Test\\(123\\)", "Test(123)"},
- {"Escaped Brackets", "\\[List\\]", "[List]"},
- {"Escaped Dollar", "Price: \\$100", "Price: $100"},
- {"Escaped Quote", "She said \\\"Hello\\\"", "She said \"Hello\""},
- {"Multiple Escapes", "One\\, Two\\, Three", "One, Two, Three"},
- {"Consecutive Escapes", "\\\\\\$100", "\\$100"},
- {"Only Escape Character", "\\", ""},
- {"Empty Input", "", ""},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := unescape(tt.input)
- if result != tt.expected {
- t.Errorf("unescape(%q) = %q; want %q", tt.input, result, tt.expected)
- }
- })
- }
- }
- func TestOSCARProxy_Signon(t *testing.T) {
- clientAddr := &net.TCPAddr{IP: net.IPv4(10, 0, 0, 5), Port: 4321}
- roastedPass := []byte{0x24, 0x08, 0x10, 0x5c, 0x23, 0x00, 0x11, 0x30}
- cookie := []byte("the-cookie")
- // bosReply answers signon queries the way the BOS server does.
- bosReply := func(feedbag []wire.FeedbagItem) func(frame wire.SNACFrame) []wire.SNACMessage {
- return func(frame wire.SNACFrame) []wire.SNACMessage {
- switch {
- case frame.FoodGroup == wire.OService && frame.SubGroup == wire.OServiceUserInfoQuery:
- return []wire.SNACMessage{newSNAC(wire.OService, wire.OServiceUserInfoUpdate, frame.RequestID,
- wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
- UserInfo: []wire.TLVUserInfo{{ScreenName: "M E"}},
- })}
- case frame.FoodGroup == wire.Feedbag && frame.SubGroup == wire.FeedbagQuery:
- return []wire.SNACMessage{newSNAC(wire.Feedbag, wire.FeedbagReply, frame.RequestID,
- wire.SNAC_0x13_0x06_FeedbagReply{Items: feedbag})}
- }
- return nil
- }
- }
- signonSNACs := func(t *testing.T) []receivedSNAC {
- return []receivedSNAC{
- newReceivedSNAC(t, wire.OService, wire.OServiceRateParamsSubAdd, 1, wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
- ClassIDs: []uint16{1, 2, 3, 4, 5},
- }),
- newReceivedSNAC(t, wire.Locate, wire.LocateSetInfo, 2, wire.SNAC_0x02_0x04_LocateSetInfo{
- TLVRestBlock: wire.TLVRestBlock{
- TLVList: wire.TLVList{
- wire.NewTLVBE(wire.LocateTLVTagsInfoCapabilities, []uuid.UUID{wire.CapChat}),
- },
- },
- }),
- newReceivedSNAC(t, wire.OService, wire.OServiceUserInfoQuery, 3, struct{}{}),
- }
- }
- authOK := []wire.TLV{
- wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
- wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, cookie),
- }
- toc1AuthTLVs := wire.TLVList{
- wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me"),
- wire.NewTLVBE(wire.LoginTLVTagsRoastedTOCPassword, roastedPass),
- }
- toc2AuthTLVs := append(append(wire.TLVList{}, toc1AuthTLVs...),
- wire.NewTLVBE(wire.LoginTLVTagsMultiConnFlags, wire.MultiConnFlagsRecentClient))
- cases := []struct {
- // name is the unit test name
- name string
- // givenCmd is the signon command
- givenCmd string
- // authReply are the TLVs the auth service replies with, nil when TOC
- // shouldn't connect to it
- authReply []wire.TLV
- // bos is the BOS service, nil when TOC shouldn't connect to it
- bos *fakeService
- // tocConfig is the stored TOC1 config, or tocConfigErr its lookup error
- tocConfig *state.User
- tocConfigErr error
- // wantAuthTLVs are the TLVs TOC sends the auth service
- wantAuthTLVs wire.TLVList
- // wantBOSSNACs returns the SNACs TOC sends the BOS service
- wantBOSSNACs func(t *testing.T) []receivedSNAC
- // wantMsg is the expected TOC response
- wantMsg []string
- // wantTOC2 and wantMsgEnc are the TOC version of a successful signon
- wantTOC2, wantMsgEnc bool
- // wantBacklog is the number of BOS SNACs kept for RecvBOS
- wantBacklog int
- // wantFeedbag is the feedbag mirror after signon
- wantFeedbag []wire.FeedbagItem
- // bucpAuth makes the auth service reply the way it does to an address
- // that recently used BUCP login
- bucpAuth bool
- }{
- {
- name: "successfully login TOC1",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: authOK,
- bos: &fakeService{reply: bosReply(nil)},
- tocConfig: &state.User{TOCConfig: "m 1\ng Buddies"},
- wantAuthTLVs: toc1AuthTLVs,
- wantBOSSNACs: signonSNACs,
- wantMsg: []string{"SIGN_ON:TOC1.0", "CONFIG:m 1\ng Buddies", "NICK:M E"},
- },
- {
- name: "successfully login TOC2",
- givenCmd: `toc2_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: authOK,
- bos: &fakeService{reply: bosReply(nil)},
- wantAuthTLVs: toc2AuthTLVs,
- wantBOSSNACs: func(t *testing.T) []receivedSNAC {
- return append(signonSNACs(t),
- newReceivedSNAC(t, wire.Feedbag, wire.FeedbagUse, 4, struct{}{}),
- newReceivedSNAC(t, wire.Feedbag, wire.FeedbagQuery, 5, struct{}{}),
- )
- },
- wantMsg: []string{"SIGN_ON:TOC2.0", "NICK:M E", "CONFIG2:done:\n"},
- wantTOC2: true,
- },
- {
- name: "successfully login toc2_login (TOC2 with encoded messaging)",
- givenCmd: `toc2_login localhost 9898 me 0x2408105c23001130 english "TIC:TOC" 160 US "" "" 3 0 30303 -kentucky -utf8 103547776`,
- authReply: authOK,
- bos: &fakeService{reply: bosReply([]wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
- },
- {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Buddies"},
- })},
- wantAuthTLVs: toc2AuthTLVs,
- wantBOSSNACs: func(t *testing.T) []receivedSNAC {
- return append(signonSNACs(t),
- newReceivedSNAC(t, wire.Feedbag, wire.FeedbagUse, 4, struct{}{}),
- newReceivedSNAC(t, wire.Feedbag, wire.FeedbagQuery, 5, struct{}{}),
- )
- },
- wantMsg: []string{"SIGN_ON:TOC2.0", "NICK:M E", "CONFIG2:g:Buddies\ndone:\n"},
- wantTOC2: true,
- wantMsgEnc: true,
- wantFeedbag: []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{1})}},
- },
- {ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Buddies"},
- },
- },
- {
- name: "SNACs that arrive during signon are kept for later",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: authOK,
- bos: &fakeService{
- greeting: []wire.SNACMessage{
- newSNAC(wire.Buddy, wire.BuddyArrived, wire.ReqIDFromServer, wire.SNAC_0x03_0x0B_BuddyArrived{
- TLVUserInfo: wire.TLVUserInfo{ScreenName: "them"},
- }),
- },
- reply: bosReply(nil),
- },
- tocConfig: &state.User{},
- wantAuthTLVs: toc1AuthTLVs,
- wantBOSSNACs: signonSNACs,
- wantMsg: []string{"SIGN_ON:TOC1.0", "CONFIG:", "NICK:M E"},
- wantBacklog: 1,
- },
- {
- name: "login with bad credentials",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: []wire.TLV{wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrInvalidUsernameOrPassword)},
- wantAuthTLVs: toc1AuthTLVs,
- wantMsg: []string{"ERROR:" + wire.TOCErrorAuthIncorrectNickOrPassword},
- },
- {
- name: "login rate limited",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: []wire.TLV{wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrRateLimitExceeded)},
- wantAuthTLVs: toc1AuthTLVs,
- wantMsg: []string{"ERROR:" + wire.TOCErrorAuthRatedFromLogin},
- },
- {
- name: "login rate limited at an address that recently used BUCP login",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: []wire.TLV{wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrRateLimitExceeded)},
- bucpAuth: true,
- wantAuthTLVs: toc1AuthTLVs,
- wantMsg: []string{"ERROR:" + wire.TOCErrorAuthRatedFromLogin},
- },
- {
- name: "login reply missing cookie",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: []wire.TLV{wire.NewTLVBE(wire.LoginTLVTagsScreenName, "me")},
- wantAuthTLVs: toc1AuthTLVs,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "BOS service refuses the session",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: authOK,
- bos: &fakeService{refuse: true},
- wantAuthTLVs: toc1AuthTLVs,
- wantBOSSNACs: func(t *testing.T) []receivedSNAC { return nil },
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "user info query fails",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: authOK,
- bos: &fakeService{reply: func(frame wire.SNACFrame) []wire.SNACMessage {
- if frame.SubGroup == wire.OServiceUserInfoQuery {
- return []wire.SNACMessage{newSNAC(wire.OService, wire.OServiceErr, frame.RequestID,
- wire.SNACError{Code: wire.ErrorCodeServiceUnavailable})}
- }
- return nil
- }},
- wantAuthTLVs: toc1AuthTLVs,
- wantBOSSNACs: signonSNACs,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "TOC1 config lookup fails",
- givenCmd: `toc_signon localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- authReply: authOK,
- bos: &fakeService{reply: bosReply(nil)},
- tocConfigErr: io.EOF,
- wantAuthTLVs: toc1AuthTLVs,
- wantBOSSNACs: signonSNACs,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "unknown signon command",
- givenCmd: `toc_bogus localhost 9898 me 0x2408105c23001130 english "TIC:TOC"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "password too short",
- givenCmd: `toc_signon localhost 9898 me 0x english "TIC:TOC"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- {
- name: "password not hex",
- givenCmd: `toc_signon localhost 9898 me 0xzz english "TIC:TOC"`,
- wantMsg: []string{cmdInternalSvcErr},
- },
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- ctx := context.Background()
- var gotAuth wire.FLAPSignonFrame
- oscarServer := &fakeOSCARServer{}
- switch {
- case tc.bucpAuth:
- oscarServer.handlers = append(oscarServer.handlers, fakeAuthBUCP(&gotAuth, tc.authReply...))
- case tc.authReply != nil:
- oscarServer.handlers = append(oscarServer.handlers, fakeAuth(&gotAuth, tc.authReply...))
- }
- if tc.bos != nil {
- tc.bos.ended = make(chan struct{})
- oscarServer.handlers = append(oscarServer.handlers, tc.bos.handle)
- }
- tocConfigStore := newMockTOCConfigStore(t)
- if tc.tocConfig != nil || tc.tocConfigErr != nil {
- tocConfigStore.EXPECT().
- User(ctx, state.NewIdentScreenName("me")).
- Return(tc.tocConfig, tc.tocConfigErr)
- }
- svc := OSCARProxy{
- Logger: slog.Default(),
- OSCARServer: oscarServer,
- TOCConfigStore: tocConfigStore,
- }
- me, msg := svc.Signon(ctx, []byte(tc.givenCmd), clientAddr)
- assert.Equal(t, tc.wantMsg, msg)
- if tc.authReply != nil {
- assert.Equal(t, tc.wantAuthTLVs, gotAuth.TLVList)
- }
- if tc.bos != nil {
- gotCookie, gotSNACs := tc.bos.received()
- assert.Equal(t, cookie, gotCookie)
- assert.Equal(t, tc.wantBOSSNACs(t), gotSNACs)
- }
- oscarServer.mu.Lock()
- assert.Empty(t, oscarServer.handlers, "TOC didn't open every expected connection")
- for _, addr := range oscarServer.clientAddrs {
- assert.Equal(t, clientAddr, addr)
- }
- oscarServer.mu.Unlock()
- if me == nil {
- if tc.bos != nil {
- // a failed signon doesn't leave the user signed on
- select {
- case <-tc.bos.ended:
- case <-time.After(time.Second):
- t.Fatal("BOS connection still open after failed signon")
- }
- }
- return
- }
- assert.Equal(t, state.NewIdentScreenName("me"), me.IdentScreenName())
- assert.Equal(t, tc.wantTOC2, me.IsTOC2())
- assert.Equal(t, tc.wantMsgEnc, me.SupportsTOC2MsgEnc())
- assert.Len(t, me.backlog, tc.wantBacklog)
- assert.Equal(t, tc.wantFeedbag, me.feedbag.snapshot())
- me.bos.Close()
- })
- }
- }
- func TestOSCARProxy_RecvClientCmd_LocalRateLimit(t *testing.T) {
- cookieBaker := newMockCookieBaker(t)
- cookieBaker.EXPECT().
- Issue([]byte("me"), state.DefaultCookieTTL).
- Return([]byte("cookie"), nil).
- Times(5)
- me, bos := newTestTOCConn(newTestSession("me"))
- svc := OSCARProxy{
- Logger: slog.Default(),
- CookieBaker: cookieBaker,
- }
- // commands that don't reach the OSCAR server are limited locally
- for range 5 {
- msg := svc.RecvClientCmd(context.Background(), me, []byte("toc_get_info them"))
- assert.Len(t, msg, 1)
- assert.Contains(t, msg[0], "GOTO_URL:profile:")
- }
- msg := svc.RecvClientCmd(context.Background(), me, []byte("toc_get_info them"))
- assert.Equal(t, []string{rateLimitExceededErr}, msg)
- assert.Empty(t, bos.sentSNACs())
- }
- func TestOSCARProxy_RecvClientCmd_FeedbagMirrorFollowsChanges(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- me.isTOC2 = true
- me.feedbag.reset([]wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdGroup,
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{})}},
- },
- })
- var randCall int
- svc := OSCARProxy{
- Logger: slog.Default(),
- RandIntn: func(n int) int {
- randCall++
- return randCall
- },
- }
- // the second command sees the group the first one added
- assert.Equal(t, []string{}, svc.RecvClientCmd(context.Background(), me, []byte("toc2_new_group Family")))
- assert.Equal(t, []string{"NEW_BUDDY_REPLY2:alice:added"},
- svc.RecvClientCmd(context.Background(), me, []byte("toc2_new_buddies {g:Family\nb:alice\n}")))
- sent := bos.sentSNACs()
- assert.Len(t, sent, 3)
- assert.Equal(t, wire.FeedbagInsertItem, sent[0].Frame.SubGroup)
- assert.Equal(t, wire.FeedbagUpdateItem, sent[1].Frame.SubGroup)
- assert.Equal(t, []wire.FeedbagItem{
- {
- ClassID: wire.FeedbagClassIdGroup,
- GroupID: 1,
- Name: "Family",
- TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{2})}},
- },
- }, sent[1].Body.(wire.SNAC_0x13_0x09_FeedbagUpdateItem).Items)
- assert.Equal(t, wire.FeedbagInsertItem, sent[2].Frame.SubGroup)
- names := me.feedbag.groupNames()
- assert.Equal(t, "Family", names[1])
- // deleting the group removes it from the mirror
- assert.Equal(t, []string{}, svc.RecvClientCmd(context.Background(), me, []byte("toc2_del_group Family")))
- _, hasFamily := me.feedbag.groupNames()[1]
- assert.False(t, hasFamily)
- }
- func TestOSCARProxy_RecvClientCmd_TOC2CmdFromTOC1(t *testing.T) {
- cases := []struct {
- name string
- givenCmd string
- }{
- {name: "toc2_send_im", givenCmd: `toc2_send_im them "hello"`},
- {name: "toc2_client_event", givenCmd: `toc2_client_event them 1`},
- {name: "toc2_new_buddies", givenCmd: "toc2_new_buddies {g:Friends\nb:alice\n}"},
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- me, bos := newTestTOCConn(newTestSession("me"))
- svc := OSCARProxy{Logger: slog.Default()}
- msg := svc.RecvClientCmd(context.Background(), me, []byte(tc.givenCmd))
- assert.Equal(t, []string{cmdInternalSvcErr}, msg)
- assert.Empty(t, bos.sentSNACs())
- })
- }
- }
|