diff --git a/dev/lib/product_taxonomy/models/integration_version.rb b/dev/lib/product_taxonomy/models/integration_version.rb index 0b84a07f3..d605dc8c1 100644 --- a/dev/lib/product_taxonomy/models/integration_version.rb +++ b/dev/lib/product_taxonomy/models/integration_version.rb @@ -30,10 +30,32 @@ def generate_all_distributions(output_path:, logger:, current_shopify_version: n # # @return [Array] def load_all_from_source(current_shopify_version: nil, base_path: INTEGRATIONS_PATH) - integrations = YAML.safe_load_file(File.expand_path("integrations.yml", base_path)) - integrations.pluck("available_versions").flatten.map do |integration_version| - integration_path = File.expand_path(integration_version, base_path) - load_from_source(integration_path:, current_shopify_version:) + integrations_yaml = YAML.safe_load_file(File.expand_path("integrations.yml", base_path)) + integrations_yaml.flat_map do |integration_yaml| + versions = integration_yaml["available_versions"].sort.map do |version_path| + load_from_source( + integration_path: File.expand_path(version_path, base_path), + current_shopify_version:, + ) + end + + resolve_to_shopify_mappings_chain(versions) if integration_yaml["name"] == "shopify" + + versions + end + end + + # Resolve a set of IntegrationVersion to_shopify mappings so that each one maps to the latest version of the + # Shopify taxonomy. + # + # @param versions [Array] The versions to resolve, ordered from oldest to newest. + def resolve_to_shopify_mappings_chain(versions) + # Resolve newest version against current taxonomy + versions.last.resolve_to_shopify_mappings(nil) + + # Resolve each older version against the one following it + versions.each_cons(2).reverse_each do |previous, next_version| + previous.resolve_to_shopify_mappings(next_version) end end @@ -155,6 +177,19 @@ def generate_distribution(output_path:, direction:) ) end + # Resolve the output categories of to_shopify mappings to the next version of the Shopify taxonomy. + # + # @param next_integration_version [IntegrationVersion | nil] The IntegrationVersion defining mappings to the next + # newer version of the Shopify taxonomy. If nil, the latest version of the Shopify taxonomy is used. + def resolve_to_shopify_mappings(next_integration_version) + @to_shopify_mappings.each do |mapping| + newer_mapping = next_integration_version&.to_shopify_mappings&.find do + _1.input_category["id"] == mapping.output_category + end + mapping.output_category = newer_mapping&.output_category || Category.find_by(id: mapping.output_category) + end + end + # For a mapping to an external taxonomy, get the IDs of external categories that are not mapped from Shopify. # # @return [Array] IDs of external categories not mapped from the Shopify taxonomy. Empty if there are no diff --git a/dev/lib/product_taxonomy/models/mapping_rule.rb b/dev/lib/product_taxonomy/models/mapping_rule.rb index 9070af167..097789ab2 100644 --- a/dev/lib/product_taxonomy/models/mapping_rule.rb +++ b/dev/lib/product_taxonomy/models/mapping_rule.rb @@ -29,7 +29,10 @@ def load_rules_from_source(integration_path:, direction:, full_names_by_id:) is_to_shopify = direction == :to_shopify input_category = is_to_shopify ? full_names_by_id[input_id] : Category.find_by(id: input_id) - output_category = is_to_shopify ? Category.find_by(id: output_id) : full_names_by_id[output_id] + # We set output_category to be the raw output ID if is_to_shopify is true, with an expectation that it will + # be resolved to a `Category` before the rule is serialized to JSON or TXT. + # See `IntegrationVersion#resolve_to_shopify_mappings`. + output_category = is_to_shopify ? output_id : full_names_by_id[output_id] raise ArgumentError, "Input category not found for mapping rule: #{rule}" unless input_category raise ArgumentError, "Output category not found for mapping rule: #{rule}" unless output_category @@ -41,7 +44,8 @@ def load_rules_from_source(integration_path:, direction:, full_names_by_id:) end end - attr_reader :input_category, :output_category + attr_reader :input_category + attr_accessor :output_category def initialize(input_category:, output_category:) @input_category = input_category @@ -87,19 +91,23 @@ def category_json(category) id: category["id"].to_s, full_name: category["full_name"], } - else + elsif category.is_a?(Category) { id: category.gid, full_name: category.full_name, } + else + raise ArgumentError, "Mapping rule category not resolved. Raw value: #{category}" end end def category_txt(category) if category.is_a?(Hash) category["full_name"] - else + elsif category.is_a?(Category) category.full_name + else + raise ArgumentError, "Mapping rule category not resolved. Raw value: #{category}" end end end diff --git a/dev/test/fixtures/data/integrations/foocommerce/1.0.0/full_names.yml b/dev/test/fixtures/data/integrations/foocommerce/1.0.0/full_names.yml index 7a4990387..9d9e577f5 100644 --- a/dev/test/fixtures/data/integrations/foocommerce/1.0.0/full_names.yml +++ b/dev/test/fixtures/data/integrations/foocommerce/1.0.0/full_names.yml @@ -1,5 +1,5 @@ --- - id: 1 - full_name: Animals & Pet Supplies (foocommerce) + full_name: Apparel & Accessories (foocommerce) - id: 2 - full_name: Animals & Pet Supplies > Live Animals (foocommerce) + full_name: Apparel & Accessories > Clothing (foocommerce) diff --git a/dev/test/fixtures/data/integrations/foocommerce/1.0.0/mappings/from_shopify.yml b/dev/test/fixtures/data/integrations/foocommerce/1.0.0/mappings/from_shopify.yml index 713539a74..59ad2092a 100644 --- a/dev/test/fixtures/data/integrations/foocommerce/1.0.0/mappings/from_shopify.yml +++ b/dev/test/fixtures/data/integrations/foocommerce/1.0.0/mappings/from_shopify.yml @@ -1,12 +1,12 @@ --- rules: - input: - product_category_id: ap + product_category_id: aa output: product_category_id: - '1' - input: - product_category_id: ap-1 + product_category_id: aa-1 output: product_category_id: - '2' diff --git a/dev/test/fixtures/data/integrations/integrations.yml b/dev/test/fixtures/data/integrations/integrations.yml index d32a94ae0..e210d6c71 100644 --- a/dev/test/fixtures/data/integrations/integrations.yml +++ b/dev/test/fixtures/data/integrations/integrations.yml @@ -5,3 +5,5 @@ - name: shopify available_versions: - shopify/2020-01 + - shopify/2021-01 + - shopify/2022-01 diff --git a/dev/test/fixtures/data/integrations/shopify/2020-01/full_names.yml b/dev/test/fixtures/data/integrations/shopify/2020-01/full_names.yml index 4a115fda4..e62ead064 100644 --- a/dev/test/fixtures/data/integrations/shopify/2020-01/full_names.yml +++ b/dev/test/fixtures/data/integrations/shopify/2020-01/full_names.yml @@ -1,5 +1,5 @@ --- - id: 1 - full_name: Animals & Pet Supplies (old shopify) + full_name: Apparel & Accessories (2020-01) - id: 2 - full_name: Animals & Pet Supplies > Live Animals (old shopify) + full_name: Apparel & Accessories > Clothing (2020-01) diff --git a/dev/test/fixtures/data/integrations/shopify/2020-01/mappings/to_shopify.yml b/dev/test/fixtures/data/integrations/shopify/2020-01/mappings/to_shopify.yml index 6bc3ec828..e8eff409d 100644 --- a/dev/test/fixtures/data/integrations/shopify/2020-01/mappings/to_shopify.yml +++ b/dev/test/fixtures/data/integrations/shopify/2020-01/mappings/to_shopify.yml @@ -1,12 +1,14 @@ --- +input_taxonomy: shopify/2020-01 +output_taxonomy: shopify/2021-01 rules: - input: product_category_id: 1 output: product_category_id: - - ap + - aa - input: product_category_id: 2 output: product_category_id: - - ap-1 + - aa-1 diff --git a/dev/test/fixtures/data/integrations/shopify/2021-01/full_names.yml b/dev/test/fixtures/data/integrations/shopify/2021-01/full_names.yml new file mode 100644 index 000000000..de88e16b6 --- /dev/null +++ b/dev/test/fixtures/data/integrations/shopify/2021-01/full_names.yml @@ -0,0 +1,3 @@ +--- +- id: aa-1 + full_name: Apparel & Accessories > Clothing (2021-01) diff --git a/dev/test/fixtures/data/integrations/shopify/2021-01/mappings/to_shopify.yml b/dev/test/fixtures/data/integrations/shopify/2021-01/mappings/to_shopify.yml new file mode 100644 index 000000000..0d1d3b4d8 --- /dev/null +++ b/dev/test/fixtures/data/integrations/shopify/2021-01/mappings/to_shopify.yml @@ -0,0 +1,9 @@ +--- +input_taxonomy: shopify/2021-01 +output_taxonomy: shopify/2022-01 +rules: +- input: + product_category_id: aa-1 + output: + product_category_id: + - aa-2 diff --git a/dev/test/fixtures/data/integrations/shopify/2022-01/full_names.yml b/dev/test/fixtures/data/integrations/shopify/2022-01/full_names.yml new file mode 100644 index 000000000..018942b19 --- /dev/null +++ b/dev/test/fixtures/data/integrations/shopify/2022-01/full_names.yml @@ -0,0 +1,3 @@ +--- +- id: aa-2 + full_name: Apparel & Accessories > Clothing Accessories (2022-01) diff --git a/dev/test/fixtures/data/integrations/shopify/2022-01/mappings/to_shopify.yml b/dev/test/fixtures/data/integrations/shopify/2022-01/mappings/to_shopify.yml new file mode 100644 index 000000000..b92de4daa --- /dev/null +++ b/dev/test/fixtures/data/integrations/shopify/2022-01/mappings/to_shopify.yml @@ -0,0 +1,9 @@ +--- +input_taxonomy: shopify/2022-01 +output_taxonomy: shopify/2025-01-unstable +rules: +- input: + product_category_id: aa-2 + output: + product_category_id: + - aa-3 diff --git a/dev/test/fixtures/dist/en/integrations/all_mappings.json b/dev/test/fixtures/dist/en/integrations/all_mappings.json new file mode 100644 index 000000000..268b99e66 --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/all_mappings.json @@ -0,0 +1,125 @@ +{ + "version": "2025-01-unstable", + "mappings": [ + { + "input_taxonomy": "shopify/2025-01-unstable", + "output_taxonomy": "foocommerce/1.0.0", + "rules": [ + { + "input": { + "category": { + "id": "gid://shopify/TaxonomyCategory/aa", + "full_name": "Apparel & Accessories" + } + }, + "output": { + "category": [ + { + "id": "1", + "full_name": "Apparel & Accessories (foocommerce)" + } + ] + } + }, + { + "input": { + "category": { + "id": "gid://shopify/TaxonomyCategory/aa-1", + "full_name": "Apparel & Accessories > Clothing" + } + }, + "output": { + "category": [ + { + "id": "2", + "full_name": "Apparel & Accessories > Clothing (foocommerce)" + } + ] + } + } + ] + }, + { + "input_taxonomy": "shopify/2020-01", + "output_taxonomy": "shopify/2025-01-unstable", + "rules": [ + { + "input": { + "category": { + "id": "1", + "full_name": "Apparel & Accessories (2020-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa", + "full_name": "Apparel & Accessories" + } + ] + } + }, + { + "input": { + "category": { + "id": "2", + "full_name": "Apparel & Accessories > Clothing (2020-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa-3", + "full_name": "Apparel & Accessories > Costumes & Accessories" + } + ] + } + } + ] + }, + { + "input_taxonomy": "shopify/2021-01", + "output_taxonomy": "shopify/2025-01-unstable", + "rules": [ + { + "input": { + "category": { + "id": "aa-1", + "full_name": "Apparel & Accessories > Clothing (2021-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa-3", + "full_name": "Apparel & Accessories > Costumes & Accessories" + } + ] + } + } + ] + }, + { + "input_taxonomy": "shopify/2022-01", + "output_taxonomy": "shopify/2025-01-unstable", + "rules": [ + { + "input": { + "category": { + "id": "aa-2", + "full_name": "Apparel & Accessories > Clothing Accessories (2022-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa-3", + "full_name": "Apparel & Accessories > Costumes & Accessories" + } + ] + } + } + ] + } + ] +} diff --git a/dev/test/fixtures/dist/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json b/dev/test/fixtures/dist/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json new file mode 100644 index 000000000..96c6626f5 --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json @@ -0,0 +1,43 @@ +{ + "version": "2025-01-unstable", + "mappings": [ + { + "input_taxonomy": "shopify/2025-01-unstable", + "output_taxonomy": "foocommerce/1.0.0", + "rules": [ + { + "input": { + "category": { + "id": "gid://shopify/TaxonomyCategory/aa", + "full_name": "Apparel & Accessories" + } + }, + "output": { + "category": [ + { + "id": "1", + "full_name": "Apparel & Accessories (foocommerce)" + } + ] + } + }, + { + "input": { + "category": { + "id": "gid://shopify/TaxonomyCategory/aa-1", + "full_name": "Apparel & Accessories > Clothing" + } + }, + "output": { + "category": [ + { + "id": "2", + "full_name": "Apparel & Accessories > Clothing (foocommerce)" + } + ] + } + } + ] + } + ] +} diff --git a/dev/test/fixtures/dist/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt b/dev/test/fixtures/dist/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt new file mode 100644 index 000000000..d8229d87e --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt @@ -0,0 +1,10 @@ +# Shopify Product Taxonomy - Mapping shopify/2025-01-unstable to foocommerce/1.0.0 +# Format: +# → {base taxonomy category name} +# ⇒ {mapped taxonomy category name} + +→ Apparel & Accessories +⇒ Apparel & Accessories (foocommerce) + +→ Apparel & Accessories > Clothing +⇒ Apparel & Accessories > Clothing (foocommerce) diff --git a/dev/test/fixtures/dist/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json new file mode 100644 index 000000000..b1dc31d87 --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json @@ -0,0 +1,43 @@ +{ + "version": "2025-01-unstable", + "mappings": [ + { + "input_taxonomy": "shopify/2020-01", + "output_taxonomy": "shopify/2025-01-unstable", + "rules": [ + { + "input": { + "category": { + "id": "1", + "full_name": "Apparel & Accessories (2020-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa", + "full_name": "Apparel & Accessories" + } + ] + } + }, + { + "input": { + "category": { + "id": "2", + "full_name": "Apparel & Accessories > Clothing (2020-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa-3", + "full_name": "Apparel & Accessories > Costumes & Accessories" + } + ] + } + } + ] + } + ] +} diff --git a/dev/test/fixtures/dist/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt new file mode 100644 index 000000000..8608efa36 --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt @@ -0,0 +1,10 @@ +# Shopify Product Taxonomy - Mapping shopify/2020-01 to shopify/2025-01-unstable +# Format: +# → {base taxonomy category name} +# ⇒ {mapped taxonomy category name} + +→ Apparel & Accessories (2020-01) +⇒ Apparel & Accessories + +→ Apparel & Accessories > Clothing (2020-01) +⇒ Apparel & Accessories > Costumes & Accessories diff --git a/dev/test/fixtures/dist/en/integrations/shopify/shopify_2021-01_to_shopify_2025-01.json b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2021-01_to_shopify_2025-01.json new file mode 100644 index 000000000..1e8c9b8ec --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2021-01_to_shopify_2025-01.json @@ -0,0 +1,27 @@ +{ + "version": "2025-01-unstable", + "mappings": [ + { + "input_taxonomy": "shopify/2021-01", + "output_taxonomy": "shopify/2025-01-unstable", + "rules": [ + { + "input": { + "category": { + "id": "aa-1", + "full_name": "Apparel & Accessories > Clothing (2021-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa-3", + "full_name": "Apparel & Accessories > Costumes & Accessories" + } + ] + } + } + ] + } + ] +} diff --git a/dev/test/fixtures/dist/en/integrations/shopify/shopify_2021-01_to_shopify_2025-01.txt b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2021-01_to_shopify_2025-01.txt new file mode 100644 index 000000000..70e35bd78 --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2021-01_to_shopify_2025-01.txt @@ -0,0 +1,7 @@ +# Shopify Product Taxonomy - Mapping shopify/2021-01 to shopify/2025-01-unstable +# Format: +# → {base taxonomy category name} +# ⇒ {mapped taxonomy category name} + +→ Apparel & Accessories > Clothing (2021-01) +⇒ Apparel & Accessories > Costumes & Accessories diff --git a/dev/test/fixtures/dist/en/integrations/shopify/shopify_2022-01_to_shopify_2025-01.json b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2022-01_to_shopify_2025-01.json new file mode 100644 index 000000000..dc69df44f --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2022-01_to_shopify_2025-01.json @@ -0,0 +1,27 @@ +{ + "version": "2025-01-unstable", + "mappings": [ + { + "input_taxonomy": "shopify/2022-01", + "output_taxonomy": "shopify/2025-01-unstable", + "rules": [ + { + "input": { + "category": { + "id": "aa-2", + "full_name": "Apparel & Accessories > Clothing Accessories (2022-01)" + } + }, + "output": { + "category": [ + { + "id": "gid://shopify/TaxonomyCategory/aa-3", + "full_name": "Apparel & Accessories > Costumes & Accessories" + } + ] + } + } + ] + } + ] +} diff --git a/dev/test/fixtures/dist/en/integrations/shopify/shopify_2022-01_to_shopify_2025-01.txt b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2022-01_to_shopify_2025-01.txt new file mode 100644 index 000000000..dada09750 --- /dev/null +++ b/dev/test/fixtures/dist/en/integrations/shopify/shopify_2022-01_to_shopify_2025-01.txt @@ -0,0 +1,7 @@ +# Shopify Product Taxonomy - Mapping shopify/2022-01 to shopify/2025-01-unstable +# Format: +# → {base taxonomy category name} +# ⇒ {mapped taxonomy category name} + +→ Apparel & Accessories > Clothing Accessories (2022-01) +⇒ Apparel & Accessories > Costumes & Accessories diff --git a/dev/test/integration/generate_mappings_dist_test.rb b/dev/test/integration/generate_mappings_dist_test.rb new file mode 100644 index 000000000..34041501a --- /dev/null +++ b/dev/test/integration/generate_mappings_dist_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require_relative "../test_helper" +require "tmpdir" + +module ProductTaxonomy + class GenerateMappingsDistTest < ActiveSupport::TestCase + FIXTURES_PATH = File.expand_path("../fixtures", __dir__) + + setup do + @tmp_path = Dir.mktmpdir + @current_shopify_version = "2025-01-unstable" + + # Create categories so they can be resolved + aa = Category.new(id: "aa", name: "Apparel & Accessories") + aa_1 = Category.new(id: "aa-1", name: "Clothing") + aa_2 = Category.new(id: "aa-2", name: "Clothing Accessories") + aa_3 = Category.new(id: "aa-3", name: "Costumes & Accessories") + aa.add_child(aa_1) + aa.add_child(aa_2) + aa.add_child(aa_3) + Category.add(aa) + Category.add(aa_1) + Category.add(aa_2) + Category.add(aa_3) + end + + teardown do + FileUtils.remove_entry(@tmp_path) + end + + test "generate_all_distributions generates all_mappings.json and distribution files for all integration versions" do + IntegrationVersion.generate_all_distributions( + output_path: @tmp_path, + current_shopify_version: @current_shopify_version, + logger: stub("logger", info: nil), + base_path: File.expand_path("data/integrations", FIXTURES_PATH), + ) + + assert_file_contents_match "all_mappings.json" + assert_file_contents_match "shopify/shopify_2020-01_to_shopify_2025-01.json" + assert_file_contents_match "shopify/shopify_2020-01_to_shopify_2025-01.txt" + assert_file_contents_match "shopify/shopify_2021-01_to_shopify_2025-01.json" + assert_file_contents_match "shopify/shopify_2021-01_to_shopify_2025-01.txt" + assert_file_contents_match "shopify/shopify_2022-01_to_shopify_2025-01.json" + assert_file_contents_match "shopify/shopify_2022-01_to_shopify_2025-01.txt" + assert_file_contents_match "foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json" + assert_file_contents_match "foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt" + end + + def assert_file_contents_match(file_path) + fixture_path = File.expand_path("dist/en/integrations/#{file_path}", FIXTURES_PATH) + expected_path = File.expand_path("en/integrations/#{file_path}", @tmp_path) + + assert(File.exist?(expected_path), "Expected file to exist: #{expected_path}") + assert_equal(File.read(fixture_path), File.read(expected_path), "File contents don't match for: #{file_path}") + end + end +end diff --git a/dev/test/models/integration_version_test.rb b/dev/test/models/integration_version_test.rb index ba5383f8f..4eead9d4f 100644 --- a/dev/test/models/integration_version_test.rb +++ b/dev/test/models/integration_version_test.rb @@ -7,16 +7,23 @@ class IntegrationVersionTest < TestCase DATA_PATH = File.expand_path("../fixtures/data", __dir__) setup do - ap = Category.new(id: "ap", name: "Animals & Pet Supplies") - ap_1 = Category.new(id: "ap-1", name: "Live Animals") - ap.add_child(ap_1) - Category.add(ap) - Category.add(ap_1) + aa = Category.new(id: "aa", name: "Apparel & Accessories") + aa_1 = Category.new(id: "aa-1", name: "Clothing") + aa_2 = Category.new(id: "aa-2", name: "Clothing Accessories") + aa_3 = Category.new(id: "aa-3", name: "Costumes & Accessories") + aa.add_child(aa_1) + aa.add_child(aa_2) + aa.add_child(aa_3) + Category.add(aa) + Category.add(aa_1) + Category.add(aa_2) + Category.add(aa_3) @current_shopify_version = "2025-01-unstable" @shopify_integration = IntegrationVersion.load_from_source( integration_path: File.expand_path("integrations/shopify/2020-01", DATA_PATH), current_shopify_version: @current_shopify_version, ) + @shopify_integration.resolve_to_shopify_mappings(nil) # Resolve against current version @external_integration = IntegrationVersion.load_from_source( integration_path: File.expand_path("integrations/foocommerce/1.0.0", DATA_PATH), current_shopify_version: @current_shopify_version, @@ -35,11 +42,33 @@ class IntegrationVersionTest < TestCase current_shopify_version: @current_shopify_version, ) - assert_equal 2, integration_versions.size + assert_equal 4, integration_versions.size assert_equal "foocommerce", integration_versions.first.name assert_equal "1.0.0", integration_versions.first.version - assert_equal "shopify", integration_versions.last.name - assert_equal "2020-01", integration_versions.last.version + assert_equal "shopify", integration_versions.second.name + assert_equal "2020-01", integration_versions.second.version + assert_equal "shopify", integration_versions.third.name + assert_equal "2021-01", integration_versions.third.version + assert_equal "shopify", integration_versions.fourth.name + assert_equal "2022-01", integration_versions.fourth.version + end + + test "load_all_from_source resolves chain of to_shopify mappings against latest version" do + integrations_path = File.expand_path("integrations", DATA_PATH) + integration_versions = IntegrationVersion.load_all_from_source( + base_path: integrations_path, + current_shopify_version: @current_shopify_version, + ) + output_mappings = integration_versions + .index_by { "#{_1.name}/#{_1.version}" } + .transform_values(&:to_shopify_mappings) + + assert_equal "gid://shopify/TaxonomyCategory/aa", output_mappings["shopify/2020-01"].first.output_category.gid + # Defined as mapping to aa-1 in the 2020-01 version, resolved through to aa-3 + assert_equal "gid://shopify/TaxonomyCategory/aa-3", output_mappings["shopify/2020-01"].second.output_category.gid + # Defined as mapping to aa-2 in the 2021-01 version, resolved through to aa-3 + assert_equal "gid://shopify/TaxonomyCategory/aa-3", output_mappings["shopify/2021-01"].first.output_category.gid + assert_equal "gid://shopify/TaxonomyCategory/aa-3", output_mappings["shopify/2022-01"].first.output_category.gid end test "to_json returns the JSON representation of a mapping to Shopify" do @@ -48,25 +77,20 @@ class IntegrationVersionTest < TestCase output_taxonomy: "shopify/2025-01-unstable", rules: [ { - input: { category: { id: "1", full_name: "Animals & Pet Supplies (old shopify)" } }, + input: { category: { id: "1", full_name: "Apparel & Accessories (2020-01)" } }, output: { category: [{ - id: "gid://shopify/TaxonomyCategory/ap", - full_name: "Animals & Pet Supplies", + id: "gid://shopify/TaxonomyCategory/aa", + full_name: "Apparel & Accessories", }], }, }, { - input: { - category: { - id: "2", - full_name: "Animals & Pet Supplies > Live Animals (old shopify)", - }, - }, + input: { category: { id: "2", full_name: "Apparel & Accessories > Clothing (2020-01)" } }, output: { category: [{ - id: "gid://shopify/TaxonomyCategory/ap-1", - full_name: "Animals & Pet Supplies > Live Animals", + id: "gid://shopify/TaxonomyCategory/aa-1", + full_name: "Apparel & Accessories > Clothing", }], }, }, @@ -81,27 +105,22 @@ class IntegrationVersionTest < TestCase output_taxonomy: "foocommerce/1.0.0", rules: [ { - input: { category: { id: "gid://shopify/TaxonomyCategory/ap", full_name: "Animals & Pet Supplies" } }, + input: { category: { id: "gid://shopify/TaxonomyCategory/aa", full_name: "Apparel & Accessories" } }, output: { category: [{ id: "1", - full_name: "Animals & Pet Supplies (foocommerce)", + full_name: "Apparel & Accessories (foocommerce)", }], }, }, { input: { category: { - id: "gid://shopify/TaxonomyCategory/ap-1", - full_name: "Animals & Pet Supplies > Live Animals", + id: "gid://shopify/TaxonomyCategory/aa-1", + full_name: "Apparel & Accessories > Clothing", }, }, - output: { - category: [{ - id: "2", - full_name: "Animals & Pet Supplies > Live Animals (foocommerce)", - }], - }, + output: { category: [{ id: "2", full_name: "Apparel & Accessories > Clothing (foocommerce)" }] }, }, ], } @@ -115,11 +134,11 @@ class IntegrationVersionTest < TestCase # → {base taxonomy category name} # ⇒ {mapped taxonomy category name} - → Animals & Pet Supplies (old shopify) - ⇒ Animals & Pet Supplies + → Apparel & Accessories (2020-01) + ⇒ Apparel & Accessories - → Animals & Pet Supplies > Live Animals (old shopify) - ⇒ Animals & Pet Supplies > Live Animals + → Apparel & Accessories > Clothing (2020-01) + ⇒ Apparel & Accessories > Clothing TXT assert_equal expected_txt.chomp, @shopify_integration.to_txt(direction: :to_shopify) end @@ -131,11 +150,11 @@ class IntegrationVersionTest < TestCase # → {base taxonomy category name} # ⇒ {mapped taxonomy category name} - → Animals & Pet Supplies - ⇒ Animals & Pet Supplies (foocommerce) + → Apparel & Accessories + ⇒ Apparel & Accessories (foocommerce) - → Animals & Pet Supplies > Live Animals - ⇒ Animals & Pet Supplies > Live Animals (foocommerce) + → Apparel & Accessories > Clothing + ⇒ Apparel & Accessories > Clothing (foocommerce) TXT assert_equal expected_txt.chomp, @external_integration.to_txt(direction: :from_shopify) end @@ -148,16 +167,19 @@ class IntegrationVersionTest < TestCase } File.expects(:write).with( "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json", - JSON.pretty_generate(expected_shopify_json), + JSON.pretty_generate(expected_shopify_json) + "\n", ) File.expects(:write).with( "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt", - @shopify_integration.to_txt(direction: :to_shopify), - ) - @shopify_integration.generate_distribution( - output_path: "/tmp/fake", - direction: :to_shopify, + @shopify_integration.to_txt(direction: :to_shopify) + "\n", ) + + assert_nothing_raised do + @shopify_integration.generate_distribution( + output_path: "/tmp/fake", + direction: :to_shopify, + ) + end end test "generate_distribution generates the distribution files for a mapping from Shopify" do @@ -168,16 +190,19 @@ class IntegrationVersionTest < TestCase } File.expects(:write).with( "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json", - JSON.pretty_generate(expected_external_json), + JSON.pretty_generate(expected_external_json) + "\n", ) File.expects(:write).with( "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt", - @external_integration.to_txt(direction: :from_shopify), - ) - @external_integration.generate_distribution( - output_path: "/tmp/fake", - direction: :from_shopify, + @external_integration.to_txt(direction: :from_shopify) + "\n", ) + + assert_nothing_raised do + @external_integration.generate_distribution( + output_path: "/tmp/fake", + direction: :from_shopify, + ) + end end test "generate_distributions only calls generate_distribution with to_shopify for Shopify integration version" do @@ -209,52 +234,6 @@ class IntegrationVersionTest < TestCase assert_equal expected_json, IntegrationVersion.to_json(mappings:, current_shopify_version: "2025-01-unstable") end - test "generate_all_distributions generates all_mappings.json and distribution files for all integration versions" do - FileUtils.expects(:mkdir_p).with("/tmp/fake/en/integrations/foocommerce") - FileUtils.expects(:mkdir_p).with("/tmp/fake/en/integrations/shopify") - expected_external_json = IntegrationVersion.to_json( - mappings: [@external_integration.to_json(direction: :from_shopify)], - current_shopify_version: @current_shopify_version, - ) - File.expects(:write).with( - "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json", - JSON.pretty_generate(expected_external_json), - ) - File.expects(:write).with( - "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt", - @external_integration.to_txt(direction: :from_shopify), - ) - expected_shopify_json = IntegrationVersion.to_json( - mappings: [@shopify_integration.to_json(direction: :to_shopify)], - current_shopify_version: @current_shopify_version, - ) - File.expects(:write).with( - "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json", - JSON.pretty_generate(expected_shopify_json), - ) - File.expects(:write).with( - "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt", - @shopify_integration.to_txt(direction: :to_shopify), - ) - all_mappings_json = IntegrationVersion.to_json( - mappings: [ - @external_integration.to_json(direction: :from_shopify), - @shopify_integration.to_json(direction: :to_shopify), - ], - current_shopify_version: @current_shopify_version, - ) - File.expects(:write).with( - "/tmp/fake/en/integrations/all_mappings.json", - JSON.pretty_generate(all_mappings_json), - ) - IntegrationVersion.generate_all_distributions( - output_path: "/tmp/fake", - current_shopify_version: @current_shopify_version, - logger: stub("logger", info: nil), - base_path: File.expand_path("integrations", DATA_PATH), - ) - end - test "unmapped_external_category_ids returns IDs of external categories not mapped from the Shopify taxonomy" do external_category1 = { "id" => "1", "full_name" => "External category 1" } from_shopify_mappings = [ diff --git a/dev/test/models/mapping_rule_test.rb b/dev/test/models/mapping_rule_test.rb index 52450db22..3f1825539 100644 --- a/dev/test/models/mapping_rule_test.rb +++ b/dev/test/models/mapping_rule_test.rb @@ -112,12 +112,12 @@ class MappingRuleTest < TestCase end end - test "load_rules_from_source raises an error if the output category is not found" do + test "load_rules_from_source raises an error if the output category is missing" do File.expects(:exist?).with("/fake/data/integrations/google/2021-09-21/mappings/to_shopify.yml").returns(true) YAML.expects(:safe_load_file) .with("/fake/data/integrations/google/2021-09-21/mappings/to_shopify.yml") .returns("rules" => [ - { "input" => { "product_category_id" => "166" }, "output" => { "product_category_id" => ["bb"] } }, + { "input" => { "product_category_id" => "166" }, "output" => { "product_category_id" => [] } }, ]) assert_raises(ArgumentError) do @@ -267,5 +267,25 @@ class MappingRuleTest < TestCase ) refute rule.input_txt_equals_output_txt? end + + test "to_txt raises an error if the output category is not resolved" do + rule = MappingRule.new( + input_category: @shopify_category, + output_category: "aa", + ) + assert_raises(ArgumentError) do + rule.to_txt + end + end + + test "to_json raises an error if the output category is not resolved" do + rule = MappingRule.new( + input_category: @shopify_category, + output_category: "aa", + ) + assert_raises(ArgumentError) do + rule.to_json + end + end end end