#define _POSIX_C_SOURCE 200809L /* for mkstemp/fdopen */ #include #include #include #include #include #include "../../engine/src/i18n.h" #include "../src/guidance.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 DailyInterpretation make_interp(DaySignificance level, AspectType type, Body transiting) { DailyInterpretation interp; interp.top_item_count = 1; interp.day_level = level; interp.top_items[0].kind = ITEM_ASPECT; interp.top_items[0].score = 9.5; interp.top_items[0].aspect.transiting_planet = transiting; interp.top_items[0].aspect.natal_planet = PLANET_MOON; interp.top_items[0].aspect.type = type; interp.top_items[0].aspect.orb = 0.5; return interp; } static DailyInterpretation make_empty_interp(void) { DailyInterpretation interp; interp.top_item_count = 0; interp.day_level = DAY_QUIET; return interp; } static CelticCrossSpread make_spread(TarotCard attitude_card, bool attitude_reversed, TarotCard outcome_card, bool outcome_reversed) { CelticCrossSpread spread; spread.positions[POSITION_ATTITUDE].card = attitude_card; spread.positions[POSITION_ATTITUDE].reversed = attitude_reversed; spread.positions[POSITION_OUTCOME].card = outcome_card; spread.positions[POSITION_OUTCOME].reversed = outcome_reversed; return spread; } static void test_harmonious_upright(void) { DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_TRINE, PLANET_JUPITER); CelticCrossSpread spread = make_spread(CARD_SUN, false, CARD_WORLD, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "already meeting the day in the right spirit") != NULL); assert(strstr(text, "Jupiter") != NULL); assert(strstr(text, "The Sun: Material happiness") != NULL); assert(strstr(text, "The World: Assured success") != NULL); assert(strstr(text, "(Reversed)") == NULL); fclose(f); printf("PASS test_harmonious_upright\n"); } static void test_discordant_reversed(void) { DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_SQUARE, PLANET_SATURN); CelticCrossSpread spread = make_spread(CARD_DEVIL, true, CARD_WORLD, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "Both the day and your own footing are unsettled") != NULL); assert(strstr(text, "Saturn") != NULL); assert(strstr(text, "The Devil (Reversed): Evil fatality") != NULL); assert(strstr(text, "The World: Assured success") != NULL); fclose(f); printf("PASS test_discordant_reversed\n"); } static void test_neutral_conjunction_upright(void) { DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_CONJUNCTION, PLANET_PLUTO); CelticCrossSpread spread = make_spread(CARD_EMPEROR, false, CARD_MOON, true); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "Today concentrates whatever you already bring to it") != NULL); assert(strstr(text, "Pluto") != NULL); assert(strstr(text, "The Emperor: Stability, power") != NULL); assert(strstr(text, "The Moon (Reversed): Instability, inconstancy") != NULL); fclose(f); printf("PASS test_neutral_conjunction_upright\n"); } /* The stance sentence itself doesn't change with intensity - only the * sentence introducing the transiting planet does (see k_intro in * guidance.c). These three cover the remaining day levels below Major * (which test_harmonious_upright already covers). */ static void test_significant_day_uses_significant_intro(void) { DailyInterpretation interp = make_interp(DAY_SIGNIFICANT, ASPECT_SEXTILE, PLANET_VENUS); CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "clearly active today") != NULL); assert(strstr(text, "Venus") != NULL); fclose(f); printf("PASS test_significant_day_uses_significant_intro\n"); } static void test_notable_day_uses_notable_intro(void) { DailyInterpretation interp = make_interp(DAY_NOTABLE, ASPECT_SEXTILE, PLANET_MARS); CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "mild touch from Mars") != NULL); fclose(f); printf("PASS test_notable_day_uses_notable_intro\n"); } static void test_quiet_day_with_a_faint_item_still_names_it(void) { DailyInterpretation interp = make_interp(DAY_QUIET, ASPECT_SEXTILE, PLANET_MERCURY); CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "only faintly active today") != NULL); assert(strstr(text, "Mercury") != NULL); fclose(f); printf("PASS test_quiet_day_with_a_faint_item_still_names_it\n"); } static void test_no_aspects_at_all_falls_back_to_attitude_only(void) { DailyInterpretation interp = make_empty_interp(); CelticCrossSpread spread = make_spread(CARD_STAR, false, CARD_SUN, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "astrologically quiet day") != NULL); assert(strstr(text, "trust your current footing") != NULL); assert(strstr(text, "The Star: Loss, theft") != NULL); assert(strstr(text, "The Sun: Material happiness") != NULL); fclose(f); printf("PASS test_no_aspects_at_all_falls_back_to_attitude_only\n"); } static void test_no_aspects_at_all_respects_reversed_attitude(void) { DailyInterpretation interp = make_empty_interp(); CelticCrossSpread spread = make_spread(CARD_STAR, true, CARD_SUN, false); FILE *f = tmpfile(); guidance_print(f, "", &interp, &spread); const char *text = slurp(f); assert(strstr(text, "quietly re-settle your own footing") != NULL); fclose(f); printf("PASS test_no_aspects_at_all_respects_reversed_attitude\n"); } /* Proves guidance_print() actually routes through i18n_get() rather * than just printing the hardcoded fallback. Run last: i18n_load() * replaces the process-global catalog for the rest of the binary's * lifetime. */ static void test_translation_catalog_overrides_fallback_text(void) { char path[] = "/tmp/deck_guidance_test_XXXXXX"; int fd = mkstemp(path); assert(fd >= 0); FILE *f = fdopen(fd, "w"); fprintf(f, "guidance.stance.discordant.upright=UEBERSETZTE HALTUNG\n"); fprintf(f, "guidance.intro.major=UEBERSETZTE EINLEITUNG %%s\n"); fprintf(f, "guidance.planet.saturn=UEBERSETZTER SATURN\n"); fprintf(f, "guidance.attitude_intro=UEBERSETZTE HALTUNGSKARTE\n"); fclose(f); assert(i18n_load(path)); unlink(path); DailyInterpretation interp = make_interp(DAY_MAJOR, ASPECT_SQUARE, PLANET_SATURN); CelticCrossSpread spread = make_spread(CARD_SUN, false, CARD_WORLD, false); FILE *out = tmpfile(); guidance_print(out, "", &interp, &spread); const char *text = slurp(out); assert(strstr(text, "UEBERSETZTE HALTUNG") != NULL); assert(strstr(text, "UEBERSETZTE EINLEITUNG UEBERSETZTER SATURN") != NULL); assert(strstr(text, "UEBERSETZTE HALTUNGSKARTE") != NULL); assert(strstr(text, "Your instincts are sound") == NULL); /* English fallback must not leak through */ fclose(out); printf("PASS test_translation_catalog_overrides_fallback_text\n"); } int main(void) { test_harmonious_upright(); test_discordant_reversed(); test_neutral_conjunction_upright(); test_significant_day_uses_significant_intro(); test_notable_day_uses_notable_intro(); test_quiet_day_with_a_faint_item_still_names_it(); test_no_aspects_at_all_falls_back_to_attitude_only(); test_no_aspects_at_all_respects_reversed_attitude(); test_translation_catalog_overrides_fallback_text(); printf("All guidance tests passed.\n"); return 0; }