local describe = require("lua_test.test").describe
local test = require("lua_test.test").test
local expect = require("lua_test.test").expect
local function reload_event_modules(reset_res)
package.loaded["pasta.shiori.event"] = nil
package.loaded["pasta.shiori.event.register"] = nil
package.loaded["pasta.store"] = nil
if reset_res ~= false then
package.loaded["pasta.shiori.res"] = nil
end
package.loaded["pasta.shiori.event.boot"] = nil
package.loaded["pasta.shiori.event.second_change"] = nil
local REG = require("pasta.shiori.event.register")
local STORE = require("pasta.store")
STORE.reset()
for k in pairs(REG) do
REG[k] = nil
end
local EVENT = require("pasta.shiori.event")
return EVENT, REG, STORE
end
describe("EVENT.fire - コルーチン対応", function()
local EVENT, REG, STORE
local function setup()
EVENT, REG, STORE = reload_event_modules()
end
test("handler が thread を返した場合 resume が実行される", function()
setup()
local resumed = false
REG.TestEvent = function(act)
return coroutine.create(function()
resumed = true
return "test_result"
end)
end
local req = { id = "TestEvent" }
EVENT.fire(req)
expect(resumed):toBe(true)
end)
test("threadがyieldした場合 STORE.co_scene が設定される", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
coroutine.yield("first_yield")
return "final"
end)
end
local req = { id = "TestEvent" }
EVENT.fire(req)
expect(type(STORE.co_scene)):toBe("thread")
expect(coroutine.status(STORE.co_scene)):toBe("suspended")
end)
test("threadが正常終了した場合 STORE.co_scene が nil にクリアされる", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
return "completed"
end)
end
local req = { id = "TestEvent" }
EVENT.fire(req)
expect(STORE.co_scene):toBe(nil)
end)
test("handler が string を返した場合 そのまま RES.ok() で返す", function()
setup()
REG.TestEvent = function(act)
return "string_result"
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("string_result")):toBeTruthy()
end)
test("handler が nil を返した場合 RES.no_content() が返る", function()
setup()
REG.TestEvent = function(act)
return nil
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("204 No Content")):toBeTruthy()
end)
test("yield値が RES.ok() で返される", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
coroutine.yield("yielded_value")
end)
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("yielded_value")):toBeTruthy()
end)
test("coroutine.resume() エラー時に STORE.co_scene がクリアされる", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
error("intentional_error")
end)
end
local req = { id = "TestEvent" }
local ok, err = pcall(function()
EVENT.fire(req)
end)
expect(ok):toBe(false)
expect(STORE.co_scene):toBe(nil)
end)
end)
describe("resume_until_valid - nil yieldスキップループ", function()
local EVENT
local function setup()
EVENT = reload_event_modules()
end
test("nil yieldしてsuspendedのコルーチンが再resumeされる (1.1)", function()
setup()
local resume_count = 0
local co = coroutine.create(function()
resume_count = resume_count + 1
coroutine.yield(nil) resume_count = resume_count + 1
coroutine.yield(nil) resume_count = resume_count + 1
return "valid_result"
end)
local ok, value = EVENT._resume_until_valid(co)
expect(ok):toBe(true)
expect(value):toBe("valid_result")
expect(resume_count):toBe(3)
end)
test("有効値(nil以外)を返したらループ終了 (1.2)", function()
setup()
local resume_count = 0
local co = coroutine.create(function()
resume_count = resume_count + 1
return "immediate_result"
end)
local ok, value = EVENT._resume_until_valid(co)
expect(ok):toBe(true)
expect(value):toBe("immediate_result")
expect(resume_count):toBe(1)
end)
test("dead状態でnilを返したらループ終了(空シーン) (1.2)", function()
setup()
local resume_count = 0
local co = coroutine.create(function()
resume_count = resume_count + 1
return nil
end)
local ok, value = EVENT._resume_until_valid(co)
expect(ok):toBe(true)
expect(value):toBe(nil) expect(resume_count):toBe(1)
expect(coroutine.status(co)):toBe("dead")
end)
test("エラー発生時にok=false、エラーメッセージを返す (1.3)", function()
setup()
local co = coroutine.create(function()
error("intentional_error")
end)
local ok, value = EVENT._resume_until_valid(co)
expect(ok):toBe(false)
expect(type(value)):toBe("string")
expect(value:find("intentional_error")):toBeTruthy()
end)
test("初回resume引数がコルーチンに渡される", function()
setup()
local received_arg = nil
local co = coroutine.create(function(arg)
received_arg = arg
return "done"
end)
local ok, value = EVENT._resume_until_valid(co, "test_arg")
expect(ok):toBe(true)
expect(received_arg):toBe("test_arg")
end)
test("2回目以降のresumeは引数なしで呼ばれる", function()
setup()
local args_received = {}
local co = coroutine.create(function(first_arg)
table.insert(args_received, first_arg or "nil")
local second_arg = coroutine.yield(nil) table.insert(args_received, second_arg or "nil")
return "done"
end)
local ok, value = EVENT._resume_until_valid(co, "first")
expect(ok):toBe(true)
expect(args_received[1]):toBe("first")
expect(args_received[2]):toBe("nil") end)
end)
describe("EVENT.fire - 既存コルーチン置換", function()
local EVENT, REG, STORE
local function setup()
EVENT, REG, STORE = reload_event_modules(false)
end
test("新しいコルーチンが設定されると既存のコルーチンがcloseされる", function()
setup()
local old_co = coroutine.create(function()
coroutine.yield("old")
end)
coroutine.resume(old_co) STORE.co_scene = old_co
REG.TestEvent = function(act)
return coroutine.create(function()
coroutine.yield("new")
end)
end
local req = { id = "TestEvent" }
EVENT.fire(req)
if coroutine.close then
expect(coroutine.status(old_co)):toBe("dead")
else
expect(coroutine.status(old_co)):toBe("suspended")
end
expect(type(STORE.co_scene)):toBe("thread")
expect(STORE.co_scene ~= old_co):toBe(true)
end)
test("同じコルーチンを再設定しようとしてもcloseしない", function()
setup()
local the_co
REG.TestEvent = function(act)
the_co = coroutine.create(function()
coroutine.yield("first")
coroutine.yield("second")
end)
return the_co
end
local req = { id = "TestEvent" }
EVENT.fire(req)
expect(STORE.co_scene):toBe(the_co)
expect(coroutine.status(the_co)):toBe("suspended")
end)
end)
describe("EVENT.fire - nil yieldスキップ統合", function()
local EVENT, REG, STORE
local function setup()
EVENT, REG, STORE = reload_event_modules()
end
test("nil yieldするthreadがresume_until_validで処理される (2.1)", function()
setup()
local resume_count = 0
REG.TestEvent = function(act)
return coroutine.create(function()
resume_count = resume_count + 1
coroutine.yield(nil) resume_count = resume_count + 1
coroutine.yield("valid_result") end)
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(resume_count):toBe(2)
expect(result:find("valid_result")):toBeTruthy()
end)
test("有効値を返すthreadハンドラが正常動作 (2.1)", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
return "immediate_result"
end)
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("immediate_result")):toBeTruthy()
end)
test("エラー発生時にset_co_sceneでcloseされる (3.1, 3.2)", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
error("test_error")
end)
end
local req = { id = "TestEvent" }
local ok, err = pcall(function()
EVENT.fire(req)
end)
expect(ok):toBe(false)
expect(err:find("test_error")):toBeTruthy()
expect(STORE.co_scene):toBe(nil)
end)
test("ループ終了後にsuspendedならset_co_sceneで保存 (2.4)", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
coroutine.yield(nil) coroutine.yield("valid") coroutine.yield("more")
end)
end
local req = { id = "TestEvent" }
EVENT.fire(req)
expect(type(STORE.co_scene)):toBe("thread")
expect(coroutine.status(STORE.co_scene)):toBe("suspended")
end)
test("空シーン(dead + nil)が204 No Contentに変換 (2.3)", function()
setup()
REG.TestEvent = function(act)
return coroutine.create(function()
return nil
end)
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("204 No Content")):toBeTruthy()
end)
end)
describe("EVENT.fire - 後方互換性", function()
local EVENT, REG
local function setup()
EVENT, REG = reload_event_modules()
end
test("既存の文字列ハンドラが正常動作 (2.2)", function()
setup()
REG.TestEvent = function(act)
return "string_response"
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("string_response")):toBeTruthy()
end)
test("既存のnil戻り値が204 No Contentに変換 (2.3)", function()
setup()
REG.TestEvent = function(act)
return nil
end
local req = { id = "TestEvent" }
local result = EVENT.fire(req)
expect(result:find("204 No Content")):toBeTruthy()
end)
end)
return true