Skip to content

Commit 4608c38

Browse files
authored
Migrate add_category command to new tool (#565)
2 parents 0f01b17 + ecaf0e5 commit 4608c38

File tree

6 files changed

+183
-0
lines changed

6 files changed

+183
-0
lines changed

dev/lib/product_taxonomy.rb

+1
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ def data_path = DATA_PATH
4848
require_relative "product_taxonomy/commands/generate_release_command"
4949
require_relative "product_taxonomy/commands/dump_categories_command"
5050
require_relative "product_taxonomy/commands/sync_en_localizations_command"
51+
require_relative "product_taxonomy/commands/add_category_command"

dev/lib/product_taxonomy/cli.rb

+6
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,11 @@ def dump_categories
5151
def sync_en_localizations
5252
SyncEnLocalizationsCommand.new(options).run
5353
end
54+
55+
desc "add_category NAME PARENT_ID", "Add a new category to the taxonomy with NAME, as a child of PARENT_ID"
56+
option :id, type: :string, desc: "Override the created category's ID"
57+
def add_category(name, parent_id)
58+
AddCategoryCommand.new(options.merge(name:, parent_id:)).run
59+
end
5460
end
5561
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
module ProductTaxonomy
4+
class AddCategoryCommand < Command
5+
def initialize(options)
6+
super
7+
load_taxonomy
8+
@name = options[:name]
9+
@parent_id = options[:parent_id]
10+
@id = options[:id]
11+
end
12+
13+
def execute
14+
create_category!
15+
update_data_files!
16+
end
17+
18+
private
19+
20+
def create_category!
21+
parent = Category.find_by(id: @parent_id)
22+
raise "Parent category `#{@parent_id}` not found" if parent.nil?
23+
24+
@new_category = Category.new(id: @id || parent.next_child_id, name: @name, parent:)
25+
raise "Failed to create category: #{@new_category.errors.full_messages.to_sentence}" unless @new_category.valid?
26+
27+
parent.add_child(@new_category)
28+
Category.add(@new_category)
29+
logger.info("Created category `#{@new_category.name}` with id=`#{@new_category.id}`")
30+
end
31+
32+
def update_data_files!
33+
DumpCategoriesCommand.new(verticals: [@new_category.root.id]).execute
34+
SyncEnLocalizationsCommand.new(targets: "categories").execute
35+
GenerateDocsCommand.new({}).execute
36+
end
37+
end
38+
end

dev/lib/product_taxonomy/models/category.rb

+9
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ def friendly_name
227227
"#{id}_#{IdentifierFormatter.format_friendly_id(name)}"
228228
end
229229

230+
# The next child ID for the category
231+
#
232+
# @return [String]
233+
def next_child_id
234+
largest_child_id = children.map { _1.id.split("-").last.to_i }.max || 0
235+
236+
"#{id}-#{largest_child_id + 1}"
237+
end
238+
230239
private
231240

232241
#
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
module ProductTaxonomy
6+
class AddCategoryCommandTest < TestCase
7+
setup do
8+
@root_category = Category.new(id: "aa", name: "Root Category")
9+
@child_category = Category.new(id: "aa-1", name: "Child Category", parent: @root_category)
10+
@root_category.add_child(@child_category)
11+
12+
Category.add(@root_category)
13+
Category.add(@child_category)
14+
end
15+
16+
test "execute successfully adds a new category" do
17+
DumpCategoriesCommand.any_instance.expects(:execute).once
18+
SyncEnLocalizationsCommand.any_instance.expects(:execute).once
19+
GenerateDocsCommand.any_instance.expects(:execute).once
20+
21+
AddCategoryCommand.new(name: "New Category", parent_id: "aa").execute
22+
23+
new_category = @root_category.children.find { |c| c.name == "New Category" }
24+
assert_not_nil new_category
25+
assert_equal "aa-2", new_category.id # Since aa-1 already exists
26+
assert_equal "New Category", new_category.name
27+
assert_equal @root_category, new_category.parent
28+
assert_not_nil Category.find_by(id: "aa-2")
29+
end
30+
31+
test "execute successfully adds a category with custom numeric ID" do
32+
DumpCategoriesCommand.any_instance.expects(:execute).once
33+
SyncEnLocalizationsCommand.any_instance.expects(:execute).once
34+
GenerateDocsCommand.any_instance.expects(:execute).once
35+
36+
AddCategoryCommand.new(name: "Custom ID Category", parent_id: "aa", id: "aa-5").execute
37+
38+
new_category = @root_category.children.find { |c| c.name == "Custom ID Category" }
39+
assert_not_nil new_category
40+
assert_equal "aa-5", new_category.id
41+
assert_equal "Custom ID Category", new_category.name
42+
assert_equal @root_category, new_category.parent
43+
assert_not_nil Category.find_by(id: "aa-5")
44+
end
45+
46+
test "execute raises error when parent category not found" do
47+
assert_raises(RuntimeError) do
48+
AddCategoryCommand.new(name: "New Category", parent_id: "nonexistent").execute
49+
end
50+
end
51+
52+
test "execute raises error when category ID already exists" do
53+
assert_raises(RuntimeError) do
54+
AddCategoryCommand.new(name: "Duplicate ID", parent_id: "aa", id: "aa-1").execute
55+
end
56+
end
57+
58+
test "execute raises error when category ID format is invalid" do
59+
assert_raises(RuntimeError) do
60+
AddCategoryCommand.new(name: "Invalid ID", parent_id: "aa", id: "aa-custom").execute
61+
end
62+
end
63+
64+
test "execute raises error when category name is invalid" do
65+
assert_raises(RuntimeError) do
66+
AddCategoryCommand.new(name: "", parent_id: "aa").execute
67+
end
68+
end
69+
70+
test "execute updates correct vertical based on parent category" do
71+
DumpCategoriesCommand.expects(:new).with(verticals: ["aa"]).returns(stub(execute: true))
72+
SyncEnLocalizationsCommand.any_instance.stubs(:execute)
73+
GenerateDocsCommand.any_instance.stubs(:execute)
74+
75+
AddCategoryCommand.new(name: "New Category", parent_id: "aa").execute
76+
77+
new_category = @root_category.children.find { |c| c.name == "New Category" }
78+
assert_not_nil new_category
79+
assert_equal @root_category, new_category.parent
80+
assert_not_nil Category.find_by(id: "aa-2")
81+
end
82+
83+
test "execute generates sequential IDs correctly" do
84+
DumpCategoriesCommand.any_instance.stubs(:execute)
85+
SyncEnLocalizationsCommand.any_instance.stubs(:execute)
86+
GenerateDocsCommand.any_instance.stubs(:execute)
87+
88+
AddCategoryCommand.new(name: "First New", parent_id: "aa").execute
89+
AddCategoryCommand.new(name: "Second New", parent_id: "aa").execute
90+
91+
new_categories = @root_category.children.select { |c| c.name.include?("New") }.sort_by(&:id)
92+
assert_equal 2, new_categories.size
93+
assert_equal "aa-2", new_categories[0].id
94+
assert_equal "aa-3", new_categories[1].id
95+
assert_equal "First New", new_categories[0].name
96+
assert_equal "Second New", new_categories[1].name
97+
assert_not_nil Category.find_by(id: "aa-2")
98+
assert_not_nil Category.find_by(id: "aa-3")
99+
end
100+
end
101+
end

dev/test/models/category_test.rb

+28
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,34 @@ class CategoryTest < TestCase
403403
assert_equal ["aa", 1, 1], @grandchild.id_parts
404404
end
405405

406+
test "next_child_id returns id-1 for category with no children" do
407+
category = Category.new(id: "aa", name: "Root")
408+
409+
assert_equal "aa-1", category.next_child_id
410+
end
411+
412+
test "next_child_id returns next sequential id based on largest child id" do
413+
root = Category.new(id: "aa", name: "Root")
414+
child1 = Category.new(id: "aa-1", name: "Child 1")
415+
child2 = Category.new(id: "aa-2", name: "Child 2")
416+
child3 = Category.new(id: "aa-5", name: "Child 3") # Note gap in sequence
417+
root.add_child(child1)
418+
root.add_child(child2)
419+
root.add_child(child3)
420+
421+
assert_equal "aa-6", root.next_child_id
422+
end
423+
424+
test "next_child_id ignores secondary children when determining next id" do
425+
root = Category.new(id: "aa", name: "Root")
426+
child = Category.new(id: "aa-1", name: "Child")
427+
secondary = Category.new(id: "bb-5", name: "Secondary")
428+
root.add_child(child)
429+
root.add_secondary_child(secondary)
430+
431+
assert_equal "aa-2", root.next_child_id
432+
end
433+
406434
private
407435

408436
def stub_localizations

0 commit comments

Comments
 (0)