#include #include #include #include "../src/json.h" #include "../src/reading_io.h" static void test_json_parse_basic_shapes(void) { const char *text = "{\"a\": 1, \"b\": [true, false, null, \"text\"], \"c\": {\"nested\": 2.5}}"; JsonValue *root = json_parse(text, strlen(text)); assert(root && root->type == JSON_OBJECT); assert(json_as_number(json_object_get(root, "a")) == 1.0); const JsonValue *b = json_object_get(root, "b"); assert(json_array_count(b) == 4); assert(json_array_get(b, 0)->type == JSON_BOOL && json_array_get(b, 0)->as.boolean == true); assert(json_array_get(b, 1)->type == JSON_BOOL && json_array_get(b, 1)->as.boolean == false); assert(json_array_get(b, 2)->type == JSON_NULL); assert(strcmp(json_as_string(json_array_get(b, 3)), "text") == 0); const JsonValue *c = json_object_get(root, "c"); assert(json_as_number(json_object_get(c, "nested")) == 2.5); json_free(root); printf("PASS test_json_parse_basic_shapes\n"); } static void test_json_parse_rejects_malformed(void) { const char *bad_inputs[] = { "{not valid json", "[1, 2,]", "{\"a\": }", "", }; for (size_t i = 0; i < sizeof(bad_inputs) / sizeof(bad_inputs[0]); i++) { JsonValue *root = json_parse(bad_inputs[i], strlen(bad_inputs[i])); assert(root == NULL); } printf("PASS test_json_parse_rejects_malformed\n"); } /* A trimmed-but-structurally-faithful fixture matching deck-engine's real * --format json shape: "spread" is present with decoy content the loader * must skip over without understanding its schema, to prove it navigates * by key path rather than assuming any position. "natal"/"transits" * bodies are real (if partial) - reading_load_json now parses sign/house * from them for display, alongside the aspects used for scoring. */ static const char *k_fixture = "{" " \"natal\": {\"bodies\": [{\"body\": \"sun\", \"sign\": \"taurus\", \"house\": 3}], \"houses\": []}," " \"transits\": {" " \"bodies\": [{\"body\": \"sun\", \"sign\": \"cancer\", \"house\": 5}]," " \"moon_phase\": \"full\"," " \"aspects\": [" " {\"transiting_planet\": \"pluto\", \"natal_planet\": \"moon\", \"type\": \"opposition\", \"orb\": 0.2}," " {\"transiting_planet\": \"saturn\", \"natal_planet\": \"sun\", \"type\": \"square\", \"orb\": 0.5}" " ]" " }," " \"spread\": {\"positions\": [{\"position\": \"present\", \"card\": \"devil\", \"reversed\": false}]}" "}"; static void test_reading_load_json_extracts_aspects(void) { DailyReading reading; bool ok = reading_load_json(k_fixture, strlen(k_fixture), &reading); assert(ok); assert(reading.transits.aspect_count == 2); assert(reading.transits.aspects[0].transiting_planet == PLANET_PLUTO); assert(reading.transits.aspects[0].natal_planet == PLANET_MOON); assert(reading.transits.aspects[0].type == ASPECT_OPPOSITION); assert(reading.transits.aspects[0].orb == 0.2); assert(reading.transits.aspects[1].transiting_planet == PLANET_SATURN); assert(reading.transits.aspects[1].natal_planet == PLANET_SUN); assert(reading.transits.aspects[1].type == ASPECT_SQUARE); printf("PASS test_reading_load_json_extracts_aspects\n"); } static void test_reading_load_json_extracts_body_sign_and_house(void) { DailyReading reading; bool ok = reading_load_json(k_fixture, strlen(k_fixture), &reading); assert(ok); assert(reading.natal.bodies[PLANET_SUN].sign == SIGN_TAURUS); assert(reading.natal.bodies[PLANET_SUN].house == 3); assert(reading.transits.bodies[PLANET_SUN].sign == SIGN_CANCER); assert(reading.transits.bodies[PLANET_SUN].house == 5); /* Bodies missing from the fixture (e.g. the Moon) get the "no data" * sentinel rather than a garbage house number. */ assert(reading.natal.bodies[PLANET_MOON].house == 0); assert(reading.transits.bodies[PLANET_MOON].house == 0); printf("PASS test_reading_load_json_extracts_body_sign_and_house\n"); } static void test_reading_load_json_unknown_body_slug_is_skipped_not_fatal(void) { const char *text = "{\"natal\": {\"bodies\": [{\"body\": \"xenu\", \"sign\": \"taurus\", \"house\": 3}]}," " \"transits\": {\"aspects\": []}}"; DailyReading reading; bool ok = reading_load_json(text, strlen(text), &reading); assert(ok); assert(reading.transits.aspect_count == 0); printf("PASS test_reading_load_json_unknown_body_slug_is_skipped_not_fatal\n"); } static void test_reading_load_json_empty_aspects_is_valid(void) { const char *text = "{\"transits\": {\"aspects\": []}}"; DailyReading reading; bool ok = reading_load_json(text, strlen(text), &reading); assert(ok); assert(reading.transits.aspect_count == 0); printf("PASS test_reading_load_json_empty_aspects_is_valid\n"); } static void test_reading_load_json_rejects_missing_transits(void) { const char *text = "{\"natal\": {}, \"spread\": {}}"; DailyReading reading; bool ok = reading_load_json(text, strlen(text), &reading); assert(!ok); printf("PASS test_reading_load_json_rejects_missing_transits\n"); } static void test_reading_load_json_rejects_unknown_slug(void) { const char *text = "{\"transits\": {\"aspects\": [" " {\"transiting_planet\": \"xenu\", \"natal_planet\": \"moon\", \"type\": \"square\", \"orb\": 1.0}" "]}}"; DailyReading reading; bool ok = reading_load_json(text, strlen(text), &reading); assert(!ok); printf("PASS test_reading_load_json_rejects_unknown_slug\n"); } int main(void) { test_json_parse_basic_shapes(); test_json_parse_rejects_malformed(); test_reading_load_json_extracts_aspects(); test_reading_load_json_extracts_body_sign_and_house(); test_reading_load_json_unknown_body_slug_is_skipped_not_fatal(); test_reading_load_json_empty_aspects_is_valid(); test_reading_load_json_rejects_missing_transits(); test_reading_load_json_rejects_unknown_slug(); printf("All JSON tests passed.\n"); return 0; }