summaryrefslogtreecommitdiff
path: root/tests/test_logic.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_logic.c')
-rw-r--r--tests/test_logic.c160
1 files changed, 160 insertions, 0 deletions
diff --git a/tests/test_logic.c b/tests/test_logic.c
new file mode 100644
index 0000000..74978a3
--- /dev/null
+++ b/tests/test_logic.c
@@ -0,0 +1,160 @@
+#include "vendor/unity/src/unity.h"
+
+#include "../src/c/fastforge_logic.h"
+
+#include "test_helpers.h"
+
+#include <string.h>
+
+void test_entry_duration_seconds_handles_invalid_ranges(void) {
+ FastEntry entry = {0};
+
+ TEST_ASSERT_EQUAL_INT64(0, entry_duration_seconds(NULL));
+
+ entry.start_time = 0;
+ entry.end_time = 100;
+ TEST_ASSERT_EQUAL_INT64(0, entry_duration_seconds(&entry));
+
+ entry.start_time = 200;
+ entry.end_time = 200;
+ TEST_ASSERT_EQUAL_INT64(0, entry_duration_seconds(&entry));
+
+ entry.start_time = 200;
+ entry.end_time = 150;
+ TEST_ASSERT_EQUAL_INT64(0, entry_duration_seconds(&entry));
+
+ entry.start_time = 200;
+ entry.end_time = 280;
+ TEST_ASSERT_EQUAL_INT64(80, entry_duration_seconds(&entry));
+}
+
+void test_stage_thresholds_and_labels(void) {
+ TEST_ASSERT_EQUAL_UINT8(0, stage_level_for_elapsed(0));
+ TEST_ASSERT_EQUAL_UINT8(0, stage_level_for_elapsed((12 * 3600) - 1));
+ TEST_ASSERT_EQUAL_UINT8(1, stage_level_for_elapsed(12 * 3600));
+ TEST_ASSERT_EQUAL_UINT8(2, stage_level_for_elapsed(18 * 3600));
+ TEST_ASSERT_EQUAL_UINT8(3, stage_level_for_elapsed(24 * 3600));
+
+ TEST_ASSERT_EQUAL_STRING("GLYCOGEN", stage_text_for_elapsed(11 * 3600));
+ TEST_ASSERT_EQUAL_STRING("FAT BURN", stage_text_for_elapsed(12 * 3600));
+ TEST_ASSERT_EQUAL_STRING("EARLY KETOSIS", stage_text_for_elapsed(18 * 3600));
+ TEST_ASSERT_EQUAL_STRING("DEEP KETOSIS", stage_text_for_elapsed(24 * 3600));
+}
+
+void test_formatters_clamp_negative_and_format_values(void) {
+ char buffer[32];
+
+ format_hhmmss(-1, buffer, sizeof(buffer));
+ TEST_ASSERT_EQUAL_STRING("00:00:00", buffer);
+
+ format_hhmmss(3661, buffer, sizeof(buffer));
+ TEST_ASSERT_EQUAL_STRING("01:01:01", buffer);
+
+ format_duration_hours_minutes(-120, buffer, sizeof(buffer));
+ TEST_ASSERT_EQUAL_STRING("0h 00m", buffer);
+
+ format_duration_hours_minutes(7260, buffer, sizeof(buffer));
+ TEST_ASSERT_EQUAL_STRING("2h 01m", buffer);
+}
+
+void test_running_fast_at_target_logic(void) {
+ FastEntry entry = {0};
+
+ TEST_ASSERT_FALSE(running_fast_is_at_target(NULL, 0));
+
+ entry.start_time = 0;
+ entry.target_minutes = 16 * 60;
+ TEST_ASSERT_FALSE(running_fast_is_at_target(&entry, 100));
+
+ entry.start_time = 100;
+ entry.end_time = 200;
+ TEST_ASSERT_FALSE(running_fast_is_at_target(&entry, 200));
+
+ entry.end_time = 0;
+ entry.target_minutes = 0;
+ TEST_ASSERT_FALSE(running_fast_is_at_target(&entry, 1000));
+
+ entry.target_minutes = 10;
+ TEST_ASSERT_FALSE(running_fast_is_at_target(&entry, 699));
+ TEST_ASSERT_TRUE(running_fast_is_at_target(&entry, 700));
+}
+
+void test_local_day_start_returns_midnight_utc(void) {
+ const time_t ts = make_utc_time(2026, 4, 5, 17, 20, 10);
+ const time_t expected_day_start = make_utc_time(2026, 4, 5, 0, 0, 0);
+
+ TEST_ASSERT_EQUAL_INT64(0, local_day_start(0));
+ TEST_ASSERT_EQUAL_INT64(0, local_day_start(-1));
+ TEST_ASSERT_EQUAL_INT64(expected_day_start, local_day_start(ts));
+}
+
+void test_streak_empty_input_resets_all_fields(void) {
+ StreakData streak;
+ const time_t now = make_utc_time(2026, 4, 10, 9, 0, 0);
+
+ streak.current_streak = 77;
+ streak.longest_streak = 88;
+ streak.last_completed_fast_end = 99;
+
+ fastforge_streak_recompute(NULL, 0, now, &streak);
+
+ TEST_ASSERT_EQUAL_UINT16(0, streak.current_streak);
+ TEST_ASSERT_EQUAL_UINT16(0, streak.longest_streak);
+ TEST_ASSERT_EQUAL_INT64(0, streak.last_completed_fast_end);
+}
+
+void test_streak_single_recent_completion_sets_current_and_longest(void) {
+ FastEntry entries[1];
+ StreakData streak;
+ const time_t now = make_utc_time(2026, 4, 10, 12, 0, 0);
+
+ memset(entries, 0, sizeof(entries));
+ entries[0].start_time = make_utc_time(2026, 4, 9, 8, 0, 0);
+ entries[0].end_time = make_utc_time(2026, 4, 9, 20, 0, 0);
+
+ fastforge_streak_recompute(entries, 1, now, &streak);
+
+ TEST_ASSERT_EQUAL_UINT16(1, streak.current_streak);
+ TEST_ASSERT_EQUAL_UINT16(1, streak.longest_streak);
+ TEST_ASSERT_EQUAL_INT64(entries[0].end_time, streak.last_completed_fast_end);
+}
+
+void test_streak_longest_can_exceed_current_when_sequence_breaks(void) {
+ FastEntry entries[4];
+ StreakData streak;
+ const time_t now = make_utc_time(2026, 4, 4, 23, 0, 0);
+
+ memset(entries, 0, sizeof(entries));
+ entries[0].start_time = make_utc_time(2026, 4, 1, 8, 0, 0);
+ entries[0].end_time = make_utc_time(2026, 4, 1, 20, 0, 0);
+ entries[1].start_time = make_utc_time(2026, 4, 2, 8, 0, 0);
+ entries[1].end_time = make_utc_time(2026, 4, 2, 20, 0, 0);
+ entries[2].start_time = make_utc_time(2026, 4, 4, 8, 0, 0);
+ entries[2].end_time = make_utc_time(2026, 4, 4, 20, 0, 0);
+ entries[3].start_time = make_utc_time(2026, 4, 4, 6, 0, 0);
+ entries[3].end_time = make_utc_time(2026, 4, 4, 7, 0, 0);
+
+ fastforge_streak_recompute(entries, 4, now, &streak);
+
+ TEST_ASSERT_EQUAL_UINT16(1, streak.current_streak);
+ TEST_ASSERT_EQUAL_UINT16(2, streak.longest_streak);
+ TEST_ASSERT_EQUAL_INT64(entries[2].end_time, streak.last_completed_fast_end);
+}
+
+void test_streak_drops_to_zero_after_missing_more_than_one_day(void) {
+ FastEntry entries[2];
+ StreakData streak;
+ const time_t now = make_utc_time(2026, 4, 10, 12, 0, 0);
+
+ memset(entries, 0, sizeof(entries));
+ entries[0].start_time = make_utc_time(2026, 4, 6, 8, 0, 0);
+ entries[0].end_time = make_utc_time(2026, 4, 6, 20, 0, 0);
+ entries[1].start_time = make_utc_time(2026, 4, 7, 8, 0, 0);
+ entries[1].end_time = make_utc_time(2026, 4, 7, 20, 0, 0);
+
+ fastforge_streak_recompute(entries, 2, now, &streak);
+
+ TEST_ASSERT_EQUAL_UINT16(0, streak.current_streak);
+ TEST_ASSERT_EQUAL_UINT16(2, streak.longest_streak);
+ TEST_ASSERT_EQUAL_INT64(entries[1].end_time, streak.last_completed_fast_end);
+}