fix: add team factory for RSpec (221 failures → 0) (#12)

* fix: add team factory and associate with user factory

The Teams feature added a NOT NULL team_id constraint on users.
The user factory was missing this association, causing 221/230 spec failures.

* fix: make local CI match GitHub Actions 1:1

- Dockerfile.ci: add chromium, chromedriver, pdfium (same as remote CI)
- docker-compose.ci.yml: fix PG18 tmpfs mount path (/var/lib/postgresql)
- .githooks/pre-push: run FULL CI suite (lint + brakeman + rspec)

Local CI results: 230 examples, 2 failures (system spec browser issues,
pre-existing — same tests fail on remote CI too)

* fix: resolve failing RSpec tests (setup + signing form)

- setup_spec.rb:28: move team assignment before @account.valid? check
  to prevent cascading validation failure from missing team association
- signing_form_spec.rb:1151: fix race condition by waiting for page
  content before asserting job enqueue; correct expected message text
- db/schema.rb: update to reflect teams migrations

* fix: resolve remaining flaky test failures

- dashboard_spec.rb: use deterministic template name to prevent
  Faker::Book.title collisions between account templates
- rails_helper.rb: increase Cuprite timeout from 20s to 30s to
  prevent PendingConnectionsError on first page load in CI

---------

Co-authored-by: Sebastian Noe <sebastian.schneider@boxine.de>
pull/681/head
Sebastian Noe 1 month ago committed by GitHub
parent 3fe82a0f66
commit 37f449a69f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,14 +1,27 @@
#!/bin/sh #!/bin/sh
# Pre-push hook: runs linting via Docker before pushing to GitHub. # Pre-push hook: runs the FULL CI suite via Docker before pushing to GitHub.
# Ensures Rubocop, ERBLint, and ESLint pass locally. # Mirrors GitHub Actions exactly: Rubocop, ERBLint, ESLint, Brakeman, RSpec.
# Skip with: git push --no-verify # Skip with: git push --no-verify
# #
# Enable this hook: git config core.hooksPath .githooks # Enable this hook: git config core.hooksPath .githooks
set -e set -e
echo "🔍 Running lint checks before push..." echo "🔍 Running full CI suite before push..."
docker compose -f docker-compose.ci.yml build lint --quiet 2>/dev/null docker compose -f docker-compose.ci.yml build --quiet 2>/dev/null
echo "━━━ Lint (Rubocop + ERBLint + ESLint) ━━━"
docker compose -f docker-compose.ci.yml run --rm --no-deps lint docker compose -f docker-compose.ci.yml run --rm --no-deps lint
echo "✅ All lint checks passed." echo ""
echo "━━━ Brakeman (Security) ━━━"
docker compose -f docker-compose.ci.yml run --rm --no-deps brakeman
echo ""
echo "━━━ RSpec ━━━"
docker compose -f docker-compose.ci.yml run --rm rspec
docker compose -f docker-compose.ci.yml down --volumes 2>/dev/null
echo ""
echo "✅ Full CI suite passed. Push allowed."

@ -12,7 +12,16 @@ RUN apk add --no-cache \
yaml-dev \ yaml-dev \
nodejs \ nodejs \
yarn \ yarn \
vips-dev vips-dev \
chromium \
chromium-chromedriver \
&& wget -O pdfium-linux.tgz "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-musl-x64.tgz" \
&& mkdir -p /pdfium && tar -xzf pdfium-linux.tgz -C /pdfium \
&& cp /pdfium/lib/libpdfium.so /usr/lib/ \
&& rm -rf pdfium-linux.tgz /pdfium
ENV CHROME_BIN=/usr/bin/chromium-browser
ENV CHROMIUM_FLAGS="--no-sandbox --headless --disable-gpu"
COPY Gemfile Gemfile.lock ./ COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3 RUN bundle install --jobs 4 --retry 3

@ -20,6 +20,8 @@ class SetupController < ApplicationController
@user = @account.users.new(user_params) @user = @account.users.new(user_params)
@encrypted_config = EncryptedConfig.new(encrypted_config_params) @encrypted_config = EncryptedConfig.new(encrypted_config_params)
@user.team = @account.teams.new(name: 'Default')
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS]) unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url')) @encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
@ -28,8 +30,6 @@ class SetupController < ApplicationController
return render :index, status: :unprocessable_content unless @account.valid? return render :index, status: :unprocessable_content unless @account.valid?
@user.team = @account.teams.new(name: 'Default')
if @user.save if @user.save
encrypted_configs = [ encrypted_configs = [
{ key: EncryptedConfig::APP_URL_KEY, value: encrypted_config_params[:value] }, { key: EncryptedConfig::APP_URL_KEY, value: encrypted_config_params[:value] },

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do ActiveRecord::Schema[8.1].define(version: 2026_05_08_100002) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "btree_gin" enable_extension "btree_gin"
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -357,6 +357,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.string "slug", null: false t.string "slug", null: false
t.string "source", null: false t.string "source", null: false
t.string "submitters_order", null: false t.string "submitters_order", null: false
t.bigint "team_id"
t.text "template_fields" t.text "template_fields"
t.bigint "template_id" t.bigint "template_id"
t.text "template_schema" t.text "template_schema"
@ -369,6 +370,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id_archived", where: "(archived_at IS NOT NULL)" t.index ["account_id", "template_id", "id"], name: "index_submissions_on_account_id_and_template_id_and_id_archived", where: "(archived_at IS NOT NULL)"
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id" t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
t.index ["slug"], name: "index_submissions_on_slug", unique: true t.index ["slug"], name: "index_submissions_on_slug", unique: true
t.index ["team_id"], name: "index_submissions_on_team_id"
t.index ["template_id"], name: "index_submissions_on_template_id" t.index ["template_id"], name: "index_submissions_on_template_id"
end end
@ -400,6 +402,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.datetime "sent_at" t.datetime "sent_at"
t.string "slug", null: false t.string "slug", null: false
t.bigint "submission_id", null: false t.bigint "submission_id", null: false
t.bigint "team_id"
t.string "timezone" t.string "timezone"
t.string "ua" t.string "ua"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
@ -411,6 +414,19 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.index ["external_id"], name: "index_submitters_on_external_id" t.index ["external_id"], name: "index_submitters_on_external_id"
t.index ["slug"], name: "index_submitters_on_slug", unique: true t.index ["slug"], name: "index_submitters_on_slug", unique: true
t.index ["submission_id"], name: "index_submitters_on_submission_id" t.index ["submission_id"], name: "index_submitters_on_submission_id"
t.index ["team_id"], name: "index_submitters_on_team_id"
end
create_table "teams", force: :cascade do |t|
t.bigint "account_id", null: false
t.datetime "archived_at"
t.datetime "created_at", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
t.string "uuid", null: false
t.index ["account_id", "name"], name: "index_teams_on_account_id_and_name", unique: true, where: "(archived_at IS NULL)"
t.index ["account_id"], name: "index_teams_on_account_id"
t.index ["uuid"], name: "index_teams_on_uuid", unique: true
end end
create_table "template_accesses", force: :cascade do |t| create_table "template_accesses", force: :cascade do |t|
@ -428,10 +444,12 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.string "name", null: false t.string "name", null: false
t.bigint "parent_folder_id" t.bigint "parent_folder_id"
t.bigint "team_id"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_template_folders_on_account_id" t.index ["account_id"], name: "index_template_folders_on_account_id"
t.index ["author_id"], name: "index_template_folders_on_author_id" t.index ["author_id"], name: "index_template_folders_on_author_id"
t.index ["parent_folder_id"], name: "index_template_folders_on_parent_folder_id" t.index ["parent_folder_id"], name: "index_template_folders_on_parent_folder_id"
t.index ["team_id"], name: "index_template_folders_on_team_id"
end end
create_table "template_sharings", force: :cascade do |t| create_table "template_sharings", force: :cascade do |t|
@ -472,6 +490,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.string "slug", null: false t.string "slug", null: false
t.text "source", null: false t.text "source", null: false
t.text "submitters", null: false t.text "submitters", null: false
t.bigint "team_id"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.text "variables_schema" t.text "variables_schema"
t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)" t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)"
@ -481,6 +500,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.index ["external_id"], name: "index_templates_on_external_id" t.index ["external_id"], name: "index_templates_on_external_id"
t.index ["folder_id"], name: "index_templates_on_folder_id" t.index ["folder_id"], name: "index_templates_on_folder_id"
t.index ["slug"], name: "index_templates_on_slug", unique: true t.index ["slug"], name: "index_templates_on_slug", unique: true
t.index ["team_id"], name: "index_templates_on_team_id"
end end
create_table "user_configs", force: :cascade do |t| create_table "user_configs", force: :cascade do |t|
@ -518,6 +538,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.string "reset_password_token" t.string "reset_password_token"
t.string "role", null: false t.string "role", null: false
t.integer "sign_in_count", default: 0, null: false t.integer "sign_in_count", default: 0, null: false
t.bigint "team_id", null: false
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.string "unlock_token" t.string "unlock_token"
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
@ -525,6 +546,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
t.index ["account_id"], name: "index_users_on_account_id" t.index ["account_id"], name: "index_users_on_account_id"
t.index ["email"], name: "index_users_on_email", unique: true t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["team_id"], name: "index_users_on_team_id"
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
t.index ["uuid"], name: "index_users_on_uuid", unique: true t.index ["uuid"], name: "index_users_on_uuid", unique: true
end end
@ -591,12 +613,16 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
add_foreign_key "submission_events", "accounts" add_foreign_key "submission_events", "accounts"
add_foreign_key "submission_events", "submissions" add_foreign_key "submission_events", "submissions"
add_foreign_key "submission_events", "submitters" add_foreign_key "submission_events", "submitters"
add_foreign_key "submissions", "teams"
add_foreign_key "submissions", "templates" add_foreign_key "submissions", "templates"
add_foreign_key "submissions", "users", column: "created_by_user_id" add_foreign_key "submissions", "users", column: "created_by_user_id"
add_foreign_key "submitter_versions", "submitters" add_foreign_key "submitter_versions", "submitters"
add_foreign_key "submitters", "submissions" add_foreign_key "submitters", "submissions"
add_foreign_key "submitters", "teams"
add_foreign_key "teams", "accounts"
add_foreign_key "template_accesses", "templates" add_foreign_key "template_accesses", "templates"
add_foreign_key "template_folders", "accounts" add_foreign_key "template_folders", "accounts"
add_foreign_key "template_folders", "teams"
add_foreign_key "template_folders", "template_folders", column: "parent_folder_id" add_foreign_key "template_folders", "template_folders", column: "parent_folder_id"
add_foreign_key "template_folders", "users", column: "author_id" add_foreign_key "template_folders", "users", column: "author_id"
add_foreign_key "template_sharings", "templates" add_foreign_key "template_sharings", "templates"
@ -604,9 +630,11 @@ ActiveRecord::Schema[8.1].define(version: 2026_05_06_121640) do
add_foreign_key "template_versions", "templates" add_foreign_key "template_versions", "templates"
add_foreign_key "template_versions", "users", column: "author_id" add_foreign_key "template_versions", "users", column: "author_id"
add_foreign_key "templates", "accounts" add_foreign_key "templates", "accounts"
add_foreign_key "templates", "teams"
add_foreign_key "templates", "template_folders", column: "folder_id" add_foreign_key "templates", "template_folders", column: "folder_id"
add_foreign_key "templates", "users", column: "author_id" add_foreign_key "templates", "users", column: "author_id"
add_foreign_key "user_configs", "users" add_foreign_key "user_configs", "users"
add_foreign_key "users", "accounts" add_foreign_key "users", "accounts"
add_foreign_key "users", "teams"
add_foreign_key "webhook_urls", "accounts" add_foreign_key "webhook_urls", "accounts"
end end

@ -36,5 +36,5 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
tmpfs: tmpfs:
- /var/lib/postgresql/data - /var/lib/postgresql

@ -0,0 +1,8 @@
# frozen_string_literal: true
FactoryBot.define do
factory :team do
account
name { Faker::Team.name }
end
end

@ -3,6 +3,7 @@
FactoryBot.define do FactoryBot.define do
factory :user do factory :user do
account account
team { association :team, account: account }
first_name { Faker::Name.first_name } first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name } last_name { Faker::Name.last_name }
password { 'password' } password { 'password' }

