219 lines
8.0 KiB
C
219 lines
8.0 KiB
C
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#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. "natal"/"transits" bodies are real (if partial) -
|
|
* reading_load_json parses sign/house from them for display, alongside
|
|
* the aspects used for scoring. "spread" only fills in the Attitude and
|
|
* Outcome positions (the two guidance.c reads); the other eight are
|
|
* absent entirely, to prove partial spread data doesn't fail the load. */
|
|
static const char *k_fixture =
|
|
"{"
|
|
" \"date\": \"2026-07-16\","
|
|
" \"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\": \"attitude\", \"card\": \"devil\", \"reversed\": true},"
|
|
" {\"position\": \"outcome\", \"card\": \"world\", \"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_date(void) {
|
|
DailyReading reading;
|
|
bool ok = reading_load_json(k_fixture, strlen(k_fixture), &reading);
|
|
|
|
assert(ok);
|
|
struct tm *utc = gmtime(&reading.utc_moment);
|
|
assert(utc->tm_year + 1900 == 2026);
|
|
assert(utc->tm_mon + 1 == 7);
|
|
assert(utc->tm_mday == 16);
|
|
|
|
printf("PASS test_reading_load_json_extracts_date\n");
|
|
}
|
|
|
|
static void test_reading_load_json_missing_date_defaults_to_zero(void) {
|
|
const char *text = "{\"transits\": {\"aspects\": []}}";
|
|
DailyReading reading;
|
|
bool ok = reading_load_json(text, strlen(text), &reading);
|
|
|
|
assert(ok);
|
|
assert(reading.utc_moment == 0);
|
|
|
|
printf("PASS test_reading_load_json_missing_date_defaults_to_zero\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_extracts_spread_positions(void) {
|
|
DailyReading reading;
|
|
bool ok = reading_load_json(k_fixture, strlen(k_fixture), &reading);
|
|
|
|
assert(ok);
|
|
assert(reading.spread.positions[POSITION_ATTITUDE].card == CARD_DEVIL);
|
|
assert(reading.spread.positions[POSITION_ATTITUDE].reversed == true);
|
|
assert(reading.spread.positions[POSITION_OUTCOME].card == CARD_WORLD);
|
|
assert(reading.spread.positions[POSITION_OUTCOME].reversed == false);
|
|
|
|
/* Positions absent from the fixture (e.g. Present) get the zeroed
|
|
* default (CARD_FOOL, upright) rather than garbage. */
|
|
assert(reading.spread.positions[POSITION_PRESENT].card == CARD_FOOL);
|
|
assert(reading.spread.positions[POSITION_PRESENT].reversed == false);
|
|
|
|
printf("PASS test_reading_load_json_extracts_spread_positions\n");
|
|
}
|
|
|
|
static void test_reading_load_json_missing_spread_is_not_fatal(void) {
|
|
const char *text = "{\"transits\": {\"aspects\": []}}";
|
|
DailyReading reading;
|
|
bool ok = reading_load_json(text, strlen(text), &reading);
|
|
|
|
assert(ok);
|
|
assert(reading.spread.positions[POSITION_OUTCOME].card == CARD_FOOL);
|
|
|
|
printf("PASS test_reading_load_json_missing_spread_is_not_fatal\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_date();
|
|
test_reading_load_json_missing_date_defaults_to_zero();
|
|
test_reading_load_json_extracts_body_sign_and_house();
|
|
test_reading_load_json_extracts_spread_positions();
|
|
test_reading_load_json_missing_spread_is_not_fatal();
|
|
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;
|
|
}
|