package tests import "core:strings" import "core:testing" import "../src/adapters" import "../src/core" import "../src/shared" deepseek_calls: int phase2_deepseek_transport :: proc(cfg: shared.Config, request_json: string) -> (string, int, shared.App_Error) { _ = cfg _ = request_json deepseek_calls += 1 if deepseek_calls == 1 { return "", 429, shared.ok() } return "{\"choices\":[{\"message\":{\"content\":\"{\\\"title\\\":\\\"Test\\\",\\\"synopsis\\\":\\\"A hero rises\\\",\\\"characters\\\":[{\\\"id\\\":\\\"char_001\\\",\\\"name\\\":\\\"Hero\\\",\\\"role\\\":\\\"protagonist\\\",\\\"description\\\":\\\"Determined\\\",\\\"firstAppearancePanel\\\":\\\"panel_001_001\\\"}],\\\"pages\\\":[{\\\"pageNumber\\\":1,\\\"layoutType\\\":\\\"grid\\\",\\\"panels\\\":[{\\\"panelId\\\":\\\"panel_001_001\\\",\\\"panelNumber\\\":1,\\\"shotType\\\":\\\"medium\\\",\\\"description\\\":\\\"Hero stands\\\",\\\"charactersPresent\\\":[\\\"char_001\\\"],\\\"dialogue\\\":[{\\\"speakerId\\\":\\\"char_001\\\",\\\"text\\\":\\\"Let's go!\\\",\\\"bubbleType\\\":\\\"normal\\\",\\\"emotion\\\":\\\"determined\\\"}],\\\"caption\\\":\\\"\\\",\\\"soundEffects\\\":[],\\\"transitionFromPrevious\\\":\\\"none\\\"}]}]}\"}}]}", 200, shared.ok() } fal_calls: int phase2_fal_transport :: proc(cfg: shared.Config, endpoint, prompt, negative_prompt: string, seed: i64, image_size: string, reference_images: []string, reference_strength: f32) -> (string, int, shared.App_Error) { _ = cfg _ = endpoint _ = prompt _ = negative_prompt _ = seed _ = image_size _ = reference_images _ = reference_strength fal_calls += 1 if fal_calls == 1 { return "", 0, shared.network_error("temporary network issue") } return "https://example.com/image.png", 200, shared.ok() } @test deepseek_retries_then_succeeds :: proc(t: ^testing.T) { deepseek_calls = 0 cfg := shared.Config{ deepseek_api_key = "test-key", deepseek_base_url = "https://api.deepseek.com", } client := adapters.new_deepseek_client() client.transport = phase2_deepseek_transport client.max_retries = 3 opts := adapters.Generate_Script_Options{ story_idea = "A hero rises", genre = "action", art_style = "manga", num_pages = 4, audience = "general", } script, err := adapters.generate_comic_script(client, cfg, opts) defer core.dispose_script_owned(&script) testing.expect(t, shared.is_ok(err), "deepseek request should eventually succeed") testing.expect(t, deepseek_calls == 2, "expected one retry before success") testing.expect(t, len(script.pages) > 0, "generated script should contain pages") } @test fal_queue_enforces_cap :: proc(t: ^testing.T) { q := adapters.new_fal_queue(1) first := adapters.try_acquire_slot(&q) second := adapters.try_acquire_slot(&q) adapters.release_slot(&q) third := adapters.try_acquire_slot(&q) testing.expect(t, first, "first slot acquisition should succeed") testing.expect(t, !second, "second slot acquisition should fail when saturated") testing.expect(t, third, "slot acquisition should succeed after release") } @test fal_panel_generation_retries_network_error :: proc(t: ^testing.T) { fal_calls = 0 cfg := shared.Config{fal_api_key = "test-key"} q := adapters.new_fal_queue(2) client := adapters.new_fal_client(&q) client.transport = phase2_fal_transport client.max_retries = 3 panel := core.Panel{panel_id = "panel_1", panel_number = 1, description = "Hero jumps over a gap"} img, err := adapters.generate_panel_image(client, cfg, panel, nil, "manga", "proj_1", "", "") defer delete(img.prompt) testing.expect(t, shared.is_ok(err), "fal panel generation should eventually succeed") testing.expect(t, fal_calls == 2, "expected one retry for network error") testing.expect(t, len(img.url) > 0, "image url should be set") } @test fal_typed_response_parsing :: proc(t: ^testing.T) { body := "{\"images\":[{\"url\":\"https://example.com/a.png\",\"width\":1024,\"height\":1024}]}" resp, err := adapters.fal_parse_response_body(body) defer adapters.dispose_fal_response(&resp) testing.expect(t, shared.is_ok(err), "typed parse should succeed") testing.expect(t, len(resp.images) == 1, "expected one image") testing.expect(t, resp.images[0].url == "https://example.com/a.png", "expected parsed URL") } stream_chunks_received: int stream_final_received: bool test_stream_callback :: proc(chunk: string, is_complete: bool) -> () { stream_chunks_received += 1 if is_complete { stream_final_received = true } } @test deepseek_stream_callback_receives_chunks :: proc(t: ^testing.T) { stream_chunks_received = 0 stream_final_received = false // Test that the stream callback mechanism works // We can't easily mock curl streaming in tests, so test the callback type exists callback: adapters.Stream_Callback = test_stream_callback testing.expect(t, callback != nil, "stream callback type should be callable") } @test deepseek_stream_requires_api_key :: proc(t: ^testing.T) { cfg := shared.Config{deepseek_api_key = ""} opts := adapters.Generate_Script_Options{ story_idea = "Test", num_pages = 1, } _, err := adapters.stream_comic_script_stub(cfg, opts, nil) testing.expect(t, !shared.is_ok(err), "should fail without API key") testing.expect(t, strings.contains(err.message, "DEEPSEEK_API_KEY"), "error should mention missing key") } @test deepseek_stream_validates_options :: proc(t: ^testing.T) { cfg := shared.Config{deepseek_api_key = "test-key"} opts := adapters.Generate_Script_Options{ story_idea = "", num_pages = 1, } _, err := adapters.stream_comic_script_stub(cfg, opts, nil) testing.expect(t, !shared.is_ok(err), "should fail with empty story_idea") }