extends GutTest func test_core_pack_owns_sorted_situation_intent_and_template_content() -> void: var catalog := ContentCatalog.create_core() assert_true(catalog.is_valid(), "%s" % [catalog.get_errors()]) assert_eq(_situation_ids(catalog.get_situations()), [&"situation_pantry_shortage"]) assert_eq(_intent_ids(catalog.get_dialogue_intents()), _sorted_ids(ConversationIntentIds.ALL)) assert_eq( _template_catalog_ids(catalog.get_dialogue_template_catalogs()), [&"jajce_conversation_templates"] ) var authored := catalog.get_situation(SituationSystem.DEFINITION_PANTRY_SHORTAGE) var compatibility_copy := SituationSystem.create_pantry_shortage_definition() assert_not_null(authored) assert_not_same(authored, compatibility_copy) assert_eq(authored.display_name, compatibility_copy.display_name) assert_eq(authored.priority, compatibility_copy.priority) assert_eq(authored.dialogue_intent_ids, compatibility_copy.dialogue_intent_ids) compatibility_copy.priority = -100 assert_eq(authored.priority, 80, "Compatibility callers cannot mutate authored content") var legacy_catalog := ( load("res://simulation/dialogue/resources/jajce_conversation_intents.tres") as ConversationIntentCatalog ) assert_not_null(legacy_catalog) assert_true(legacy_catalog.rebuild().is_empty(), "%s" % [legacy_catalog.get_errors()]) assert_eq(legacy_catalog.get_intent_ids(), _intent_ids(catalog.get_dialogue_intents())) var templates := catalog.get_dialogue_template_catalog(&"jajce_conversation_templates") assert_not_null(templates) assert_true(templates.validate(legacy_catalog.get_intent_ids()).is_empty()) func test_invalid_cross_references_fail_without_partial_publication() -> void: var core := load(ContentCatalog.CORE_PACK_PATH) as SimulationContentPack var addon := SimulationContentPack.new() addon.pack_id = &"broken_authored_content" addon.display_name = "Broken authored content" addon.required_pack_ids = [&"core"] var broken_situation := _broken_situation() assert_eq(broken_situation.alternatives.size(), 2) assert_eq( ( broken_situation . validate() . filter(func(error: String) -> bool: return "alternative" in error.to_lower()) . size() ), 3 ) addon.situations = [core.situations[0], broken_situation] var broken_intent := ConversationIntentDefinition.new() broken_intent.intent_id = &"broken_intent" broken_intent.response_intent_ids = [&"missing_response"] broken_intent.template_catalog_id = &"missing_template_catalog" addon.dialogue_intents = [core.dialogue_intents[0], broken_intent] var rogue_templates := ConversationTemplateCatalog.new() rogue_templates.catalog_id = &"rogue_templates" rogue_templates.line_templates = {"ghost_intent": ["Ghost line"]} rogue_templates.option_templates = {"ghost_intent": ["Ghost option"]} addon.dialogue_template_catalogs = [core.dialogue_template_catalogs[0], rogue_templates] var catalog := ContentCatalog.new() var packs: Array[SimulationContentPack] = [addon, core] var errors := catalog.rebuild(packs) for expected in [ "Duplicate situation_definition_id 'situation_pantry_shortage'", "duplicate predicate 'shared_predicate' across trigger/resolution predicates", "unsupported event predicate 'unknown_predicate_handler'", "duplicate alternative 'duplicate_alternative'", "references unknown resolution predicate 'missing_predicate'", "references unknown dialogue intent 'missing_intent'", "Duplicate dialogue intent_id 'accept'", "references unknown response intent 'missing_response'", "references unknown template catalog 'missing_template_catalog'", "Duplicate dialogue template catalog_id 'jajce_conversation_templates'", "Unknown line template intent 'ghost_intent'", "Unknown option template intent 'ghost_intent'", ]: assert_true(_has_error(errors, expected), "%s missing from %s" % [expected, errors]) assert_false(catalog.is_valid()) assert_true(catalog.get_situations().is_empty()) assert_true(catalog.get_dialogue_intents().is_empty()) assert_true(catalog.get_dialogue_template_catalogs().is_empty()) assert_null(catalog.get_situation(&"broken_situation")) assert_null(catalog.get_dialogue_intent(&"broken_intent")) func _broken_situation() -> SituationDefinition: var trigger := SituationPredicate.new() trigger.predicate_id = &"shared_predicate" trigger.evaluator_id = &"unknown_predicate_handler" trigger.source = String(SituationPredicate.SOURCE_EVENT) var resolution := SituationPredicate.new() resolution.predicate_id = &"shared_predicate" resolution.evaluator_id = &"event_type" resolution.source = String(SituationPredicate.SOURCE_EVENT) var first_alternative := SituationAlternativeDefinition.new() first_alternative.alternative_id = &"duplicate_alternative" first_alternative.display_name = "First alternative" first_alternative.resolution_predicate_ids = [&"missing_predicate"] var second_alternative := first_alternative.duplicate_alternative() var definition := SituationDefinition.new() definition.situation_definition_id = &"broken_situation" definition.display_name = "Broken situation" definition.trigger_predicates = [trigger] definition.resolution_predicates = [resolution] definition.dialogue_intent_ids = [&"missing_intent"] definition.alternatives = [first_alternative, second_alternative] return definition func _situation_ids(definitions: Array[SituationDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for definition in definitions: ids.append(definition.situation_definition_id) return ids func _intent_ids(definitions: Array[ConversationIntentDefinition]) -> Array[StringName]: var ids: Array[StringName] = [] for definition in definitions: ids.append(definition.intent_id) return ids func _template_catalog_ids( definitions: Array[ConversationTemplateCatalog], ) -> Array[StringName]: var ids: Array[StringName] = [] for definition in definitions: ids.append(definition.catalog_id) return ids func _sorted_ids(source: Array[StringName]) -> Array[StringName]: var ids := source.duplicate() ids.sort_custom( func(left: StringName, right: StringName) -> bool: return String(left) < String(right) ) return ids func _has_error(errors: Array[String], fragment: String) -> bool: for error in errors: if fragment in error: return true return false