package tests import "core:fmt" import "core:os" import "core:strings" import "core:testing" import app "../src/app" import "../src/core" import "../src/shared" import "../src/ui" @test cli_parse_commands :: proc(t: ^testing.T) { c1 := app.parse_cli_command(nil) testing.expect(t, c1.kind == .Demo, "no args should map to demo") a2 := [1]string{"status"} c2 := app.parse_cli_command(a2[:]) testing.expect(t, c2.kind == .Status, "status should parse") a3 := [2]string{"save", "x.json"} c3 := app.parse_cli_command(a3[:]) testing.expect(t, c3.kind == .Save, "save should parse") testing.expect(t, c3.path == "x.json", "save path should parse") a4 := [1]string{"tui"} c4 := app.parse_cli_command(a4[:]) testing.expect(t, c4.kind == .Tui, "tui should parse") a5 := [1]string{"gui"} c5 := app.parse_cli_command(a5[:]) testing.expect(t, c5.kind == .Gui, "gui should parse") testing.expect(t, app.normalize_tui_command("q") == "quit", "q alias should expand") testing.expect(t, app.normalize_tui_command("1") == "goto story", "1 alias should map to story") testing.expect(t, app.normalize_tui_command("?") == "doctor", "? alias should map to doctor") testing.expect(t, app.normalize_tui_command("r") == "ready", "r alias should map to ready") testing.expect(t, app.normalize_tui_command("n") == "next", "n alias should map to next") testing.expect(t, app.normalize_tui_command("p") == "plan", "p alias should map to plan") testing.expect(t, app.normalize_tui_command("x") == "auto", "x alias should map to auto") pages, matched, perr := app.parse_generate_script_pages("generate script 6") testing.expect(t, shared.is_ok(perr), "generate script pages parse should succeed") testing.expect(t, matched, "generate script pages should match") testing.expect(t, pages == 6, "generate script pages should parse value") local_pages, lmatched, lerr := app.parse_generate_script_local_pages("generate script local 3") testing.expect(t, shared.is_ok(lerr), "generate script local parse should succeed") testing.expect(t, lmatched, "generate script local should match") testing.expect(t, local_pages == 3, "generate script local should parse value") page, pmatch, pperr := app.parse_generate_panels_page("generate panels page 2") testing.expect(t, shared.is_ok(pperr), "generate panels page parse should succeed") testing.expect(t, pmatch, "generate panels page should match") testing.expect(t, page == 2, "generate panels page should parse value") lpage, lpmatch, lperr := app.parse_generate_panels_local_page("generate panels local page 2") testing.expect(t, shared.is_ok(lperr), "generate panels local page parse should succeed") testing.expect(t, lpmatch, "generate panels local page should match") testing.expect(t, lpage == 2, "generate panels local page should parse value") fmt_kind, export_path, ematch, eerr := app.parse_export_command("export cbz ./out.cbz") testing.expect(t, shared.is_ok(eerr), "export parse should succeed") testing.expect(t, ematch, "export parse should match") testing.expect(t, fmt_kind == .CBZ, "export format should parse") testing.expect(t, export_path == "./out.cbz", "export path should parse") qfmt, qpath, qpages, qmatch, qerr := app.parse_quick_local_command("quick local pdf ./quick.pdf 3") testing.expect(t, shared.is_ok(qerr), "quick local parse should succeed") testing.expect(t, qmatch, "quick local parse should match") testing.expect(t, qfmt == .PDF, "quick local format should parse") testing.expect(t, qpath == "./quick.pdf", "quick local path should parse") testing.expect(t, qpages == 3, "quick local pages should parse") proj, qafmt, qaout, qapages, qamatch, qaerr := app.parse_quick_local_all_command("quick local all ./p.comic.json cbz ./q.cbz 4") testing.expect(t, shared.is_ok(qaerr), "quick local all parse should succeed") testing.expect(t, qamatch, "quick local all parse should match") testing.expect(t, proj == "./p.comic.json", "quick local all project path should parse") testing.expect(t, qafmt == .CBZ, "quick local all format should parse") testing.expect(t, qaout == "./q.cbz", "quick local all export path should parse") testing.expect(t, qapages == 4, "quick local all pages should parse") aafmt, aapath, aamatch, aaerr := app.parse_auto_all_command("auto all pdf ./auto.pdf") testing.expect(t, shared.is_ok(aaerr), "auto all parse should succeed") testing.expect(t, aamatch, "auto all parse should match") testing.expect(t, aafmt == .PDF, "auto all format should parse") testing.expect(t, aapath == "./auto.pdf", "auto all path should parse") aalfmt, aalpath, aalpages, aalmatch, aalerr := app.parse_auto_all_local_command("auto all local cbz ./auto.cbz 3") testing.expect(t, shared.is_ok(aalerr), "auto all local parse should succeed") testing.expect(t, aalmatch, "auto all local parse should match") testing.expect(t, aalfmt == .CBZ, "auto all local format should parse") testing.expect(t, aalpath == "./auto.cbz", "auto all local path should parse") testing.expect(t, aalpages == 3, "auto all local pages should parse") } @test cli_save_and_load_roundtrip :: proc(t: ^testing.T) { tmp_dir, terr := os.make_directory_temp("", "comic-cli-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) path := fmt.aprintf("%s/project.comic.json", tmp_dir) defer delete(path) state := core.new_initial_state() state.story_idea = "cli story" save_out, save_err := app.run_cli_command(app.Parsed_CLI_Command{kind = .Save, path = path}, &state) testing.expect(t, shared.is_ok(save_err), "save command should succeed") testing.expect(t, strings.contains(save_out, "Saved project"), "save output should mention save") delete(save_out) state.story_idea = "changed" load_out, load_err := app.run_cli_command(app.Parsed_CLI_Command{kind = .Load, path = path}, &state) testing.expect(t, shared.is_ok(load_err), "load command should succeed") testing.expect(t, strings.contains(load_out, "Loaded project"), "load output should mention load") testing.expect(t, state.story_idea == "cli story", "load should restore story") delete(load_out) core.dispose_state_owned(&state) } @test cli_tui_generate_script_requires_key :: proc(t: ^testing.T) { prev := os.get_env("DEEPSEEK_API_KEY", context.temp_allocator) defer { if len(prev) > 0 { _ = os.set_env("DEEPSEEK_API_KEY", prev) } else { _ = os.unset_env("DEEPSEEK_API_KEY") } } _ = os.unset_env("DEEPSEEK_API_KEY") state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out, err := app.run_tui_command(&controller, "generate script 6", &last_job) testing.expect(t, !shared.is_ok(err), "generate script should fail without configured key") testing.expect(t, len(out) == 0, "error path should not return output") } @test cli_tui_generate_panels_page_requires_script :: proc(t: ^testing.T) { state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out, err := app.run_tui_command(&controller, "generate panels page 2", &last_job) testing.expect(t, !shared.is_ok(err), "generate panels page should fail with empty script") testing.expect(t, len(out) == 0, "error path should not return output") } @test cli_tui_generate_script_local_succeeds_without_key :: proc(t: ^testing.T) { state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out, err := app.run_tui_command(&controller, "generate script local 2", &last_job) testing.expect(t, shared.is_ok(err), "generate script local should succeed without key") testing.expect(t, strings.contains(out, "local script generated"), "local generation should return success message") delete(out) testing.expect(t, len(controller.state.script.pages) == 2, "local script should create requested pages") } @test cli_tui_layout_and_export_require_data :: proc(t: ^testing.T) { state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out1, err1 := app.run_tui_command(&controller, "layout auto", &last_job) testing.expect(t, !shared.is_ok(err1), "layout auto should fail without script") testing.expect(t, len(out1) == 0, "layout error path should not return output") _, out2, err2 := app.run_tui_command(&controller, "export pdf ./tmp.pdf", &last_job) testing.expect(t, !shared.is_ok(err2), "export should fail without layouts") testing.expect(t, len(out2) == 0, "export error path should not return output") } @test cli_tui_local_panels_and_export_pdf :: proc(t: ^testing.T) { tmp_dir, terr := os.make_directory_temp("", "comic-cli-local-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) out_pdf := fmt.aprintf("%s/local.pdf", tmp_dir) defer delete(out_pdf) state := core.new_initial_state() controller := ui.new_controller(state) defer { for _, img in controller.state.panel_images { delete(img.url) delete(img.prompt) } delete(controller.state.panel_images) controller.state.panel_images = nil ui.dispose_controller(&controller) } last_job := 0 _, out1, err1 := app.run_tui_command(&controller, "generate script local 2", &last_job) testing.expect(t, shared.is_ok(err1), "local script should succeed") delete(out1) _, out2, err2 := app.run_tui_command(&controller, "generate panels local", &last_job) testing.expect(t, shared.is_ok(err2), "local panels should succeed") delete(out2) _, out3, err3 := app.run_tui_command(&controller, "layout auto", &last_job) testing.expect(t, shared.is_ok(err3), "layout auto should succeed") delete(out3) export_cmd := fmt.aprintf("export pdf %s", out_pdf) defer delete(export_cmd) _, out4, err4 := app.run_tui_command(&controller, export_cmd, &last_job) testing.expect(t, shared.is_ok(err4), "export should succeed") delete(out4) testing.expect(t, os.exists(out_pdf), "exported pdf should exist") } @test cli_tui_quick_local_export_pdf :: proc(t: ^testing.T) { tmp_dir, terr := os.make_directory_temp("", "comic-cli-quick-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) out_pdf := fmt.aprintf("%s/quick.pdf", tmp_dir) defer delete(out_pdf) state := core.new_initial_state() controller := ui.new_controller(state) defer { for _, img in controller.state.panel_images { delete(img.url) delete(img.prompt) } delete(controller.state.panel_images) controller.state.panel_images = nil ui.dispose_controller(&controller) } cmd := fmt.aprintf("quick local pdf %s 3", out_pdf) defer delete(cmd) last_job := 0 _, out, err := app.run_tui_command(&controller, cmd, &last_job) testing.expect(t, shared.is_ok(err), "quick local should succeed") testing.expect(t, strings.contains(out, "quick local exported"), "quick local should return success output") delete(out) testing.expect(t, len(controller.state.script.pages) == 3, "quick local should build requested page count") testing.expect(t, os.exists(out_pdf), "quick-local exported pdf should exist") } @test cli_tui_quick_local_all_saves_and_exports :: proc(t: ^testing.T) { tmp_dir, terr := os.make_directory_temp("", "comic-cli-quick-all-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) project_path := fmt.aprintf("%s/project.comic.json", tmp_dir) export_path := fmt.aprintf("%s/quick.cbz", tmp_dir) defer delete(project_path) defer delete(export_path) state := core.new_initial_state() controller := ui.new_controller(state) defer { for _, img in controller.state.panel_images { delete(img.url) delete(img.prompt) } delete(controller.state.panel_images) controller.state.panel_images = nil ui.dispose_controller(&controller) } cmd := fmt.aprintf("quick local all %s cbz %s 2", project_path, export_path) defer delete(cmd) last_job := 0 _, out, err := app.run_tui_command(&controller, cmd, &last_job) testing.expect(t, shared.is_ok(err), "quick local all should succeed") testing.expect(t, strings.contains(out, "quick local all saved"), "quick local all should return success output") delete(out) testing.expect(t, os.exists(project_path), "quick local all should save project") testing.expect(t, os.exists(export_path), "quick local all should export artifact") } @test cli_tui_doctor_reports_status :: proc(t: ^testing.T) { state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out, err := app.run_tui_command(&controller, "doctor", &last_job) testing.expect(t, shared.is_ok(err), "doctor should succeed") testing.expect(t, strings.contains(out, "Doctor"), "doctor output should include header") testing.expect(t, strings.contains(out, "deepseek key:"), "doctor output should include deepseek key status") testing.expect(t, strings.contains(out, "curl:"), "doctor output should include curl status") delete(out) } @test cli_tui_ready_reports_status :: proc(t: ^testing.T) { state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out, err := app.run_tui_command(&controller, "ready", &last_job) testing.expect(t, shared.is_ok(err), "ready should succeed") testing.expect(t, strings.contains(out, "Ready"), "ready output should include header") testing.expect(t, strings.contains(out, "script generated:"), "ready output should include script status") testing.expect(t, strings.contains(out, "export ready:"), "ready output should include export status") delete(out) } @test cli_tui_next_recommends_action :: proc(t: ^testing.T) { prev_deep := os.get_env("DEEPSEEK_API_KEY", context.temp_allocator) prev_fal := os.get_env("FAL_API_KEY", context.temp_allocator) defer { if len(prev_deep) > 0 { _ = os.set_env("DEEPSEEK_API_KEY", prev_deep) } else { _ = os.unset_env("DEEPSEEK_API_KEY") } if len(prev_fal) > 0 { _ = os.set_env("FAL_API_KEY", prev_fal) } else { _ = os.unset_env("FAL_API_KEY") } } _ = os.unset_env("DEEPSEEK_API_KEY") _ = os.unset_env("FAL_API_KEY") state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out1, err1 := app.run_tui_command(&controller, "next", &last_job) testing.expect(t, shared.is_ok(err1), "next should succeed") testing.expect(t, strings.contains(out1, "generate script local"), "next should recommend local script generation") delete(out1) _, out2, err2 := app.run_tui_command(&controller, "generate script local 1", &last_job) testing.expect(t, shared.is_ok(err2), "generate script local should succeed") delete(out2) _, out3, err3 := app.run_tui_command(&controller, "next", &last_job) testing.expect(t, shared.is_ok(err3), "next should succeed after script") testing.expect(t, strings.contains(out3, "generate panels local"), "next should recommend local panel generation") delete(out3) } @test cli_tui_plan_reports_progress :: proc(t: ^testing.T) { state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out1, err1 := app.run_tui_command(&controller, "plan", &last_job) testing.expect(t, shared.is_ok(err1), "plan should succeed") testing.expect(t, strings.contains(out1, "Plan"), "plan output should include header") testing.expect(t, strings.contains(out1, "1) Script"), "plan output should include script step") delete(out1) _, out2, err2 := app.run_tui_command(&controller, "generate script local 1", &last_job) testing.expect(t, shared.is_ok(err2), "local script should succeed") delete(out2) _, out3, err3 := app.run_tui_command(&controller, "plan", &last_job) testing.expect(t, shared.is_ok(err3), "plan should succeed after script") testing.expect(t, strings.contains(out3, "next:"), "plan output should include next hint") delete(out3) } @test cli_tui_auto_runs_next_step :: proc(t: ^testing.T) { prev_deep := os.get_env("DEEPSEEK_API_KEY", context.temp_allocator) prev_fal := os.get_env("FAL_API_KEY", context.temp_allocator) defer { if len(prev_deep) > 0 { _ = os.set_env("DEEPSEEK_API_KEY", prev_deep) } else { _ = os.unset_env("DEEPSEEK_API_KEY") } if len(prev_fal) > 0 { _ = os.set_env("FAL_API_KEY", prev_fal) } else { _ = os.unset_env("FAL_API_KEY") } } _ = os.unset_env("DEEPSEEK_API_KEY") _ = os.unset_env("FAL_API_KEY") state := core.new_initial_state() controller := ui.new_controller(state) defer ui.dispose_controller(&controller) last_job := 0 _, out, err := app.run_tui_command(&controller, "auto", &last_job) testing.expect(t, shared.is_ok(err), "auto should succeed") testing.expect(t, strings.contains(out, "auto ran:"), "auto output should include executed command") delete(out) testing.expect(t, len(controller.state.script.pages) > 0, "auto should progress by generating script") } @test cli_tui_auto_all_runs_to_export :: proc(t: ^testing.T) { prev_deep := os.get_env("DEEPSEEK_API_KEY", context.temp_allocator) prev_fal := os.get_env("FAL_API_KEY", context.temp_allocator) defer { if len(prev_deep) > 0 { _ = os.set_env("DEEPSEEK_API_KEY", prev_deep) } else { _ = os.unset_env("DEEPSEEK_API_KEY") } if len(prev_fal) > 0 { _ = os.set_env("FAL_API_KEY", prev_fal) } else { _ = os.unset_env("FAL_API_KEY") } } _ = os.unset_env("DEEPSEEK_API_KEY") _ = os.unset_env("FAL_API_KEY") tmp_dir, terr := os.make_directory_temp("", "comic-cli-auto-all-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) out_pdf := fmt.aprintf("%s/auto_all.pdf", tmp_dir) defer delete(out_pdf) state := core.new_initial_state() controller := ui.new_controller(state) defer { for _, img in controller.state.panel_images { delete(img.url) delete(img.prompt) } delete(controller.state.panel_images) controller.state.panel_images = nil ui.dispose_controller(&controller) } last_job := 0 cmd := fmt.aprintf("auto all pdf %s", out_pdf) defer delete(cmd) _, out, err := app.run_tui_command(&controller, cmd, &last_job) testing.expect(t, shared.is_ok(err), "auto all should succeed") testing.expect(t, strings.contains(out, "auto all exported"), "auto all output should include success") delete(out) testing.expect(t, os.exists(out_pdf), "auto all should produce export file") } @test cli_tui_auto_all_local_runs_to_export :: proc(t: ^testing.T) { tmp_dir, terr := os.make_directory_temp("", "comic-cli-auto-all-local-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) out_cbz := fmt.aprintf("%s/auto_all_local.cbz", tmp_dir) defer delete(out_cbz) state := core.new_initial_state() controller := ui.new_controller(state) defer { for _, img in controller.state.panel_images { delete(img.url) delete(img.prompt) } delete(controller.state.panel_images) controller.state.panel_images = nil ui.dispose_controller(&controller) } last_job := 0 cmd := fmt.aprintf("auto all local cbz %s 2", out_cbz) defer delete(cmd) _, out, err := app.run_tui_command(&controller, cmd, &last_job) testing.expect(t, shared.is_ok(err), "auto all local should succeed") testing.expect(t, strings.contains(out, "auto all local exported"), "auto all local output should include success") delete(out) testing.expect(t, os.exists(out_cbz), "auto all local should produce export file") } @test cli_tui_open_and_saveas_aliases :: proc(t: ^testing.T) { tmp_dir, terr := os.make_directory_temp("", "comic-cli-alias-*", context.temp_allocator) if terr != nil { testing.expect(t, false, "failed to create temp dir") return } defer os.remove_all(tmp_dir) path := fmt.aprintf("%s/alias.comic.json", tmp_dir) defer delete(path) state := core.new_initial_state() controller := ui.new_controller(state) controller.state.story_idea = "alias story" last_job := 0 save_cmd := fmt.aprintf("saveas %s", path) defer delete(save_cmd) _, out1, err1 := app.run_tui_command(&controller, save_cmd, &last_job) testing.expect(t, shared.is_ok(err1), "saveas should succeed") delete(out1) controller.state.story_idea = "changed" open_cmd := fmt.aprintf("open %s", path) defer delete(open_cmd) _, out2, err2 := app.run_tui_command(&controller, open_cmd, &last_job) testing.expect(t, shared.is_ok(err2), "open should succeed") delete(out2) testing.expect(t, controller.state.story_idea == "alias story", "open should restore saved state") if shared.is_ok(err2) { ui.dispose_controller_owned(&controller) } else { ui.dispose_controller(&controller) } }