diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-17 22:48:36 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-17 22:48:36 +0200 |
| commit | 2748dc23be86eeb1e4f5e155b8f2191245bb5f5c (patch) | |
| tree | 58a3d0953732eef3cc2f2f64b411a396246d26ee /test | |
| parent | 24d61c40786497d864c7c8843e68cf2af880e5f7 (diff) | |
Add trash!, restore_from_trash!, and destroy_from_trash! with log entries
Also update the node action contract to include the trash related verbs.
Diffstat (limited to 'test')
| -rw-r--r-- | test/models/node_trash_test.rb | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/test/models/node_trash_test.rb b/test/models/node_trash_test.rb new file mode 100644 index 0000000..b72770b --- /dev/null +++ b/test/models/node_trash_test.rb | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | require "test_helper" | ||
| 2 | |||
| 3 | class NodeTrashTest < ActiveSupport::TestCase | ||
| 4 | |||
| 5 | def setup | ||
| 6 | @user1 = User.create!(:login => "trasher", :email => "t@example.com", | ||
| 7 | :password => "foobar", :password_confirmation => "foobar") | ||
| 8 | end | ||
| 9 | |||
| 10 | test "trash! demotes the head into the draft slot when no draft exists" do | ||
| 11 | node = create_node_with_published_page | ||
| 12 | former_head = node.head | ||
| 13 | |||
| 14 | node.trash!(@user1) | ||
| 15 | node.reload | ||
| 16 | |||
| 17 | assert_nil node.head_id | ||
| 18 | assert_equal former_head, node.draft | ||
| 19 | assert node.in_trash? | ||
| 20 | end | ||
| 21 | |||
| 22 | test "trash! keeps an existing draft; the former head stays a plain revision" do | ||
| 23 | node = create_node_with_published_page | ||
| 24 | draft = find_or_create_draft(node, @user1) | ||
| 25 | former_head = node.head | ||
| 26 | |||
| 27 | node.trash!(@user1) | ||
| 28 | node.reload | ||
| 29 | |||
| 30 | assert_nil node.head_id | ||
| 31 | assert_equal draft, node.draft | ||
| 32 | assert_includes node.pages, former_head | ||
| 33 | end | ||
| 34 | |||
| 35 | test "trash! demotes descendant heads and counts them in the log entry" do | ||
| 36 | parent = create_node_with_published_page | ||
| 37 | child = parent.children.create!(:slug => "trashed_child") | ||
| 38 | child.draft.update!(:title => "Child") | ||
| 39 | child.publish_draft!(@user1) | ||
| 40 | |||
| 41 | parent.trash!(@user1) | ||
| 42 | |||
| 43 | action = NodeAction.where(:action => "trash").last | ||
| 44 | assert_equal parent, action.node | ||
| 45 | assert_equal 2, action.metadata["demoted_heads"] | ||
| 46 | assert action.metadata["was_published"] | ||
| 47 | assert_nil child.reload.head_id | ||
| 48 | assert child.in_trash? | ||
| 49 | end | ||
| 50 | |||
| 51 | test "trash! logs one entry with the path pair and updates unique names" do | ||
| 52 | node = create_node_with_published_page | ||
| 53 | path_before = node.unique_name | ||
| 54 | |||
| 55 | assert_difference "NodeAction.count", 1 do | ||
| 56 | node.trash!(@user1) | ||
| 57 | end | ||
| 58 | |||
| 59 | action = NodeAction.last | ||
| 60 | assert_equal path_before, action.metadata.dig("path", "from") | ||
| 61 | assert_equal node.reload.unique_name, action.metadata.dig("path", "to") | ||
| 62 | assert action.metadata.dig("path", "to").start_with?(CccConventions::TRASH_SLUG) | ||
| 63 | end | ||
| 64 | |||
| 65 | test "trash! on an already trashed node is a logged-nothing no-op" do | ||
| 66 | node = create_node_with_published_page | ||
| 67 | node.trash!(@user1) | ||
| 68 | |||
| 69 | assert_no_difference "NodeAction.count" do | ||
| 70 | assert_nil node.reload.trash!(@user1) | ||
| 71 | end | ||
| 72 | end | ||
| 73 | |||
| 74 | test "restore_from_trash! reparents as drafts without republishing" do | ||
| 75 | node = create_node_with_published_page | ||
| 76 | node.trash!(@user1) | ||
| 77 | target = Node.root.children.create!(:slug => "restore_target") | ||
| 78 | |||
| 79 | node.reload.restore_from_trash!(target, @user1) | ||
| 80 | node.reload | ||
| 81 | |||
| 82 | assert_equal target, node.parent | ||
| 83 | assert_not node.in_trash? | ||
| 84 | assert_nil node.head_id | ||
| 85 | assert_not_nil node.draft_id | ||
| 86 | assert_equal "restore_from_trash", NodeAction.last.action | ||
| 87 | end | ||
| 88 | |||
| 89 | test "restore_from_trash! refuses targets inside the Trash and the Trash itself" do | ||
| 90 | node = create_node_with_published_page | ||
| 91 | node.trash!(@user1) | ||
| 92 | other_trashed = Node.trash.children.create!(:slug => "also_trashed") | ||
| 93 | |||
| 94 | assert_raises(ActiveRecord::RecordInvalid) { node.reload.restore_from_trash!(Node.trash, @user1) } | ||
| 95 | assert_raises(ActiveRecord::RecordInvalid) { node.reload.restore_from_trash!(other_trashed, @user1) } | ||
| 96 | end | ||
| 97 | |||
| 98 | test "destroy_from_trash! refuses nodes outside the Trash" do | ||
| 99 | node = create_node_with_published_page | ||
| 100 | |||
| 101 | assert_raises(ActiveRecord::RecordInvalid) { node.destroy_from_trash!(@user1) } | ||
| 102 | assert Node.exists?(node.id) | ||
| 103 | end | ||
| 104 | |||
| 105 | test "destroy_from_trash! refuses nodes with children" do | ||
| 106 | node = create_node_with_published_page | ||
| 107 | node.children.create!(:slug => "still_here") | ||
| 108 | node.trash!(@user1) | ||
| 109 | |||
| 110 | assert_raises(ActiveRecord::RecordNotDestroyed) { node.reload.destroy_from_trash!(@user1) } | ||
| 111 | end | ||
| 112 | |||
| 113 | test "destroy_from_trash! logs an entry that survives the node" do | ||
| 114 | node = create_node_with_published_page | ||
| 115 | Globalize.with_locale(I18n.default_locale) { node.head.update!(:title => "Doomed") } | ||
| 116 | node.trash!(@user1) | ||
| 117 | final_path = node.reload.unique_name | ||
| 118 | |||
| 119 | node.destroy_from_trash!(@user1) | ||
| 120 | |||
| 121 | action = NodeAction.where(:action => "destroy").last | ||
| 122 | assert_nil action.node_id | ||
| 123 | assert_equal final_path, action.metadata["path"] | ||
| 124 | assert_equal "Doomed", action.subject_name | ||
| 125 | assert_equal @user1, action.user | ||
| 126 | end | ||
| 127 | end | ||