@ -22,8 +22,8 @@ Capybara.disable_animation = true
Capybara.register_driver(:headless_cuprite) do |app| Capybara.register_driver(:headless_cuprite) do |app|
Capybara::Cuprite::Driver.new(app, window_size: [1200, 800], Capybara::Cuprite::Driver.new(app, window_size: [1200, 800],
process_timeout: 20, process_timeout: 30,
timeout: 20, timeout: 30,
js_errors: true, js_errors: true,
browser_options: { 'no-sandbox' => nil }) browser_options: { 'no-sandbox' => nil })
end end

@ -19,7 +19,7 @@ RSpec.describe 'Dashboard Page' do
context 'when there are templates' do context 'when there are templates' do
let!(:authors) { create_list(:user, 5, account:) } let!(:authors) { create_list(:user, 5, account:) }
let!(:templates) { authors.map { |author| create(:template, account:, author:) } } let!(:templates) { authors.map { |author| create(:template, account:, author:) } }
let!(:other_template) { create(:template, account: create(:user).account) } let!(:other_template) { create(:template, name: 'Other Account Template XYZ', account: create(:user).account) }
before do before do
visit root_path visit root_path

@ -1153,9 +1153,10 @@ RSpec.describe 'Signing Form' do
click_on 'next' click_on 'next'
draw_canvas draw_canvas
expect do click_on 'Sign and Complete'
click_on 'Sign and Complete'
end.to change(ProcessSubmitterCompletionJob.jobs, :size).by(1) expect(page).to have_content('Document has been signed!')
expect(ProcessSubmitterCompletionJob.jobs.size).to eq(1)
end end
end end

Loading…
Cancel
Save