extends GutTest const ACTION_ID := &"test_activity_metric_action" func test_valid_activity_metric_action_uses_the_builtin_contract() -> void: var action := _valid_action() action.completion_cost_resource_id = SimulationIds.RESOURCE_WOOD action.completion_cost_amount = 1.0 var catalog := _catalog_with_action(action) assert_true(catalog.is_valid(), "%s" % [catalog.get_errors()]) assert_not_null(catalog.get_action(ACTION_ID)) func test_activity_metric_handler_rejects_non_activity_targets() -> void: var action := _valid_action() action.target_type = SimulationIds.TARGET_FREE _assert_contract_error(action, "activity") func test_activity_metric_handler_requires_exactly_one_effect() -> void: var without_effects := _valid_action() without_effects.effects = [] _assert_contract_error(without_effects, "exactly one") var with_multiple_effects := _valid_action() var second_effect := _valid_effect() second_effect.effect_id = &"second_metric_effect" with_multiple_effects.effects = [_valid_effect(), second_effect] _assert_contract_error(with_multiple_effects, "exactly one") func test_activity_metric_handler_rejects_wrong_effect_kind() -> void: var action := _valid_action() var damage := ActionEffect.new() damage.effect_id = &"damage_instead_of_metric" damage.effect_kind = ActionEffect.KIND_DAMAGE damage.amount = 1.0 action.effects = [damage] _assert_contract_error(action, "metric_delta") func test_activity_metric_handler_rejects_unsupported_metric() -> void: var action := _valid_action() var effect := _valid_effect() effect.subject_key = &"morale" action.effects = [effect] _assert_contract_error(action, "safety") func test_activity_metric_handler_rejects_malformed_player_multiplier() -> void: var action := _valid_action() var effect := _valid_effect() effect.parameters = {"player_multiplier": "three", "npc_productivity": true} action.effects = [effect] _assert_contract_error(action, "player_multiplier") func test_activity_metric_handler_rejects_malformed_npc_productivity() -> void: var action := _valid_action() var effect := _valid_effect() effect.parameters = {"player_multiplier": 2.0, "npc_productivity": "yes"} action.effects = [effect] _assert_contract_error(action, "npc_productivity") func test_activity_metric_handler_requires_a_completion_event_type() -> void: var action := _valid_action() action.event_type_id = &"" _assert_contract_error(action, "event_type_id") func test_activity_metric_completion_cost_must_have_a_storage_route() -> void: var action := _valid_action() action.completion_cost_resource_id = &"unrouted_activity_cost" action.completion_cost_amount = 1.0 var unrouted_item := ItemDefinition.new() unrouted_item.item_id = action.completion_cost_resource_id unrouted_item.display_name = "Unrouted Activity Cost" unrouted_item.category = ItemDefinition.CATEGORY_RESOURCE var catalog := _catalog_with_action(action, [unrouted_item]) assert_false(catalog.is_valid(), "An executable cost needs an authoritative storage route") assert_true( _has_error(catalog.get_errors(), "storage"), "Expected a storage routing error, got %s" % [catalog.get_errors()] ) func _assert_contract_error(action: ActionDefinition, expected_fragment: String) -> void: var catalog := _catalog_with_action(action) assert_false(catalog.is_valid(), "Invalid '%s' contract was published" % action.action_id) assert_true( _has_error(catalog.get_errors(), expected_fragment), "Expected '%s' in %s" % [expected_fragment, catalog.get_errors()] ) func _catalog_with_action( action: ActionDefinition, items: Array[ItemDefinition] = [] ) -> ContentCatalog: var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack var addon := SimulationContentPack.new() addon.pack_id = &"activity_contract_test" addon.display_name = "Activity Contract Test" addon.required_pack_ids = [&"core"] addon.required_handler_ids = [SimulationIds.ACTION_HANDLER_ACTIVITY_METRIC_DELTA] addon.actions = [action] addon.items = items var packs: Array[SimulationContentPack] = [addon, core] var catalog := ContentCatalog.new() catalog.rebuild(packs) return catalog func _valid_action() -> ActionDefinition: var action := ActionDefinition.new() action.action_id = ACTION_ID action.display_name = "Test Activity Metric Action" action.target_type = SimulationIds.TARGET_ACTIVITY action.completion_handler_id = SimulationIds.ACTION_HANDLER_ACTIVITY_METRIC_DELTA action.effects = [_valid_effect()] action.event_type_id = SimulationIds.EVENT_ACTIVITY_COMPLETED return action func _valid_effect() -> ActionEffect: var effect := ActionEffect.new() effect.effect_id = &"test_metric_effect" effect.effect_kind = ActionEffect.KIND_METRIC_DELTA effect.subject_key = &"safety" effect.value = 1.0 effect.parameters = {"player_multiplier": 2.0, "npc_productivity": true} return effect func _has_error(errors: Array[String], fragment: String) -> bool: for error in errors: if fragment in error: return true return false