@@ -894,3 +894,384 @@ test "tamagotchi — TamagotchiMetrics bestServiceStr" {
894894 try std .testing .expectEqualStrings ("w7-66" , result );
895895 try std .testing .expectApproxEqAbs (@as (f32 , 4.6 ), metrics .happiness_best_ppl , 0.01 );
896896}
897+
898+ // ═══════════════════════════════════════════════════════════════════════════════
899+ // REAL FUNCTION TESTS — Actual computation logic tests
900+ // ═══════════════════════════════════════════════════════════════════════════════
901+
902+ test "tamagotchi — ModuleHealth overallScore calculates correctly" {
903+ // Test the actual score calculation formula
904+ var health = ModuleHealth {};
905+
906+ // All unknown = 100 (baseline)
907+ try std .testing .expectEqual (@as (u8 , 100 ), health .overallScore ());
908+
909+ // One weak module = -10 points
910+ health .medulla = .weak ;
911+ try std .testing .expectEqual (@as (u8 , 90 ), health .overallScore ());
912+
913+ // Two weak modules = -20 points
914+ health .pons = .weak ;
915+ try std .testing .expectEqual (@as (u8 , 80 ), health .overallScore ());
916+
917+ // Three weak modules = -30 points
918+ health .lc = .weak ;
919+ try std .testing .expectEqual (@as (u8 , 70 ), health .overallScore ());
920+
921+ // Four weak modules = -40 points
922+ health .hippocampus = .weak ;
923+ try std .testing .expectEqual (@as (u8 , 60 ), health .overallScore ());
924+
925+ // One broken overrides weak = -25 points (from medulla)
926+ health .medulla = .broken ;
927+ try std .testing .expectEqual (@as (u8 , 75 ), health .overallScore ());
928+
929+ // Two broken = -50 points
930+ health .pons = .broken ;
931+ try std .testing .expectEqual (@as (u8 , 50 ), health .overallScore ());
932+
933+ // Three broken = -75 points
934+ health .lc = .broken ;
935+ try std .testing .expectEqual (@as (u8 , 25 ), health .overallScore ());
936+
937+ // Four broken = -100 points (minimum)
938+ health .hippocampus = .broken ;
939+ try std .testing .expectEqual (@as (u8 , 0 ), health .overallScore ());
940+ }
941+
942+ test "tamagotchi — ModuleHealth overallLabel threshold boundaries" {
943+ var health = ModuleHealth {};
944+
945+ // Score 100 = "All modules OK"
946+ try std .testing .expectEqual (@as (u8 , 100 ), health .overallScore ());
947+ try std .testing .expectEqualStrings ("All modules OK" , health .overallLabel ());
948+
949+ // Score 80 = "Minor issues" (exact boundary)
950+ health .medulla = .weak ;
951+ health .pons = .weak ;
952+ try std .testing .expectEqual (@as (u8 , 80 ), health .overallScore ());
953+ try std .testing .expectEqualStrings ("Minor issues" , health .overallLabel ());
954+
955+ // Score 79 = still "Minor issues" (above 50 threshold)
956+ health .lc = .weak ;
957+ try std .testing .expectEqual (@as (u8 , 70 ), health .overallScore ());
958+ try std .testing .expectEqualStrings ("Minor issues" , health .overallLabel ());
959+
960+ // Score 50 = "Some problems" (exact boundary)
961+ health .medulla = .broken ;
962+ health .pons = .broken ;
963+ health .lc = .healthy ;
964+ health .hippocampus = .healthy ;
965+ try std .testing .expectEqual (@as (u8 , 50 ), health .overallScore ());
966+ try std .testing .expectEqualStrings ("Some problems" , health .overallLabel ());
967+
968+ // Score 49 = "Critical failures" (below 50 threshold)
969+ health .hippocampus = .weak ;
970+ try std .testing .expectEqual (@as (u8 , 40 ), health .overallScore ());
971+ try std .testing .expectEqualStrings ("Critical failures" , health .overallLabel ());
972+ }
973+
974+ test "tamagotchi — GrowthStage fromUptime edge cases" {
975+ // Test exact boundary transitions and negative handling
976+
977+ // Zero uptime = egg
978+ try std .testing .expectEqual (GrowthStage .egg , GrowthStage .fromUptime (0 ));
979+
980+ // Just before 10 min = egg
981+ try std .testing .expectEqual (GrowthStage .egg , GrowthStage .fromUptime (599 ));
982+
983+ // Exactly 10 min = baby (boundary)
984+ try std .testing .expectEqual (GrowthStage .baby , GrowthStage .fromUptime (600 ));
985+
986+ // Just before 60 min = baby
987+ try std .testing .expectEqual (GrowthStage .baby , GrowthStage .fromUptime (3599 ));
988+
989+ // Exactly 60 min = child (boundary)
990+ try std .testing .expectEqual (GrowthStage .child , GrowthStage .fromUptime (3600 ));
991+
992+ // Exactly 4 hours = teen (boundary: 240 * 60 = 14400)
993+ try std .testing .expectEqual (GrowthStage .teen , GrowthStage .fromUptime (14400 ));
994+
995+ // Exactly 12 hours = adult (boundary: 720 * 60 = 43200)
996+ try std .testing .expectEqual (GrowthStage .adult , GrowthStage .fromUptime (43200 ));
997+
998+ // Very large uptime (1 week) = adult
999+ const one_week : i64 = 7 * 24 * 60 * 60 ;
1000+ try std .testing .expectEqual (GrowthStage .adult , GrowthStage .fromUptime (one_week ));
1001+ }
1002+
1003+ test "tamagotchi — TamagotchiMetrics discipline breakdown" {
1004+ var metrics = TamagotchiMetrics {};
1005+
1006+ // Initial state: no fixes
1007+ try std .testing .expectEqual (@as (u32 , 0 ), metrics .discipline_fixes );
1008+ try std .testing .expectEqual (@as (u32 , 0 ), metrics .discipline_doctor );
1009+ try std .testing .expectEqual (@as (u32 , 0 ), metrics .discipline_farm );
1010+ try std .testing .expectEqual (@as (u32 , 0 ), metrics .discipline_other );
1011+
1012+ // Add doctor fixes
1013+ metrics .discipline_doctor = 5 ;
1014+ metrics .discipline_fixes = 5 ;
1015+ try std .testing .expectEqual (@as (u32 , 5 ), metrics .discipline_fixes );
1016+ try std .testing .expectEqual (@as (u32 , 5 ), metrics .discipline_doctor );
1017+
1018+ // Add farm fixes (should accumulate)
1019+ metrics .discipline_farm = 3 ;
1020+ metrics .discipline_fixes += 3 ;
1021+ try std .testing .expectEqual (@as (u32 , 8 ), metrics .discipline_fixes );
1022+ try std .testing .expectEqual (@as (u32 , 3 ), metrics .discipline_farm );
1023+
1024+ // Add other fixes
1025+ metrics .discipline_other = 2 ;
1026+ metrics .discipline_fixes += 2 ;
1027+ try std .testing .expectEqual (@as (u32 , 10 ), metrics .discipline_fixes );
1028+ try std .testing .expectEqual (@as (u32 , 2 ), metrics .discipline_other );
1029+
1030+ // Verify breakdown matches total
1031+ const breakdown_total = metrics .discipline_doctor + metrics .discipline_farm + metrics .discipline_other ;
1032+ try std .testing .expectEqual (metrics .discipline_fixes , breakdown_total );
1033+ }
1034+
1035+ test "tamagotchi — formatTamagotchiReport PPL trend messages" {
1036+ const farm_status_excellent : thalamus.FarmStatus = .{
1037+ .total_services = 100 ,
1038+ .active = 80 ,
1039+ .best_ppl = 3.5 , // Excellent: < 5.0
1040+ };
1041+
1042+ const farm_status_good : thalamus.FarmStatus = .{
1043+ .total_services = 100 ,
1044+ .active = 80 ,
1045+ .best_ppl = 7.5 , // Good: 5.0 - 10.0
1046+ };
1047+
1048+ const farm_status_needs : thalamus.FarmStatus = .{
1049+ .total_services = 100 ,
1050+ .active = 80 ,
1051+ .best_ppl = 15.0 , // Needs attention: > 10.0
1052+ };
1053+
1054+ const module_health = ModuleHealth {};
1055+
1056+ // Test excellent PPL message
1057+ const report_excellent = try formatTamagotchiReport (
1058+ std .testing .allocator ,
1059+ 7200 ,
1060+ farm_status_excellent ,
1061+ 0 ,
1062+ 0.5 ,
1063+ module_health ,
1064+ .normal ,
1065+ );
1066+ defer std .testing .allocator .free (report_excellent );
1067+ try std .testing .expect (std .mem .indexOf (u8 , report_excellent , "Excellent" ) != null );
1068+
1069+ // Test good PPL message
1070+ const report_good = try formatTamagotchiReport (
1071+ std .testing .allocator ,
1072+ 7200 ,
1073+ farm_status_good ,
1074+ 0 ,
1075+ 0.5 ,
1076+ module_health ,
1077+ .normal ,
1078+ );
1079+ defer std .testing .allocator .free (report_good );
1080+ try std .testing .expect (std .mem .indexOf (u8 , report_good , "Good progress" ) != null );
1081+
1082+ // Test needs attention message
1083+ const report_needs = try formatTamagotchiReport (
1084+ std .testing .allocator ,
1085+ 7200 ,
1086+ farm_status_needs ,
1087+ 0 ,
1088+ 0.5 ,
1089+ module_health ,
1090+ .normal ,
1091+ );
1092+ defer std .testing .allocator .free (report_needs );
1093+ try std .testing .expect (std .mem .indexOf (u8 , report_needs , "Needs attention" ) != null );
1094+ }
1095+
1096+ test "tamagotchi — formatTamagotchiReport handles empty service name" {
1097+ const farm_status : thalamus.FarmStatus = .{
1098+ .total_services = 50 ,
1099+ .active = 40 ,
1100+ .best_ppl = 5.2 ,
1101+ .best_ppl_service_len = 0 , // Empty service name
1102+ };
1103+
1104+ const module_health = ModuleHealth {};
1105+
1106+ const report = try formatTamagotchiReport (
1107+ std .testing .allocator ,
1108+ 5400 ,
1109+ farm_status ,
1110+ 1 ,
1111+ 0.6 ,
1112+ module_health ,
1113+ .alert ,
1114+ );
1115+ defer std .testing .allocator .free (report );
1116+
1117+ // Should contain "none" as fallback service name
1118+ try std .testing .expect (std .mem .indexOf (u8 , report , "none" ) != null );
1119+ try std .testing .expect (report .len > 0 );
1120+ }
1121+
1122+ test "tamagotchi — formatTamagotchiReport heartbeat age formatting" {
1123+ const module_health_seconds = ModuleHealth {
1124+ .medulla = .healthy ,
1125+ .medulla_last_beat_age = 45 , // < 60 seconds
1126+ .pons = .healthy ,
1127+ .lc = .healthy ,
1128+ .hippocampus = .healthy ,
1129+ };
1130+
1131+ const module_health_minutes = ModuleHealth {
1132+ .medulla = .healthy ,
1133+ .medulla_last_beat_age = 150 , // > 60 seconds = 2.5 minutes
1134+ .pons = .healthy ,
1135+ .lc = .healthy ,
1136+ .hippocampus = .healthy ,
1137+ };
1138+
1139+ const farm_status = thalamus.FarmStatus {};
1140+
1141+ // Test seconds format
1142+ const report_seconds = try formatTamagotchiReport (
1143+ std .testing .allocator ,
1144+ 7200 ,
1145+ farm_status ,
1146+ 0 ,
1147+ 0.5 ,
1148+ module_health_seconds ,
1149+ .normal ,
1150+ );
1151+ defer std .testing .allocator .free (report_seconds );
1152+ try std .testing .expect (std .mem .indexOf (u8 , report_seconds , "45s ago" ) != null );
1153+
1154+ // Test minutes format
1155+ const report_minutes = try formatTamagotchiReport (
1156+ std .testing .allocator ,
1157+ 7200 ,
1158+ farm_status ,
1159+ 0 ,
1160+ 0.5 ,
1161+ module_health_minutes ,
1162+ .normal ,
1163+ );
1164+ defer std .testing .allocator .free (report_minutes );
1165+ try std .testing .expect (std .mem .indexOf (u8 , report_minutes , "2m ago" ) != null );
1166+ }
1167+
1168+ test "tamagotchi — formatQuickReport contains all required fields" {
1169+ var farm_status = thalamus.FarmStatus {
1170+ .total_services = 104 ,
1171+ .active = 85 ,
1172+ .crashed = 3 ,
1173+ .stale_count = 6 ,
1174+ .accounts_alive = 7 ,
1175+ .accounts_total = 8 ,
1176+ .best_ppl = 4.32 ,
1177+ };
1178+ @memcpy (farm_status .best_ppl_service [0.. "hslm-r33" .len ], "hslm-r33" );
1179+ farm_status .best_ppl_service_len = "hslm-r33" .len ;
1180+
1181+ const report = try formatQuickReport (
1182+ std .testing .allocator ,
1183+ 18000 , // 5 hours = teen stage
1184+ farm_status ,
1185+ 3 ,
1186+ .alert ,
1187+ );
1188+ defer std .testing .allocator .free (report );
1189+
1190+ // Verify all key fields are present
1191+ try std .testing .expect (std .mem .indexOf (u8 , report , "Teen" ) != null );
1192+ try std .testing .expect (std .mem .indexOf (u8 , report , "5h" ) != null );
1193+ try std .testing .expect (std .mem .indexOf (u8 , report , "Farm:" ) != null );
1194+ try std .testing .expect (std .mem .indexOf (u8 , report , "85" ) != null ); // active count
1195+ try std .testing .expect (std .mem .indexOf (u8 , report , "104" ) != null ); // total services
1196+ try std .testing .expect (std .mem .indexOf (u8 , report , "PPL:" ) != null );
1197+ try std .testing .expect (std .mem .indexOf (u8 , report , "4.3" ) != null ); // best_ppl rounded
1198+ try std .testing .expect (std .mem .indexOf (u8 , report , "Fixes:" ) != null );
1199+ try std .testing .expect (std .mem .indexOf (u8 , report , "3" ) != null );
1200+ try std .testing .expect (std .mem .indexOf (u8 , report , "ALERT" ) != null );
1201+ }
1202+
1203+ test "tamagotchi — ModuleHealth mixed weak and broken score calculation" {
1204+ // Test various combinations of weak and broken states
1205+ const HealthTestCase = struct {
1206+ medulla : ModuleHealth.CellStatus ,
1207+ pons : ModuleHealth.CellStatus ,
1208+ lc : ModuleHealth.CellStatus ,
1209+ hippocampus : ModuleHealth.CellStatus ,
1210+ expected_score : u8 ,
1211+ };
1212+ const test_cases = [_ ]HealthTestCase {
1213+ // All unknown/healthy = 100
1214+ HealthTestCase { .medulla = .unknown , .pons = .unknown , .lc = .unknown , .hippocampus = .unknown , .expected_score = 100 },
1215+ HealthTestCase { .medulla = .healthy , .pons = .healthy , .lc = .healthy , .hippocampus = .healthy , .expected_score = 100 },
1216+ // Mixed healthy + unknown = 100
1217+ HealthTestCase { .medulla = .healthy , .pons = .unknown , .lc = .healthy , .hippocampus = .unknown , .expected_score = 100 },
1218+ // One weak = 90
1219+ HealthTestCase { .medulla = .weak , .pons = .healthy , .lc = .healthy , .hippocampus = .healthy , .expected_score = 90 },
1220+ HealthTestCase { .medulla = .healthy , .pons = .weak , .lc = .healthy , .hippocampus = .healthy , .expected_score = 90 },
1221+ // One broken = 75
1222+ HealthTestCase { .medulla = .broken , .pons = .healthy , .lc = .healthy , .hippocampus = .healthy , .expected_score = 75 },
1223+ HealthTestCase { .medulla = .healthy , .pons = .broken , .lc = .healthy , .hippocampus = .healthy , .expected_score = 75 },
1224+ // One broken + one weak = 65
1225+ HealthTestCase { .medulla = .broken , .pons = .weak , .lc = .healthy , .hippocampus = .healthy , .expected_score = 65 },
1226+ HealthTestCase { .medulla = .weak , .pons = .broken , .lc = .healthy , .hippocampus = .healthy , .expected_score = 65 },
1227+ // Two broken = 50
1228+ HealthTestCase { .medulla = .broken , .pons = .broken , .lc = .healthy , .hippocampus = .healthy , .expected_score = 50 },
1229+ // Two broken + two weak = 30
1230+ HealthTestCase { .medulla = .broken , .pons = .broken , .lc = .weak , .hippocampus = .weak , .expected_score = 30 },
1231+ // Three broken = 25
1232+ HealthTestCase { .medulla = .broken , .pons = .broken , .lc = .broken , .hippocampus = .healthy , .expected_score = 25 },
1233+ // All broken = 0
1234+ HealthTestCase { .medulla = .broken , .pons = .broken , .lc = .broken , .hippocampus = .broken , .expected_score = 0 },
1235+ };
1236+
1237+ for (test_cases ) | tc | {
1238+ var health = ModuleHealth {
1239+ .medulla = tc .medulla ,
1240+ .pons = tc .pons ,
1241+ .lc = tc .lc ,
1242+ .hippocampus = tc .hippocampus ,
1243+ };
1244+ try std .testing .expectEqual (tc .expected_score , health .overallScore ());
1245+ }
1246+ }
1247+
1248+ test "tamagotchi — GrowthStage fromUptime minute calculation" {
1249+ // Verify minute division is correct
1250+ const UptimeTestCase = struct {
1251+ seconds : i64 ,
1252+ expected_minutes : i64 ,
1253+ expected_stage : GrowthStage ,
1254+ };
1255+ const test_cases = [_ ]UptimeTestCase {
1256+ UptimeTestCase { .seconds = 30 , .expected_minutes = 0 , .expected_stage = GrowthStage .egg }, // 30 seconds
1257+ UptimeTestCase { .seconds = 60 , .expected_minutes = 1 , .expected_stage = GrowthStage .egg }, // 1 minute
1258+ UptimeTestCase { .seconds = 300 , .expected_minutes = 5 , .expected_stage = GrowthStage .egg }, // 5 minutes
1259+ UptimeTestCase { .seconds = 600 , .expected_minutes = 10 , .expected_stage = GrowthStage .baby }, // 10 minutes
1260+ UptimeTestCase { .seconds = 1800 , .expected_minutes = 30 , .expected_stage = GrowthStage .baby }, // 30 minutes
1261+ UptimeTestCase { .seconds = 3600 , .expected_minutes = 60 , .expected_stage = GrowthStage .child }, // 60 minutes = 1 hour
1262+ UptimeTestCase { .seconds = 7200 , .expected_minutes = 120 , .expected_stage = GrowthStage .child }, // 120 minutes = 2 hours
1263+ UptimeTestCase { .seconds = 14400 , .expected_minutes = 240 , .expected_stage = GrowthStage .teen }, // 240 minutes = 4 hours
1264+ UptimeTestCase { .seconds = 28800 , .expected_minutes = 480 , .expected_stage = GrowthStage .teen }, // 480 minutes = 8 hours
1265+ UptimeTestCase { .seconds = 43200 , .expected_minutes = 720 , .expected_stage = GrowthStage .adult }, // 720 minutes = 12 hours
1266+ UptimeTestCase { .seconds = 86400 , .expected_minutes = 1440 , .expected_stage = GrowthStage .adult }, // 1440 minutes = 24 hours
1267+ };
1268+
1269+ for (test_cases ) | tc | {
1270+ const stage = GrowthStage .fromUptime (tc .seconds );
1271+ try std .testing .expectEqual (tc .expected_stage , stage );
1272+
1273+ // Verify internal minute calculation
1274+ const minutes = @divTrunc (tc .seconds , 60 );
1275+ try std .testing .expectEqual (tc .expected_minutes , minutes );
1276+ }
1277+ }
0 commit comments