shk_parser 0.1.1

A parser for Stronghold Kingdoms attack formation files (.cas)
Documentation
#[cfg(test)]
mod test_cases {
    use crate::*;

    #[test]
    fn test_parse_real_files() {
        let test_files = [
            ("formations/AttackSetup_test2.cas", 1),   // Catapult
            ("formations/AttackSetup_test3.cas", 1),   // Captain with Delay
            ("formations/AttackSetup_test4.cas", 1),   // Captain with Rallying Cry
            ("formations/AttackSetup_test5.cas", 1),   // Captain with Arrow Volley
            ("formations/AttackSetup_test6.cas", 1),   // Archer
            ("formations/AttackSetup_testttt.cas", 4), // 4 Pikemen
        ];

        for (filename, expected_count) in test_files {
            let result = parse_formation_file(filename);
            assert!(
                result.is_ok(),
                "Failed to parse {}: {:?}",
                filename,
                result.err()
            );

            let units = result.unwrap();
            assert_eq!(
                units.len(),
                expected_count,
                "Expected {} units in {}, got {}",
                expected_count,
                filename,
                units.len()
            );
        }
    }

    #[test]
    fn test_catapult_parsing() {
        let units = parse_formation_file("formations/AttackSetup_test2.cas").unwrap();
        assert_eq!(units.len(), 1);

        let unit = &units[0];
        assert_eq!(unit.position.x, 59);
        assert_eq!(unit.position.y, 1);

        match &unit.unit_type {
            UnitType::Catapult { target } => {
                assert_eq!(target.x, 58);
                assert_eq!(target.y, 33);
            }
            _ => panic!("Expected catapult, got {:?}", unit.unit_type),
        }
    }

    #[test]
    fn test_captain_with_arrow_volley() {
        let units = parse_formation_file("formations/AttackSetup_test5.cas").unwrap();
        assert_eq!(units.len(), 1);

        let unit = &units[0];
        assert_eq!(unit.position.x, 59);
        assert_eq!(unit.position.y, 0);

        match &unit.unit_type {
            UnitType::Captain { ability, wait_time } => {
                assert_eq!(*wait_time, 200);
                match ability {
                    CaptainAbility::ArrowVolley { target } => {
                        assert_eq!(target.x, 59);
                        assert_eq!(target.y, 35);
                    }
                    _ => panic!("Expected ArrowVolley, got {ability:?}"),
                }
            }
            _ => panic!("Expected captain, got {:?}", unit.unit_type),
        }
    }

    #[test]
    fn test_archer_parsing() {
        let units = parse_formation_file("formations/AttackSetup_test6.cas").unwrap();
        assert_eq!(units.len(), 1);

        let unit = &units[0];
        assert_eq!(unit.position.x, 58);
        assert_eq!(unit.position.y, 0);

        match unit.unit_type {
            UnitType::Archer => {}
            _ => panic!("Expected archer, got {:?}", unit.unit_type),
        }
    }

    #[test]
    fn test_multiple_pikemen() {
        let units = parse_formation_file("formations/AttackSetup_testttt.cas").unwrap();
        assert_eq!(units.len(), 4);

        // All should be pikemen
        for unit in &units {
            match unit.unit_type {
                UnitType::Pikeman => {}
                _ => panic!("Expected pikeman, got {:?}", unit.unit_type),
            }
        }

        // Check specific positions
        assert_eq!(units[0].position, Position { x: 58, y: 0 });
        assert_eq!(units[1].position, Position { x: 117, y: 59 });
        assert_eq!(units[2].position, Position { x: 58, y: 117 });
        assert_eq!(units[3].position, Position { x: 0, y: 59 });
    }

    #[test]
    fn test_display_formatting() {
        let archer = UnitRecord::new(10, 20, UnitType::Archer);
        assert_eq!(archer.to_string(), "Archer at (10, 20)");

        let catapult = UnitRecord::new(
            5,
            15,
            UnitType::Catapult {
                target: Position { x: 25, y: 35 },
            },
        );
        assert_eq!(
            catapult.to_string(),
            "Catapult targeting (25, 35) at (5, 15)"
        );

        let captain = UnitRecord::new(
            0,
            0,
            UnitType::Captain {
                ability: CaptainAbility::Delay,
                wait_time: 100,
            },
        );
        assert_eq!(
            captain.to_string(),
            "Captain using Delay for 100 seconds at (0, 0)"
        );
    }

    #[test]
    fn test_invalid_file() {
        let result = parse_formation_file("nonexistent.cas");
        assert!(result.is_err());

        // File not found will result in an IO error which gets wrapped differently
        // Just check that it fails
        assert!(result.is_err());
    }

    #[test]
    fn test_empty_data() {
        let result = parse_formation(&[]);
        assert!(result.is_err());

        match result.unwrap_err() {
            ParseError::FileTooSmall { .. } => {}
            other => panic!("Expected FileTooSmall error, got {other:?}"),
        }
    }

    #[test]
    fn test_truncated_header() {
        let result = parse_formation(&[1, 2]); // Only 2 bytes
        assert!(result.is_err());

        match result.unwrap_err() {
            ParseError::FileTooSmall { .. } => {}
            other => panic!("Expected FileTooSmall error, got {other:?}"),
        }
    }
}