Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
jobs:
gemfile-drift:
name: Gemfile drift
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -25,7 +25,7 @@ jobs:

security:
name: Security
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -47,7 +47,7 @@ jobs:

lint:
name: Lint
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -66,7 +66,7 @@ jobs:

zizmor:
name: GitHub Actions audit
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-sync-saas-lockfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ permissions:

jobs:
sync:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # zizmor: ignore[artipacked] -- credentials needed for git push # v7.0.1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
jobs:
test:
name: Tests (${{ matrix.mode }})
runs-on: ubuntu-latest
runs-on: ubuntu-24.04

strategy:
matrix:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class RemoveBoardCreatorDraftedIndexFromCards < ActiveRecord::Migration[8.2]
def up
remove_index :cards, name: "index_cards_on_board_creator_drafted", if_exists: true
end
end
2 changes: 1 addition & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion db/schema_sqlite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.2].define(version: 2026_10_19_150000) do
ActiveRecord::Schema[8.2].define(version: 2026_11_02_120000) do
create_table "accesses", id: :uuid, force: :cascade do |t|
t.datetime "accessed_at"
t.uuid "account_id", null: false
Expand Down
20 changes: 20 additions & 0 deletions test/controllers/cards/boards_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,24 @@ class Cards::BoardsControllerTest < ActionDispatch::IntegrationTest
assert_equal card.title, json["title"]
assert_equal new_board.id, json["board"]["id"]
end

test "update moves a draft to a board where its creator has another draft" do
draft = users(:kevin).draft_new_card_in(boards(:writebook))
users(:kevin).draft_new_card_in(boards(:private))

put card_board_path(draft), params: { board_id: boards(:private).id }

assert_redirected_to draft
assert_equal boards(:private), draft.reload.board
assert_equal 2, boards(:private).cards.drafted.where(creator: users(:kevin)).count
end

test "update moves a draft to a board without a draft of its creator" do
draft = users(:kevin).draft_new_card_in(boards(:writebook))

put card_board_path(draft), params: { board_id: boards(:private).id }

assert_redirected_to draft
assert_equal boards(:private), draft.reload.board
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require "test_helper"

class RemoveBoardCreatorDraftedIndexFromCardsTest < ActiveSupport::TestCase
INDEX_NAME = "index_cards_on_board_creator_drafted"

test "does nothing when the drafted card index is absent" do
assert_not drafted_card_index?

assert_no_queries_match(/DROP INDEX/i, include_schema: true) { migrate_up }

assert_not drafted_card_index?
end

if ActiveRecord::Base.connection_db_config.adapter == "sqlite3"
test "removes the drafted card index when present" do
add_drafted_card_index

migrate_up

assert_not drafted_card_index?
end

test "the drafted card index rejects moving a draft onto a board with another draft of its creator" do
add_drafted_card_index
draft = kevin_drafts_on_writebook_and_private

error = assert_raises(ActiveRecord::RecordNotUnique) { draft.move_to(boards(:private)) }

assert_match(/cards\.board_id, cards\.creator_id/, error.message)
end

test "a draft moves to a board where its creator already has a draft once the index is gone" do
add_drafted_card_index
draft = kevin_drafts_on_writebook_and_private
migrate_up

draft.move_to(boards(:private))

assert_equal 2, boards(:private).cards.drafted.where(creator: users(:kevin)).count
end
end

private
def add_drafted_card_index
connection.add_index :cards, [ :board_id, :creator_id ], unique: true, where: "status = 'drafted'", name: INDEX_NAME
end

def migrate_up
migration = connection.pool.migration_context.migrations.find { it.name == "RemoveBoardCreatorDraftedIndexFromCards" }
flunk "Migration RemoveBoardCreatorDraftedIndexFromCards is missing" unless migration

ActiveRecord::Migration.suppress_messages { migration.migrate(:up) }
end

def drafted_card_index?
connection.index_name_exists?(:cards, INDEX_NAME)
end

def kevin_drafts_on_writebook_and_private
users(:kevin).draft_new_card_in(boards(:private))
users(:kevin).draft_new_card_in(boards(:writebook))
end

def connection
ActiveRecord::Base.connection
end
end
Loading