224 lines
12 KiB
Lua
Executable File
224 lines
12 KiB
Lua
Executable File
-- Focused injected reader tests; no engine, network or metadata required.
|
|
local target=assert(arg[1],"species_reader.lua path required")
|
|
local path=target:match("^(.*[/\\])") or ""
|
|
local factory=dofile(target)
|
|
local total=0
|
|
local function test(name,fn)
|
|
local ok,err=pcall(fn); assert(ok,name..": "..tostring(err))
|
|
total=total+1; print("PASS "..name)
|
|
end
|
|
local function forbidden() error("Reader attempted a side effect") end
|
|
local screens, hits, node, reads, rays={}, {}, {name="hearth:oak_log"}, 0, 0
|
|
local H={sessions={alice={}},players={alice={food=73,water=61}},clock={season="Autumn"}}
|
|
H.botany=dofile(path.."botany.lua")(H,{})
|
|
H.wiki_articles=dofile(path.."wiki_articles.lua")
|
|
H.species_effects=dofile(path.."species_effects.lua")(H,{})
|
|
local api={set_node=forbidden,swap_node=forbidden,get_meta=forbidden,
|
|
emerge_area=forbidden,forceload_block=forbidden,request_http_api=forbidden,
|
|
show_url=forbidden,register_chatcommand=forbidden,register_on_player_receive_fields=forbidden}
|
|
function api.formspec_escape(s)
|
|
return (s:gsub("\\","\\\\"):gsub("%[","\\["):gsub("%]","\\]"):gsub(";","\\;"):gsub(",","\\,"))
|
|
end
|
|
function api.show_formspec(name,form,spec) screens[#screens+1]={name=name,form=form,spec=spec} end
|
|
function api.get_node_or_nil() reads=reads+1; return node end
|
|
local eye_height=1.6
|
|
local player={get_player_name=function() return "alice" end,
|
|
get_pos=function() return {x=0,y=0,z=0} end,
|
|
get_properties=function() return {eye_height=eye_height} end,
|
|
get_look_dir=function() return {x=0,y=0,z=2} end,
|
|
get_meta=forbidden,set_pos=forbidden,get_inventory=forbidden}
|
|
local function hit(z,name)
|
|
return {type="node",under={x=0,y=2,z=z},intersection_point={x=0,y=1.6,z=z},name=name}
|
|
end
|
|
function api.raycast(start,finish,objects,liquids)
|
|
rays=rays+1
|
|
assert(start.x==0 and start.y==1.6 and start.z==0,"not eye origin")
|
|
assert(finish.y==start.y and finish.z==6,"reach not normalized to six")
|
|
assert(objects==false and liquids==false)
|
|
local i=0
|
|
return function() i=i+1; assert(i<=3,"unbounded scan"); return hits[i] end
|
|
end
|
|
local R=factory(H,api)
|
|
local function latest() return screens[#screens].spec end
|
|
test("factory installs no global callbacks or API mutations",function()
|
|
assert(type(R.show)=="function" and type(R.look)=="function" and type(R.resolve)=="function" and type(R.handle)=="function")
|
|
assert(api.set_node==forbidden and H.species_reader==nil)
|
|
end)
|
|
test("exact IDs and canonical Latin resolve without fuzzy URL fallback",function()
|
|
assert(R.resolve("oak").latin=="Quercus robur")
|
|
assert(R.resolve(" Quercus robur ").id=="oak")
|
|
for _,v in ipairs({"Quercus","quercus robur","Pedunculate oak","hearth:oak_log","https://en.wikipedia.org/wiki/Oak",string.rep("x",121),42}) do
|
|
assert(not R.resolve(v))
|
|
end
|
|
assert(not R.resolve(nil))
|
|
end)
|
|
test("actual botanical seasonal stages logs leaves and copy API resolve",function()
|
|
local S=dofile(path.."seasons.lua")(H,{})
|
|
H.seasons=S
|
|
-- The real diversity factory installs the existing seasonal tree mappings.
|
|
H.diversity=dofile(path.."diversity.lua")(H,{})
|
|
for _,v in ipairs({"hearth:oak_seedling","hearth:oak_young","hearth:oak_mature","hearth:oak_log","hearth:oak_leaves","hearth:oak_leaves_winter","hearth:clover_winter"}) do
|
|
local r=H.botany.get(v); assert(r,v)
|
|
assert(R.show(player,r.id))
|
|
end
|
|
local r=R.resolve("oak"); r.common="changed"; assert(R.resolve("oak").common~="changed")
|
|
end)
|
|
test("ray uses normalized six-block eye reach and loaded-node read only",function()
|
|
hits={hit(6)}; node={name="hearth:oak_log"}; reads=0
|
|
assert(R.look(player).id=="oak" and reads==1)
|
|
end)
|
|
test("ray rejects hits beyond reach without node reads",function()
|
|
hits={hit(6.01)}; reads=0; assert(not R.look(player) and reads==0)
|
|
end)
|
|
test("unknown and unloaded first surfaces cannot be inspected through",function()
|
|
hits={hit(2),hit(3)}
|
|
for _,v in ipairs({false,{name="ignore"},{name="hearth:stone"},{name="foreign:oak_log"}}) do
|
|
node=v or nil; reads=0; assert(not R.look(player) and reads==1)
|
|
end
|
|
end)
|
|
test("empty ray and invalid hit positions do not read map",function()
|
|
hits={}; reads=0; assert(not R.look(player) and reads==0)
|
|
hits={{type="node",under={x=0,y=0,z=0}}}; assert(not R.look(player) and reads==0)
|
|
hits={hit(3)}; hits[1].under.x=0/0; assert(not R.look(player) and reads==0)
|
|
eye_height=math.huge; local n=rays; assert(not R.look(player) and rays==n); eye_height=1.6
|
|
end)
|
|
test("all eighty entries render with honest seventy-eight excerpt and two note headings",function()
|
|
local excerpts,notes=0,0
|
|
for _,r in ipairs(H.botany.list()) do
|
|
assert(R.show(player,r.id)); local a=H.wiki_articles[r.id]
|
|
assert(latest():find(api.formspec_escape(r.latin),1,true))
|
|
if a.kind=="wikipedia_intro" then
|
|
excerpts=excerpts+1; assert(latest():find("Wikipedia excerpt (offline)",1,true))
|
|
else
|
|
notes=notes+1
|
|
assert(r.id=="old_mans_beard" or r.id=="cup_lichen")
|
|
assert(latest():find("No Wikipedia article cached (offline)",1,true))
|
|
assert(not latest():find("Wikipedia excerpt (offline)",1,true))
|
|
assert(latest():find("CC0",1,true) and not latest():find("CC BY-SA 4.0",1,true))
|
|
assert(not a.revision and latest():find(api.formspec_escape(a.text),1,true))
|
|
end
|
|
end
|
|
assert(excerpts==78 and notes==2)
|
|
end)
|
|
test("excerpt retains full shipped introduction and attribution revision history license",function()
|
|
assert(R.show(player,"oak")); local a=H.wiki_articles.oak
|
|
for _,key in ipairs({"title","text","attribution","license","license_url","url","revision_url","history_url","revision_timestamp","retrieved","changes"}) do
|
|
assert(latest():find(api.formspec_escape(a[key]),1,true),key)
|
|
end
|
|
assert(latest():find("not the full wiki",1,true) and latest():find("not",1,true))
|
|
assert(latest():find("medical advice",1,true))
|
|
end)
|
|
test("every shipped introduction fits and is displayed in full",function()
|
|
for _,r in ipairs(H.botany.list()) do
|
|
assert(R.show(player,r.id))
|
|
assert(latest():find(api.formspec_escape(H.wiki_articles[r.id].text),1,true),r.id)
|
|
end
|
|
end)
|
|
test("current effects use actual describe records with simulated descriptive separation",function()
|
|
assert(R.show(player,"honey_fungus",2))
|
|
assert(latest():find("SIMULATED",1,true) and latest():find("DESCRIPTIVE (not simulated)",1,true))
|
|
assert(latest():find("no infection or attack",1,true))
|
|
assert(R.show(player,"alder",2))
|
|
assert(latest():find("900 warm active seconds",1,true) and latest():find("Frankia",1,true))
|
|
assert(latest():find("not measured Wikipedia constants",1,true))
|
|
end)
|
|
test("article textarea and provenance are unnamed read-only copyable fields",function()
|
|
assert(R.show(player,"oak"))
|
|
local count=0
|
|
for body in latest():gmatch("textarea%[([^\n]-);;;") do count=count+1 end
|
|
assert(count==3 and not latest():find("field[",1,true))
|
|
assert(latest():find("URLs are copyable",1,true))
|
|
end)
|
|
test("forged IDs source URLs text and pages never replace session article",function()
|
|
assert(R.show(player,"oak"))
|
|
local before=#screens
|
|
assert(R.handle(player,{id="pine",url="javascript:boom",text="replacement",page="999",reader_tab="99"}))
|
|
assert(#screens==before and H.sessions.alice.species_reader.id=="oak")
|
|
R.handle(player,{reader_tab="2",id="pine"}); assert(H.sessions.alice.species_reader.id=="oak")
|
|
R.handle(player,{reader_tab="1",text="replacement"}); assert(latest():find(api.formspec_escape(H.wiki_articles.oak.text),1,true))
|
|
end)
|
|
test("malicious cached text source and URLs are plain escaped inert data",function()
|
|
local a=H.wiki_articles.oak
|
|
local payload="];button[0,0;1,1;attack;Injected]<script>error('execute')</script>"
|
|
H.wiki_articles.oak={kind="wikipedia_intro",text=payload,url=payload,attribution=payload,license=payload}
|
|
assert(R.show(player,"oak"))
|
|
assert(latest():find(api.formspec_escape(payload),1,true) and not latest():find(";attack;Injected]",1,true))
|
|
assert(not latest():find("hypertext[",1,true) and not latest():find("button_url[",1,true))
|
|
H.wiki_articles.oak=a
|
|
end)
|
|
test("Unicode body caps at eight thousand with four-thousand character pages",function()
|
|
local a=H.wiki_articles.oak
|
|
local char=string.char(195,169)
|
|
H.wiki_articles.oak={kind="wikipedia_intro",text=string.rep(char,9000)}
|
|
assert(R.show(player,"oak"))
|
|
assert(latest():find(string.rep(char,4000),1,true) and not latest():find(string.rep(char,4001),1,true))
|
|
assert(latest():find("display limited to 8000",1,true))
|
|
R.handle(player,{reader_next=true}); assert(H.sessions.alice.species_reader.page==2)
|
|
assert(latest():find(string.rep(char,4000),1,true))
|
|
R.handle(player,{reader_next=true}); assert(H.sessions.alice.species_reader.page==2)
|
|
R.handle(player,{reader_previous=true}); assert(H.sessions.alice.species_reader.page==1)
|
|
assert(#latest()<20000)
|
|
H.wiki_articles.oak=a
|
|
end)
|
|
test("absent optional catalogue sources and effects are explicit not counterfeit",function()
|
|
local B,A,E=H.botany,H.wiki_articles,H.species_effects
|
|
H.wiki_articles=nil; assert(R.show(player,"oak")); assert(latest():find("No article is available",1,true))
|
|
H.species_effects=nil; assert(R.show(player,"oak",2)); assert(latest():find("Effect descriptions unavailable",1,true))
|
|
H.botany=nil; assert(not R.resolve("oak") and not R.show(player,"oak"))
|
|
H.botany,H.wiki_articles,H.species_effects=B,A,E
|
|
end)
|
|
test("player close confirmation clears reader but changes no gameplay or persistent data",function()
|
|
local needs=H.players.alice
|
|
assert(R.show(player,"oak")); local old=H.sessions.alice.species_reader
|
|
R.handle(player,{text="modified"}); assert(H.sessions.alice.species_reader==old)
|
|
R.handle(player,{quit=true}); assert(not H.sessions.alice.species_reader)
|
|
assert(H.players.alice==needs and needs.food==73 and needs.water==61)
|
|
assert(not R.handle(player,{reader_next=true}))
|
|
end)
|
|
test("missing session rejects forms and forged follow-up packets",function()
|
|
local s=H.sessions.alice; H.sessions.alice=nil; local n=#screens
|
|
assert(not R.show(player,"oak") and not R.handle(player,{reader_tab="2"}))
|
|
assert(#screens==n); H.sessions.alice=s
|
|
end)
|
|
test("actual feedback HUD hints known species with hand or building and fails closed unloaded",function()
|
|
local saved_core,saved_hearth=core,hearth
|
|
local ok,err=pcall(function()
|
|
local t,wield_name,message=1,"",""
|
|
local s={}
|
|
hearth={sessions={alice=s},botany=H.botany,movement={delta=function(d) return d end},
|
|
construction={preview=function() return true,"ok" end}}
|
|
core={get_us_time=function() return t*1000000 end,
|
|
register_chatcommand=function() end,raycast=function(start,finish)
|
|
assert(start.y==1.6 and finish.z==5)
|
|
local i=0; return function() i=i+1; return hits[i] end
|
|
end,get_node_or_nil=api.get_node_or_nil,
|
|
get_meta=forbidden,set_node=forbidden,swap_node=forbidden,sound_play=forbidden,
|
|
get_dig_params=function() return {diggable=true} end,
|
|
registered_nodes={["hearth:oak_log"]={description="Oak log",groups={tree=1}},
|
|
["hearth:stone"]={description="Stone",groups={stone=1}},
|
|
["hearth:wall"]={description="Wall",groups={wood=1}}}}
|
|
local p={get_player_name=player.get_player_name,get_pos=player.get_pos,
|
|
get_properties=player.get_properties,get_look_dir=function() return {x=0,y=0,z=1} end,
|
|
get_hp=function() return 20 end,get_velocity=function() return {x=0,y=0,z=0} end,
|
|
get_player_control=function() return {} end,set_animation=function() end,
|
|
hud_add=function(_,def) message=def.text; return 1 end,
|
|
hud_change=function(_,_,key,value) if key=="text" then message=value end end}
|
|
function p:get_wielded_item()
|
|
return {get_name=function() return wield_name end,get_wear=function() return 0 end,
|
|
get_tool_capabilities=function() return {} end}
|
|
end
|
|
dofile(path.."feedback.lua")
|
|
hits={hit(3)}; node={name="hearth:oak_log"}
|
|
hearth.feedback_step(p,0); assert(message:find("/inspect species",1,true))
|
|
wield_name="hearth:wall"; t=2
|
|
hearth.feedback_step(p,0)
|
|
assert(message:find("Ready to place",1,true) and message:find("/inspect species",1,true))
|
|
wield_name=""; node={name="hearth:stone"}; t=3
|
|
hearth.feedback_step(p,0); assert(not message:find("/inspect",1,true))
|
|
node=nil; t=4; hearth.feedback_step(p,0); assert(message=="")
|
|
end)
|
|
core,hearth=saved_core,saved_hearth
|
|
assert(ok,err)
|
|
end)
|
|
print("PASS "..total.." species reader tests")
|