diff --git a/desk/app/bait.hoon b/desk/app/bait.hoon index 34251e9a..08f7ae70 100644 --- a/desk/app/bait.hoon +++ b/desk/app/bait.hoon @@ -5,6 +5,7 @@ +$ versioned-state $% state-0 state-1 + state-2 == :: +$ state-0 @@ -15,6 +16,10 @@ $: %1 token-metadata=(map [inviter=ship token=cord] metadata:reel) == ++$ state-2 + $: %2 + token-metadata=(map token:reel metadata:reel) + == -- :: |% @@ -53,7 +58,7 @@ == -- :: -=| state-1 +=| state-2 =* state - :: %- agent:dbug @@ -72,10 +77,22 @@ ^- (quip card _this) =/ old !<(versioned-state old-state) ?- -.old - %1 + %2 `this(state old) + :: + %1 + =/ new-metadata + %- ~(gas by *(map token:reel metadata:reel)) + %+ turn + ~(tap by token-metadata.old) + |= [[inviter=ship =token:reel] meta=metadata:reel] + =/ new-token + (rap 3 (scot %p inviter) '/' token ~) + [new-token meta] + `this(state [%2 new-metadata]) + :: %0 - `this(state *state-1) + `this(state *state-2) == :: ++ on-poke @@ -88,53 +105,97 @@ :_ this =/ full-line=request-line:server (parse-request-line:server url.request) =/ line - ?: ?=([%lure @ @ *] site.full-line) + ?: ?=([%lure @ *] site.full-line) t.site.full-line ?: ?=([@ @ *] site.full-line) site.full-line !! ?+ method.request (give not-found:gen:server) - %'GET' - ?: ?=([%bait %who ~] line) - (give (json-response:gen:server s+(scot %p our.bowl))) - =/ inviter (slav %p i.line) - =/ token i.t.line - =/ =metadata:reel (fall (~(get by token-metadata) [inviter token]) *metadata:reel) - ?: ?=([@ @ %metadata ~] line) - (give (json-response:gen:server (enjs-metadata metadata))) - (give (manx-response:gen:server (landing-page metadata))) + %'GET' (get-request line) + :: %'POST' - =/ inviter (slav %p i.line) - =/ token i.t.line ?~ body.request - (give not-found:gen:server) + (give-not-found 'body not found') ?. =('ship=%7E' (end [3 8] q.u.body.request)) - (give not-found:gen:server) + (give-not-found 'ship not found in body') =/ joiner (slav %p (cat 3 '~' (rsh [3 8] q.u.body.request))) - :* :* %pass /bite %agent [inviter %reel] - %poke %reel-bite !>([%bite-1 token joiner inviter]) - == - :* %pass /bite %agent [our.bowl %reel] - %poke %reel-bite !>([%bite-1 token joiner inviter]) - == - (give (manx-response:gen:server (sent-page joiner))) - == + =; [=bite:reel inviter=(unit ship)] + ?~ inviter + (give-not-found 'inviter not found') + ^- (list card) + :: TODO: figure out if we need to send both pokes + :* :* %pass /bite %agent [u.inviter %reel] + %poke %reel-bite !>(bite) + == + :* %pass /bite %agent [our.bowl %reel] + %poke %reel-bite !>(bite) + == + (give (manx-response:gen:server (sent-page joiner))) + == + =/ =(pole knot) line + ?: ?=([@ @ ~] line) + =/ inviter (slav %p i.line) + =/ old-token i.t.line + :_ `inviter + [%bite-1 old-token joiner inviter] + =/ token + ?~ ext.full-line i.line + (crip "{(trip i.line)}.{(trip u.ext.full-line)}") + =/ =metadata:reel (~(gut by token-metadata) token *metadata:reel) + ?~ type=(~(get by fields.metadata) 'bite-type') + ~|("no bite type for token: {}" !!) + ?> =('2' u.type) + :- [%bite-2 token joiner metadata] + ?~ inviter-field=(~(get by fields.metadata) 'inviter') ~ + `(slav %p u.inviter-field) == + ++ get-request + |= =(pole knot) + ^- (list card) + ?+ pole (give not-found:gen:server) + [%bait %who ~] + (give (json-response:gen:server s+(scot %p our.bowl))) + :: + [ship=@ name=@ %metadata ~] + =/ token (crip "{(trip ship.pole)}/{(trip name.pole)}") + =/ =metadata:reel + (~(gut by token-metadata) token *metadata:reel) + (give (json-response:gen:server (enjs-metadata metadata))) + :: + [token=@ %metadata ~] + =/ =metadata:reel + (~(gut by token-metadata) token.pole *metadata:reel) + (give (json-response:gen:server (enjs-metadata metadata))) + :: + [token=* ~] + =/ token (crip (join '/' pole)) + =/ =metadata:reel + (~(gut by token-metadata) token *metadata:reel) + (give (manx-response:gen:server (landing-page metadata))) + == :: + ++ give-not-found + |= body=cord + (give [[404 ~] `(as-octs:mimes:html body)]) ++ give |= =simple-payload:http (give-simple-payload:app:server id simple-payload) -- %bait-describe - =+ !<([token=cord =metadata:reel] vase) - `this(token-metadata (~(put by token-metadata) [src.bowl token] metadata)) + =+ !<([=nonce:reel =metadata:reel] vase) + =/ =token:reel (scot %uv (end [3 16] eny.bowl)) + :_ this(token-metadata (~(put by token-metadata) token metadata)) + =/ =cage reel-confirmation+!>([nonce token]) + ~[[%pass /confirm/[nonce] %agent [src.bowl %reel] %poke cage]] :: %bait-undescribe =+ !<(token=cord vase) - `this(token-metadata (~(del by token-metadata) [src.bowl token])) + `this(token-metadata (~(del by token-metadata) token)) + :: %bind-slash :_ this ~[[%pass /eyre/connect %arvo %e %connect [~ /] dap.bowl]] + :: %unbind-slash :_ this ~[[%pass /eyre/connect %arvo %e %connect [~ /] %docket]] diff --git a/desk/app/reel.hoon b/desk/app/reel.hoon index caea752a..78bf8edc 100644 --- a/desk/app/reel.hoon +++ b/desk/app/reel.hoon @@ -7,12 +7,17 @@ state-1 state-2 state-3 + state-4 == :: :: vic: URL of bait service :: civ: @p of bait service -:: our-metadata: map from tokens to their metadata -:: outstanding-pokes: ships we have poked and await a response from +:: our-metadata: a mapping from nonce/token to metadata +:: open-link-requests: open requests for an existing foreign link, v0 +:: lure links only +:: open-describes: attempts to create a link waiting to be assigned a token +:: stable-id: a mapping from something the client can use to identify the +:: metadata to nonce and/or token :: +$ state-0 $: %0 @@ -40,11 +45,21 @@ our-metadata=(map cord metadata:reel) outstanding-pokes=(set (pair ship cord)) == ++$ state-4 + $: %4 + vic=@t + civ=ship + our-metadata=(map token:reel metadata:reel) + open-link-requests=(set (pair ship cord)) + open-describes=(set token:reel) + stable-id=(map cord token:reel) + == +:: url with old style token ++ url-for-token - |= [vic=cord our=ship token=cord] - (crip "{(trip vic)}{(trip (scot %p our))}/{(trip token)}") + |= [vic=cord token=cord] + (cat 3 vic token) -- -=| state-3 +=| state-4 =* state - :: %- agent:dbug @@ -63,14 +78,16 @@ ^- (quip card _this) =/ old !<(versioned-state old-state) ?- -.old - %3 + %4 `this(state old) + %3 + `this(state [%4 vic.old civ.old our-metadata.old outstanding-pokes.old ~ ~]) %2 - `this(state [%3 vic.old civ.old our-metadata.old ~]) + `this(state [%4 vic.old civ.old our-metadata.old ~ ~ ~]) %1 - `this(state [%3 'https://tlon.network/lure/' ~loshut-lonreg ~ ~]) + `this(state [%4 'https://tlon.network/lure/' ~loshut-lonreg ~ ~ ~ ~]) %0 - `this(state [%3 'https://tlon.network/lure/' ~loshut-lonreg ~ ~]) + `this(state [%4 'https://tlon.network/lure/' ~loshut-lonreg ~ ~ ~ ~]) == :: ++ on-poke @@ -85,7 +102,9 @@ :_ this(vic vic.command) ~[[%pass /set-ship %arvo %k %fard q.byk.bowl %reel-set-ship %noun !>(vic.command)]] %set-ship - :_ this(civ civ.command) + :: since we're changing providers, we need to regenerate links + :: we'll use whatever key we currently have as the nonce + :_ this(civ civ.command, open-describes ~(key by our-metadata)) %+ turn ~(tap by our-metadata) |= [token=cord =metadata:reel] ^- card @@ -93,24 +112,62 @@ == :: %reel-bite + ?> =(civ src.bowl) =+ !<(=bite:reel vase) [[%give %fact ~[/bites] mark !>(bite)]~ this] :: %reel-describe - =+ !<([token=cord =metadata:reel] vase) - :_ this(our-metadata (~(put by our-metadata) token metadata)) - ~[[%pass /describe %agent [civ %bait] %poke %bait-describe !>([token metadata])]] + ?> =(our.bowl src.bowl) + =+ !<([id=cord =metadata:reel] vase) + ?~ (~(has by stable-id) id) `this + :: the token here is a temporary identifier for the metadata + :: a new one will be assigned by the bait provider and returned to us + =/ new-fields (~(put by fields.metadata) 'bite-type' '2') + =/ new-metadata metadata(fields new-fields) + =/ =nonce:reel (scot %da now.bowl) + =. our-metadata (~(put by our-metadata) nonce new-metadata) + =. open-describes (~(put in open-describes) nonce) + =. stable-id (~(put by stable-id) id nonce) + :_ this + ~[[%pass /describe %agent [civ %bait] %poke %bait-describe !>([nonce new-metadata])]] + :: + %reel-confirmation + ?> =(civ src.bowl) + =+ !<(confirmation:reel vase) + =. open-describes (~(del in open-describes) nonce) + ?~ md=(~(get by our-metadata) nonce) + ~|("no metadata for nonce: {}" !!) + =/ ids=(list [id=cord =token:reel]) + %+ skim + ~(tap by stable-id) + |= [key=cord =token:reel] + =(nonce token) + ?~ ids + ~|("no stable id for nonce: {}" !!) + =* id -<.ids + :: update the token the id points to + =. stable-id (~(put by stable-id) id token) + :: swap out the nonce for the token in our-metadata + =. our-metadata + (~(put by (~(del by our-metadata) nonce)) token u.md) + `this + :: %reel-undescribe - =+ !<(token=cord vase) + ?> =(our.bowl src.bowl) + =+ !<(=token:reel vase) + :: the token here should be the actual token given to us by the provider :_ this(our-metadata (~(del by our-metadata) token)) ~[[%pass /undescribe %agent [civ %bait] %poke %bait-undescribe !>(token)]] + :: old pokes for getting links, we no longer use these because all links + :: are unique to that ship/user and can be scried out + :: %reel-want-token-link - =+ !<(token=cord vase) + =+ !<(=token:reel vase) :_ this =/ result=(unit [cord cord]) ?. (~(has by our-metadata) token) `[token ''] - `[token (url-for-token vic our.bowl token)] - ~[[%pass [%token-link-want token ~] %agent [src.bowl %reel] %poke %reel-give-token-link !>(result)]] + `[token (url-for-token vic token)] + ~[[%pass [%token-link-want token ~] %agent [src dap]:bowl %poke %reel-give-token-link !>(result)]] %reel-give-token-link =+ !<(result=(unit [cord cord]) vase) ?~ result `this @@ -124,43 +181,69 @@ ++ on-agent |= [=wire =sign:agent:gall] ^- (quip card _this) - ?: ?=([%token-link @ @ ~] wire) + =/ =(pole knot) wire + ?+ pole (on-agent:def wire sign) + [%token-link @ name=@ ~] ?+ -.sign (on-agent:def wire sign) %poke-ack - `this(outstanding-pokes (~(del in outstanding-pokes) [src.bowl i.t.t.wire])) + `this(open-link-requests (~(del in open-link-requests) [src.bowl name.pole])) == - (on-agent:def wire sign) + == :: ++ on-watch - |= =path + |= =(pole knot) ^- (quip card _this) ?> =(our.bowl src.bowl) - ?+ path (on-watch:def path) - [%bites ~] `this - [%token-link @ @ ~] - =/ target (slav %p i.t.path) - =/ group i.t.t.path - ?~ (~(has in outstanding-pokes) [target group]) `this - :_ this(outstanding-pokes (~(put in outstanding-pokes) [target group])) - :~ [%pass path %agent [target %reel] %poke %reel-want-token-link !>(group)] - [%pass /expire/(scot %p our.bowl)/[group] %arvo %b [%wait (add ~h1 now.bowl)]] + =/ any ?(%v0 %v1) + =? pole !?=([any *] pole) + [%v0 pole] + ?+ pole ~|("bad pole: {}" (on-watch:def pole)) + [any %bites ~] `this + :: old subscription for getting links, we no longer use these because all + :: links are unique to that ship/user and can be scried out + :: + [%v0 %token-link ship=@ token=@ ~] + =/ ship (slav %p ship.pole) + =/ key [ship token.pole] + ?~ (~(has in open-link-requests) key) `this + :_ this(open-link-requests (~(put in open-link-requests) key)) + =/ =dock [ship dap.bowl] + =/ =cage reel-want-token-link+!>(token.pole) + :~ [%pass +.pole %agent dock %poke cage] + [%pass /expire/[ship.pole]/[token.pole] %arvo %b [%wait (add ~h1 now.bowl)]] == == :: ++ on-leave on-leave:def ++ on-peek - |= =path + |= =(pole knot) ^- (unit (unit cage)) - ?+ path [~ ~] - [%x %service ~] ``noun+!>(vic) - [%x %bait ~] ``reel-bait+!>([vic civ]) - :: - [%x %outstanding-poke @ @ ~] - ``json+!>([%b (~(has in outstanding-pokes) [(slav %p i.t.t.path) i.t.t.t.path])]) - :: - [%x %metadata @ ~] - =/ =metadata:reel (fall (~(get by our-metadata) i.t.t.path) *metadata:reel) + =/ any ?(%v0 %v1) + =? +.pole !?=([any *] +.pole) + [%v0 +.pole] + ?+ pole [~ ~] + [%x any %service ~] ``noun+!>(vic) + [%x any %bait ~] ``reel-bait+!>([vic civ]) + :: + [%x %v0 %outstanding-poke ship=@ name=@ ~] + =/ has (~(has in open-link-requests) [(slav %p ship.pole) name.pole]) + ``json+!>([%b has]) + :: + [%x any %metadata token=@ ~] + =/ =metadata:reel (fall (~(get by our-metadata) token.pole) *metadata:reel) ``reel-metadata+!>(metadata) + :: + [%x any %token-url token=*] + =/ =token:reel (crip (join '/' token.pole)) + =/ url (url-for-token vic token) + ``reel-token-url+!>(url) + :: + [%x %v1 %id-url id=*] + =/ id (crip (join '/' id.pole)) + ?~ token=(~(get by stable-id) id) + ``reel-token-url+!>('') + =/ url (cat 3 vic id) + ``reel-token-url+!>(url) == :: ++ on-arvo @@ -179,7 +262,9 @@ =/ target (slav %p i.t.wire) =/ group i.t.t.wire ?~ error.sign-arvo - `this(outstanding-pokes (~(del in outstanding-pokes) [target group])) + :_ this(open-link-requests (~(del in open-link-requests) [target group])) + =/ path (welp /token-link t.wire) + ~[[%give %kick ~[path] ~]] (on-arvo:def wire sign-arvo) == == diff --git a/desk/lib/test-agent.hoon b/desk/lib/test-agent.hoon new file mode 100644 index 00000000..a2d58ab1 --- /dev/null +++ b/desk/lib/test-agent.hoon @@ -0,0 +1,421 @@ +:: test-agent: testing harness for agents +:: +:: this library helps you write tests for agents in the monadic ;< style. +:: the general concept is that an agent and its bowl are continuously +:: "passed forward" as you perform operations on them. +:: +:: the bowl is partially managed for you by the library: when the agent +:: emits %watch or %leave cards, outgoing subscriptions in wex.bowl are +:: updated. likewise for %watch-ack and %kick calls to +on-agent. incoming +:: subscriptions in sup.bowl are updated in response to %kick cards and +:: calls to +on-watch and +on-leave. +:: +:: an example test arm, which initializes an agent and pokes it, might look +:: something like this (assuming /+ *test-agent): +:: +:: ++ test-example +:: %- eval-mare +:: =/ m (mare ,~) +:: ^- form:m +:: ;< ~ bind:m (set-scry-gate |=(path `!>(%some-noun))) +:: ;< caz=(list card) bind:m (do-init %my-agent-name my-agent-core) +:: ;< ~ bind:m (ex-cards caz ~) +:: ;< ~ bind:m (set-src ~dev) +:: ;< caz=(list card) bind:m (do-poke %noun !>(123)) +:: (ex-cards caz (ex-fact [/echo]~ %noun !>([~dev 123])) ~) +:: +/+ test +!. +:: +=/ drop-verb=? & +:: +|% +:: voodoo basics +:: ++$ agent $+(agent agent:gall) ++$ bowl $+(bowl bowl:gall) ++$ card $+(card card:agent:gall) ++$ scry $-(path (unit vase)) +:: ++$ state [=agent =bowl =scry] :: passed continuously +++ form-raw |$ [a] $-(state (output-raw a)) :: continuation +++ output-raw |$ [a] (each [out=a =state] tang) :: continue or fail +:: +++ mare + |* a=mold + |% + ++ output (output-raw a) + ++ form (form-raw a) + ++ pure + |= arg=a + ^- form + |= =state + [%& arg state] + :: + ++ bind + |* b=mold + |= [m-b=(form-raw b) fun=$-(b form)] + ^- form + |= =state + =/ b-res=(output-raw b) (m-b state) + ?- -.b-res + %& ((fun out.p.b-res) state.p.b-res) + %| [%| p.b-res] + == + -- +:: +++ eval-mare + =/ m (mare ,~) + |= f=form:m + ^- tang + =/ res (f *state) + ?-(-.res %& ~, %| p.res) +:: +:: internal transformations (you shouldn't be calling these directly) +:: +++ play-cards + |= [=bowl cards=(list card)] + ^+ bowl + ?~ cards bowl + =* card i.cards + =* next $(cards t.cards) + ?+ card next + [%pass * %agent * ?(%watch %watch-as %leave) *] + =- =.(wex.bowl - next) + =/ key=[=wire =gill:gall] [p [ship name]:q]:card + ?: ?=(%leave -.task.q.card) + (~(del by wex.bowl) key) + ?. (lien ~(tap by wex.bowl) |=([k=[wire gill:gall] *] =(k key))) + =; =path + (~(put by wex.bowl) key [| path]) + ?- -.task.q.card + %watch path.task.q.card + %watch-as path.task.q.card + == + ~_ 'subscribe wire not unique' ::TODO maybe integrate the test tang instead? + ~| key + !! + :: + [%give %kick *] + =- =.(sup.bowl - next) + %+ roll paths.p.card + |= [=path =_sup.bowl] + %- ~(gas by *bitt:gall) + %+ skip ~(tap by sup) + |= [duct s=ship p=^path] + &(=(p path) |(?=(~ ship.p.card) =(s u.ship.p.card))) + == +:: +++ play-sign + |= [=bowl =wire =gill:gall =sign:agent:gall] + ^+ bowl + =. src.bowl p.gill + ?: ?=(%poke-ack -.sign) bowl + =; =_wex.bowl bowl(wex wex) + ?. (~(has by wex.bowl) [wire gill]) + ~_ leaf+"missing subscription, got %{(trip -.sign)} on {(spud wire)}" + !! + ?+ sign wex.bowl + ?([%watch-ack ~ *] [%kick ~]) + (~(del by wex.bowl) [wire gill]) + :: + [%watch-ack ~] + %+ ~(jab by wex.bowl) [wire gill] + |= [ack=? =path] + ~_ 'duplicate watch-ack' + ?<(ack [& path]) + == +:: +++ do :: execute agent lifecycle step with mocked scry + |= call=$-(state [(list card) agent:gall]) + =/ m (mare ,(list card)) + ^- form:m + |= s=state + =; result=(each [(list card) agent:gall] (list tank)) + ?: ?=(%| -.result) + |+p.result + =^ c agent.s p.result + =. bowl.s (play-cards bowl.s c) + &+[c s] + =; res=toon + ?- -.res + %0 :- %& + ::NOTE we would ;;, but it's too slow. + :: we know for a fact p.res is of the type we expect, + :: so we just play pretend with vases instead. + !< [(list card) agent:gall] + [-:!>(*[(list card) agent:gall]) p.res] + %1 |+~['blocking on scry' >;;(path p.res)<] + %2 |+p.res + == + %+ mock [. !=((call s))] + |= [ref=* pax=*] + ^- (unit (unit *)) + ?> ?=(^ ref) + ?> =(hoon-version -.ref) + =+ ;;(pax=path pax) + =/ res=(unit vase) (scry.s pax) + %. ?~(res ~ ``q.u.res) + :: warn about type mismatches if the tested code expects a result type + :: different from what the mocked scry produces. + :: + ::NOTE we would ;;, but it's too slow. + :: we can safely assume +.ref is indeed a type, + :: so we just play pretend with vases instead. + =+ !<(typ=type [-:!>(*type) +.ref]) + ?. &(?=(^ res) !(~(nest ut typ) | p.u.res)) + same + %- %*(. slog pri 2) + :~ 'mocked scry result mismatches expected type' + >pax< + (~(dunk ut typ) %need) + (~(dunk ut p.u.res) %have) + == +:: +++ jab-state + |= f=$-(state state) + =/ m (mare ,~) + ^- form:m + |= s=state + &+[~ (f s)] +:: +:: managed agent lifecycle +:: +++ do-init + =+ scry-warn=& + |= [dap=term =agent] + =/ m (mare ,(list card)) + ^- form:m + ;< old-scry=scry bind:m |=(s=state &+[scry.s s]) + ;< ~ bind:m %- set-scry-gate + |= p=path + ~? >> scry-warn + ['scrying during +on-init... careful!' p] + (old-scry p) + ;< b=bowl bind:m get-bowl + ;< ~ bind:m (set-bowl %*(. *bowl dap dap, our our.b, src our.b)) + ;< c=(list card) bind:m (do |=(s=state ~(on-init agent bowl.s))) + ;< ~ bind:m (set-scry-gate old-scry) + (pure:m c) +:: +++ do-load + =+ scry-warn=& + |= [=agent vase=(unit vase)] + =/ m (mare ,(list card)) + ^- form:m + ;< old-scry=scry bind:m |=(s=state &+[scry.s s]) + ;< ~ bind:m %- set-scry-gate + |= p=path + ~? >> scry-warn + ['scrying during +on-load... careful!' p] + (old-scry p) + ;< c=(list card) bind:m %- do |= s=state + %- ~(on-load agent bowl.s) + (fall vase ~(on-save agent.s bowl.s)) + ;< ~ bind:m (set-scry-gate old-scry) + (pure:m c) +:: +++ do-poke + |= [=mark =vase] + %- do + |= s=state + (~(on-poke agent.s bowl.s) mark vase) +:: +++ do-watch + |= =path + =/ m (mare ,(list card)) + ^- form:m + |= s=state + =. sup.bowl.s + =/ =duct [%test-sub (scot %p src.bowl.s) path]~ + ~_ leaf+"sub on {(spud path)} already made by {(scow %p src.bowl.s)}" + ?< (~(has by sup.bowl.s) duct) + (~(put by sup.bowl.s) duct [src.bowl.s path]) + %. s %- do + |= s=state + (~(on-watch agent.s bowl.s) path) +:: +++ do-leave + |= =path + =/ m (mare ,(list card)) + ^- form:m + |= s=state + =. sup.bowl.s + =/ =duct [%test-sub (scot %p src.bowl.s) path]~ + ~_ leaf+"sub on {(spud path)} not yet made by {(scow %p src.bowl.s)}" + ?> (~(has by sup.bowl.s) duct) + (~(del by sup.bowl.s) duct [src.bowl.s path]) + %. s %- do + |= s=state + (~(on-leave agent.s bowl.s) path) +:: +++ do-agent + |= [=wire =gill:gall =sign:agent:gall] + =/ m (mare ,(list card)) + ^- form:m + |= s=state + =. bowl.s (play-sign bowl.s wire gill sign) + %. s %- do + |= s=state + (~(on-agent agent.s bowl.s) wire sign) +:: +++ do-arvo + |= [=wire sign=sign-arvo] + %- do + |= s=state + (~(on-arvo agent.s bowl.s) wire sign) +:: +++ do-fail + |= [=term =tang] + %- do + |= s=state + (~(on-fail agent.s bowl.s) term tang) +:: +:: data extraction +:: +++ get-save + =/ m (mare ,vase) + ^- form:m + |= s=state + &+[~(on-save agent.s bowl.s) s] +:: +++ get-peek + |= =path + =/ m (mare ,(unit (unit cage))) + ^- form:m + |= s=state + &+[(~(on-peek agent.s bowl.s) path) s] +:: +++ get-agent + =/ m (mare agent) + ^- form:m + |= s=state + &+[agent.s s] +:: +++ get-bowl + =/ m (mare bowl) + ^- form:m + |= s=state + &+[bowl.s s] +:: +:: bowl modification +:: +++ jab-bowl + |= f=$-(bowl bowl) + =/ m (mare ,~) + ^- form:m + |= s=state + &+[~ s(bowl (f bowl.s))] +:: +++ set-bowl + |=(b=bowl (jab-bowl |=(* b))) +:: +++ set-src + |= src=ship + %- jab-bowl + |=(b=bowl b(src src)) +:: +++ wait + |= d=@dr + %- jab-bowl + |=(b=bowl b(now (add now.b d))) +:: +++ set-scry-gate + |= f=scry + =/ m (mare ,~) + ^- form:m + |= s=state + &+[~ s(scry f)] +:: +:: testing utilities +:: +++ ex-equal + |= [actual=vase expected=vase] ::NOTE reverse order from /lib/test + =/ m (mare ,~) + ^- form:m + |= s=state + =/ =tang (expect-eq:test expected actual) + ?~(tang &+[~ s] |+tang) +:: +++ ex-fail + |= form=(form-raw ,*) + =/ m (mare ,~) + ^- form:m + |= =state + =/ res (form state) + ?-(-.res %| &+[~ state], %& |+['expected failure, but succeeded']~) +:: +++ ex-cards + |= [caz=(list card) exes=(list $-(card tang))] + =? caz drop-verb + :: remove cards unconditionally emitted by /lib/verb + :: + %+ skip caz + |= =card + ?=([%give %fact [[%verb ?(%events %events-plus) ~] ~] *] card) + =/ m (mare ,~) + ^- form:m + |= s=state + =; =tang + ?~(tang &+[~ s] |+tang) + |- ^- tang + ?~ exes + ?~ caz + ~ + ['got more cards than expected' >caz< ~] + ?~ caz + ['expected more cards than got' ~] + %+ weld + (i.exes i.caz) + $(exes t.exes, caz t.caz) +:: +++ ex-card + |= caw=card + |= cav=card + (expect-eq:test !>(caw) !>(cav)) +:: +++ ex-fact + |= [paths=(list path) =mark =vase] + |= car=card + ^- tang + =* nope + %- expect-eq:test + [!>(`card`[%give %fact paths mark vase]) !>(`card`car)] + ?. ?=([%give %fact *] car) nope + ?. =(paths paths.p.car) nope + ?. =(mark p.cage.p.car) nope + =/ =tang (expect-eq:test vase q.cage.p.car) + ?~ tang ~ + ['in %fact vase,' tang] +:: +++ ex-poke + |= [=wire =gill:gall =mark =vase] + |= car=card + ^- tang + =* nope + %- expect-eq:test + [!>(`card`[%pass wire %agent gill %poke mark vase]) !>(`card`car)] + ?. ?=([%pass * %agent * %poke *] car) nope + ?. =(wire p.car) nope + ?. =(gill [ship name]:q.car) nope + ?. =(mark p.cage.task.q.car) nope + =/ =tang (expect-eq:test vase q.cage.task.q.car) + ?~ tang ~ + ['in %poke vase on ,' tang] +:: +++ ex-task + |= [=wire =gill:gall =task:agent:gall] + (ex-card %pass wire %agent gill task) +:: +++ ex-arvo + |= [=wire note=note-arvo] + (ex-card %pass wire %arvo note) +:: +++ ex-scry-result + |= [=path =vase] + =/ m (mare ,~) + ^- form:m + ;< res=(unit (unit cage)) bind:m (get-peek path) + (ex-equal q:(need (need res)) vase) +:: +-- diff --git a/desk/sur/reel.hoon b/desk/sur/reel.hoon index b3fee23d..1f46275c 100644 --- a/desk/sur/reel.hoon +++ b/desk/sur/reel.hoon @@ -7,7 +7,11 @@ +$ bite $% [%bite-0 token=@ta ship=@p] [%bite-1 token=@ta joiner=@p inviter=@p] + [%bite-2 =token joiner=@p =metadata] == :: ++$ token cord ++$ nonce @ta +$ metadata [tag=term fields=(map cord cord)] ++$ confirmation [=nonce =token] -- diff --git a/desk/tests/app/bait.hoon b/desk/tests/app/bait.hoon new file mode 100644 index 00000000..e1bc3c3a --- /dev/null +++ b/desk/tests/app/bait.hoon @@ -0,0 +1,165 @@ +/- r=reel, spider +/+ *test-agent, reel, strandio, server +/= bait-agent /app/bait +|% +++ dap %bait-test +++ vic 'https://tlon.network/lure/' +++ civ ~loshut-lonreg +++ eny + `@uv`0xffff.ffff.ffff.ffff.ffff.ffff.ffff.ffff +++ nonce `@ta`'~2000.1.1' +++ token `@t`(scot %uv (end [3 16] eny)) ++$ bait-state + $: %2 + metadata=(map token:r metadata:r) + == +++ test-bait-describe + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap bait-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our civ, src ~dev, eny eny))) + =/ =metadata:r [%test (my ['inviter' '~dev'] ['bite-type' '2'] ~)] + =/ describe [nonce metadata] + ;< caz=(list card) bind:m (do-poke %bait-describe !>(describe)) + ;< * bind:m + %+ ex-cards caz + ~[(ex-poke /confirm/[nonce] [~dev %reel] reel-confirmation+!>([nonce token]))] + ;< state=vase bind:m get-save + =+ !<(bait-state state) + (ex-equal !>(metadata) !>((my [token ^metadata] ~))) +++ test-bait-who-get + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap bait-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev))) + =/ simple-payload + (json-response:gen:server s+(scot %p ~dev)) + :: request 1: test old style tokens + =/ eyre-id %eyre-request-1 + =/ request=[id=@ta inbound-request:eyre] + [eyre-id (eyre-get-request '/lure/bait/who')] + ;< caz=(list card) bind:m (do-poke %handle-http-request !>(request)) + %+ ex-cards caz + (eyre-request-cards eyre-id simple-payload) +++ test-bait-metadata-get + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap bait-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our civ, src civ, eny eny))) + =/ =metadata:r [%test (my ['title' 'test-group'] ~)] + =/ init-state=bait-state + :- %2 + (my ['~zod/test' metadata] [token metadata] ~) + ;< * bind:m (do-load bait-agent `!>(init-state)) + =/ simple-payload + (json-response:gen:server (enjs-metadata:reel metadata)) + :: request 1: test old style tokens + =/ eyre-id %eyre-request-1 + =/ request=[id=@ta inbound-request:eyre] + [eyre-id (eyre-get-request '/lure/~zod/test/metadata')] + ;< caz=(list card) bind:m (do-poke %handle-http-request !>(request)) + ;< * bind:m + %+ ex-cards caz + (eyre-request-cards eyre-id simple-payload) + :: request 2: test new style tokens + =/ eyre-id %eyre-request-2 + =/ request=[id=@ta inbound-request:eyre] + [eyre-id (eyre-get-request (crip "/lure/{(trip token)}/metadata"))] + ;< caz=(list card) bind:m (do-poke %handle-http-request !>(request)) + %+ ex-cards caz + (eyre-request-cards eyre-id simple-payload) +:: +++ test-bait-bite-post + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap bait-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our civ, src civ, eny eny))) + =/ m1=metadata:r [%test (my ['title' 'test-group'] ~)] + =/ m2=metadata:r + :- %test + %- my + :~ ['title' 'test-group'] + ['bite-type' '2'] + ['inviter' '~dev'] + == + =/ init-state=bait-state + :- %2 + (my ['~zod/test' m1] [token m2] ~) + ;< * bind:m (do-load bait-agent `!>(init-state)) + =/ payload (as-octs:mimes:html 'ship=%7Erus') + =/ simple-payload + (manx-response:gen:server (sent-page ~rus)) + :: request 1: test new style tokens + =/ eyre-id %eyre-request-1 + =/ request=[id=@ta inbound-request:eyre] + [eyre-id (eyre-post-request (cat 3 '/lure/' token) payload)] + ;< caz=(list card) bind:m (do-poke %handle-http-request !>(request)) + ;< * bind:m + %+ ex-cards caz + =/ =cage reel-bite+!>([%bite-2 token ~rus m2]) + %+ welp + :~ (ex-poke /bite [~dev %reel] cage) + (ex-poke /bite [civ %reel] cage) + == + (eyre-request-cards eyre-id simple-payload) + :: request 2: test old style tokens + =/ eyre-id %eyre-request-2 + =/ request=[id=@ta inbound-request:eyre] + [eyre-id (eyre-post-request '/lure/~zod/test' payload)] + ;< caz=(list card) bind:m (do-poke %handle-http-request !>(request)) + %+ ex-cards caz + =/ =cage reel-bite+!>([%bite-1 `@ta`'test' ~rus ~zod]) + %+ welp + :~ (ex-poke /bite [~zod %reel] cage) + (ex-poke /bite [civ %reel] cage) + == + (eyre-request-cards eyre-id simple-payload) +++ eyre-get-request + |= url=@t + :* | + & + *address:eyre + :* %'GET' + url + ~ + ~ + == + == +:: +++ eyre-post-request + |= [url=@t payload=octs] + :* | + & + *address:eyre + :* %'POST' + url + ~ + `payload + == + == +++ eyre-request-cards + |= [id=@ta =simple-payload:http] + ^- (list $-(card tang)) + =/ paths ~[/http-response/[id]] + =/ header-cage + [%http-response-header !>(response-header.simple-payload)] + =/ data-cage + [%http-response-data !>(data.simple-payload)] + %- limo + :~ (ex-fact paths header-cage) + (ex-fact paths data-cage) + (ex-card [%give %kick paths ~]) + == +++ sent-page + |= invitee=ship + ^- manx + ;html + ;head + ;title:"Lure" + == + ;body + Your invite has been sent! Go to your ship to accept it. + ;script: document.cookie="ship={(trip (scot %p invitee))}" + == + == +-- \ No newline at end of file diff --git a/desk/tests/app/reel.hoon b/desk/tests/app/reel.hoon new file mode 100644 index 00000000..7bc662cf --- /dev/null +++ b/desk/tests/app/reel.hoon @@ -0,0 +1,120 @@ +/- r=reel +/+ *test-agent +/= reel-agent /app/reel +|% +++ dap %reel-test +++ vic 'https://tlon.network/lure/' +++ civ ~loshut-lonreg +++ token '~bus/reel-test' ++$ reel-state + $: %4 + vic=@t + civ=ship + our-metadata=(map token:r metadata:r) + open-link-requests=(set (pair ship cord)) + open-describes=(set token:r) + stable-id=(map cord token:r) + == +++ test-reel-describe + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap reel-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~dev, now *@da))) + =/ =metadata:r [%test (my ['inviter' '~dev'] ~)] + =/ describe [token metadata] + ;< caz=(list card) bind:m (do-poke %reel-describe !>(describe)) + ;< bw=bowl bind:m get-bowl + =/ nonce (scot %da now.bw) + =/ edited-md [%test (~(put by fields.metadata) 'bite-type' '2')] + :: make sure we're sending a describe request to the bait provider + ;< * bind:m + %+ ex-cards caz + =/ request [nonce edited-md] + ~[(ex-poke /describe [civ %bait] bait-describe+!>(request))] + ;< state=vase bind:m get-save + =+ !<(reel-state state) + :: ensure link metadata added to our state and has bite-type field + ;< * bind:m (ex-equal !>(our-metadata) !>((my [nonce edited-md] ~))) + :: ensure nonce is added to open-describes set + ;< * bind:m (ex-equal !>(open-describes) !>((sy [nonce] ~))) + :: ensure stable-id has an entry for the token + ;< * bind:m (ex-equal !>(stable-id) !>((my [token nonce] ~))) + :: simulate the bait provider returning the new metadata + ;< bw=bowl bind:m get-bowl + =/ real-token (shax (jam [dap eny.bw])) + ;< * bind:m (jab-bowl |=(b=bowl b(src civ))) + ;< * bind:m (do-poke %reel-confirmation !>([nonce real-token])) + ;< state=vase bind:m get-save + =+ !<(reel-state state) + ;< * bind:m (ex-equal !>(open-describes) !>(~)) + ;< * bind:m (ex-equal !>(stable-id) !>((sy [token real-token] ~))) + (ex-equal !>(our-metadata) !>((my [real-token edited-md] ~))) +:: +:: testing old way of distributing links from requester side +++ test-reel-token-link-requester + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap reel-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~dev, src ~dev, now *@da))) + ;< bw=bowl bind:m get-bowl + =/ request-path /token-link/(scot %p ~bus)/[dap] + :: simulate subscription from frontend for link + ;< caz=(list card) bind:m + (do-watch request-path) + =/ next (add now.bw ~h1) + =/ expire=wire /expire/(scot %p ~bus)/[dap] + ;< * bind:m + %+ ex-cards caz + =/ =cage reel-want-token-link+!>(dap) + :~ (ex-poke request-path [~bus dap] cage) + (ex-arvo expire %b %wait next) + == + ;< state=vase bind:m get-save + =+ !<(reel-state state) + =/ new-requests (sy [~bus dap] ~) + :: ensure that the request is in the open-link-requests set + ;< * bind:m (ex-equal !>(open-link-requests) !>(new-requests)) + ;< * bind:m (jab-bowl |=(b=bowl b(now next))) + ;< bw=bowl bind:m get-bowl + :: simulate link request expiring + ;< * bind:m (do-arvo expire %behn %wake ~) + ;< state=vase bind:m get-save + =+ !<(reel-state state) + :: make sure the request is removed from the open-link-requests set + ;< * bind:m (ex-equal !>(open-link-requests) !>(~)) + :: try to get the link again, but this time not expiring + ;< * bind:m (do-watch request-path) + =/ url (cat 3 vic '~bus/reel-test') + =/ response `[dap url] + ;< * bind:m (jab-bowl |=(b=bowl b(src ~bus))) + ;< caz=(list card) bind:m (do-poke %reel-give-token-link !>(response)) + %+ ex-cards caz + ~[(ex-fact ~[request-path] %json !>(s+url))] +:: +:: testing old way of distributing links from dispenser side +++ test-reel-token-link-dispenser + %- eval-mare + =/ m (mare ,~) + ;< * bind:m (do-init dap reel-agent) + ;< * bind:m (jab-bowl |=(b=bowl b(our ~bus, src ~bus))) + :: build state for link + =/ fields=(map cord cord) (my ['inviter' '~zod'] ~) + =/ init-state=vase + !> + :* %4 + vic + civ + (my [token %meta fields] ~) + ~ + ~ + (my [token token] ~) + == + ;< * bind:m (do-load reel-agent `init-state) + ;< * bind:m (jab-bowl |=(b=bowl b(src ~zod))) + :: simulate link request + ;< caz=(list card) bind:m (do-poke %reel-want-token-link !>(token)) + %+ ex-cards caz + =/ url (cat 3 vic '~bus/reel-test') + =/ =cage reel-give-token-link+!>(`[token url]) + ~[(ex-poke /token-link-want/[token] [~zod dap] cage)] +-- \ No newline at end of file