#define _POSIX_C_SOURCE 200809L /* for mkstemp/fdopen */ #include #include #include #include #include #include "../../engine/src/i18n.h" #include "../src/narrative.h" static const char *slurp(FILE *f) { static char buf[4096]; long len = ftell(f); rewind(f); size_t n = fread(buf, 1, (size_t)len, f); buf[n] = '\0'; return buf; } static void test_hard_aspect_shows_base_but_not_harmonious_bonus(void) { Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON, .type = ASPECT_SQUARE, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 0); const char *text = slurp(f); assert(strstr(text, "depression") != NULL); assert(strstr(text, "past associations") == NULL); fclose(f); printf("PASS test_hard_aspect_shows_base_but_not_harmonious_bonus\n"); } static void test_soft_aspect_adds_harmonious_bonus(void) { Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_SUN, .type = ASPECT_TRINE, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 0); const char *text = slurp(f); assert(strstr(text, "depression") != NULL); assert(strstr(text, "past associations") != NULL); fclose(f); printf("PASS test_soft_aspect_adds_harmonious_bonus\n"); } static void test_conjunction_is_neutral_prints_base_only(void) { Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_SUN, .type = ASPECT_CONJUNCTION, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 0); const char *text = slurp(f); assert(strstr(text, "depression") != NULL); assert(strstr(text, "past associations") == NULL); fclose(f); printf("PASS test_conjunction_is_neutral_prints_base_only\n"); } /* The Sun has no unconditional base text (see narrative.c) - a neutral * conjunction to it should print nothing at all. */ static void test_empty_base_and_neutral_aspect_prints_nothing(void) { Aspect a = { .transiting_planet = PLANET_SUN, .natal_planet = PLANET_MOON, .type = ASPECT_CONJUNCTION, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 0); long len = ftell(f); assert(len == 0); fclose(f); printf("PASS test_empty_base_and_neutral_aspect_prints_nothing\n"); } static void test_discordant_aspect_on_empty_base_planet(void) { Aspect a = { .transiting_planet = PLANET_SUN, .natal_planet = PLANET_MOON, .type = ASPECT_OPPOSITION, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 0); const char *text = slurp(f); assert(strstr(text, "Degradation") != NULL); fclose(f); printf("PASS test_discordant_aspect_on_empty_base_planet\n"); } static void test_known_house_adds_area_framing(void) { Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON, .type = ASPECT_SQUARE, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 3); const char *text = slurp(f); assert(strstr(text, "house 3") != NULL); assert(strstr(text, "communication") != NULL); assert(strstr(text, "depression") != NULL); fclose(f); printf("PASS test_known_house_adds_area_framing\n"); } static void test_unknown_house_omits_area_framing(void) { Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON, .type = ASPECT_SQUARE, .orb = 1.0 }; FILE *f = tmpfile(); narrative_print(f, " ", &a, 0); /* 0 = "no data" sentinel, not a real house */ const char *text = slurp(f); assert(strstr(text, "house") == NULL); assert(strstr(text, "depression") != NULL); fclose(f); printf("PASS test_unknown_house_omits_area_framing\n"); } /* Proves narrative_print() actually routes through i18n_get() rather * than just printing the hardcoded fallback - loads a translation file * that overrides one planet's base text and one house's area text, and * checks the override wins. Run last: i18n_load() replaces the * process-global catalog for the rest of the test binary's lifetime. */ static void test_translation_catalog_overrides_fallback_text(void) { char path[] = "/tmp/deck_narrative_test_XXXXXX"; int fd = mkstemp(path); assert(fd >= 0); FILE *f = fdopen(fd, "w"); fprintf(f, "narrative.saturn.base=UEBERSETZTER SATURN TEXT\n"); fprintf(f, "narrative.house.3=UEBERSETZTES HAUS DREI\n"); fclose(f); assert(i18n_load(path)); unlink(path); Aspect a = { .transiting_planet = PLANET_SATURN, .natal_planet = PLANET_MOON, .type = ASPECT_CONJUNCTION, .orb = 1.0 }; FILE *out = tmpfile(); narrative_print(out, "", &a, 3); const char *text = slurp(out); assert(strstr(text, "UEBERSETZTER SATURN TEXT") != NULL); assert(strstr(text, "UEBERSETZTES HAUS DREI") != NULL); assert(strstr(text, "depression") == NULL); /* English fallback must not leak through */ fclose(out); printf("PASS test_translation_catalog_overrides_fallback_text\n"); } int main(void) { test_hard_aspect_shows_base_but_not_harmonious_bonus(); test_soft_aspect_adds_harmonious_bonus(); test_conjunction_is_neutral_prints_base_only(); test_empty_base_and_neutral_aspect_prints_nothing(); test_discordant_aspect_on_empty_base_planet(); test_known_house_adds_area_framing(); test_unknown_house_omits_area_framing(); test_translation_catalog_overrides_fallback_text(); printf("All narrative tests passed.\n"); return 0; }