diff --git a/.circleci/config.yml b/.circleci/config.yml index 4f672380d..7fed0ba83 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,12 +55,42 @@ jobs: rustc --version cargo --version + - restore_cache: + keys: + - v1-cargo-{{ checksum "ext/widget_renderer/Cargo.lock" }} + - v1-cargo- + - run: name: Build widget renderer (Rust) command: | source $HOME/.cargo/env cargo build --release --manifest-path ext/widget_renderer/Cargo.toml + - run: + name: Verify Rust native library linkage + command: | + set -euo pipefail + LIB=ext/widget_renderer/target/release/libwidget_renderer.so + if [ -f "$LIB" ]; then + echo "Found built rust library; verifying linkage..." + if ldd "$LIB" 2>&1 | grep -q "not found"; then + echo "ERROR: Rust library has unresolved dependencies (ldd shows 'not found')." + ldd "$LIB" || true + exit 1 + else + echo "Rust library linkage looks good" + fi + else + echo "No Rust library built - skipping linkage verification" + fi + + - save_cache: + paths: + - ext/widget_renderer/target + - ~/.cargo/registry + - ~/.cargo/git + key: v1-cargo-{{ checksum "ext/widget_renderer/Cargo.lock" }} + # Download and cache dependencies - restore_cache: keys: diff --git a/app/models/cx_collection.rb b/app/models/cx_collection.rb index 36c270631..3781c5c5b 100644 --- a/app/models/cx_collection.rb +++ b/app/models/cx_collection.rb @@ -87,7 +87,10 @@ def duplicate!(new_user:) end def self.to_csv - collections = all.includes(:organization, :service_provider, :service, :user).references(:organization).order(:fiscal_year, :quarter, 'organizations.name') + collections = all + .includes(:organization, :service, :user, :cx_collection_details, service_provider: :organization) + .references(:organization) + .order(:fiscal_year, :quarter, 'organizations.name') attributes = %i[ id @@ -118,7 +121,7 @@ def self.to_csv csv << attributes collections.each do |collection| - csv << attributes = [ + csv << [ collection.id, collection.name, collection.organization_id, diff --git a/app/models/user.rb b/app/models/user.rb index 683d4c505..9fce23b96 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,7 +19,9 @@ class User < ApplicationRecord def cx_collections user_org = organization - user_parent_org = user_org&.parent + return CxCollection.none if user_org.nil? + + user_parent_org = user_org.parent CxCollection.where(cx_collections: { organization_id: [user_org.id, user_parent_org&.id].compact }) end diff --git a/db/migrate/20251210192727_add_indexes_to_cx_collections.rb b/db/migrate/20251210192727_add_indexes_to_cx_collections.rb new file mode 100644 index 000000000..faec71f0d --- /dev/null +++ b/db/migrate/20251210192727_add_indexes_to_cx_collections.rb @@ -0,0 +1,16 @@ +class AddIndexesToCxCollections < ActiveRecord::Migration[8.0] + def change + # cx_collections table - missing all FK indexes + add_index :cx_collections, :organization_id + add_index :cx_collections, :user_id + add_index :cx_collections, :service_provider_id + add_index :cx_collections, :service_id + + # cx_collection_details table - missing FK index + add_index :cx_collection_details, :cx_collection_id + + # cx_responses table - missing FK indexes + add_index :cx_responses, :cx_collection_detail_id + add_index :cx_responses, :cx_collection_detail_upload_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 2017dc8bd..275383302 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_07_17_034402) do +ActiveRecord::Schema[8.0].define(version: 2025_12_10_192727) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -97,6 +97,7 @@ t.text "trust_question_text" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["cx_collection_id"], name: "index_cx_collection_details_on_cx_collection_id" end create_table "cx_collections", force: :cascade do |t| @@ -124,6 +125,10 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.datetime "submitted_at" + t.index ["organization_id"], name: "index_cx_collections_on_organization_id" + t.index ["service_id"], name: "index_cx_collections_on_service_id" + t.index ["service_provider_id"], name: "index_cx_collections_on_service_provider_id" + t.index ["user_id"], name: "index_cx_collections_on_user_id" end create_table "cx_responses", force: :cascade do |t| @@ -149,6 +154,8 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "external_id" + t.index ["cx_collection_detail_id"], name: "index_cx_responses_on_cx_collection_detail_id" + t.index ["cx_collection_detail_upload_id"], name: "index_cx_responses_on_cx_collection_detail_upload_id" end create_table "digital_product_versions", force: :cascade do |t| diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0193f7923..49b8f6bc1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -165,6 +165,44 @@ end end + describe "#cx_collections" do + let(:user_with_org) { FactoryBot.create(:user, organization: organization) } + let(:service_provider) { FactoryBot.create(:service_provider, organization: organization) } + let(:service) { FactoryBot.create(:service, organization: organization, service_provider: service_provider) } + + context "when user has no organization" do + it "returns an empty collection" do + user_without_org = User.new(email: "test@example.gov") + user_without_org.organization = nil + result = user_without_org.cx_collections + expect(result).to eq(CxCollection.none) + expect(result.count).to eq(0) + end + end + + context "when user has an organization" do + it "returns cx_collections for the user's organization" do + cx_collection = FactoryBot.create(:cx_collection, organization: organization, service: service, service_provider: service_provider, user: user_with_org) + result = user_with_org.cx_collections + expect(result).to include(cx_collection) + end + end + + context "when user's organization has a parent organization" do + let(:parent_org) { FactoryBot.create(:organization, name: "Parent Org", domain: "parent.gov", abbreviation: "PARENT") } + let(:child_org) { FactoryBot.create(:organization, name: "Child Org", domain: "child.gov", abbreviation: "CHILD", parent: parent_org) } + let(:child_user) { FactoryBot.create(:user, organization: child_org) } + let(:parent_service_provider) { FactoryBot.create(:service_provider, organization: parent_org) } + let(:parent_service) { FactoryBot.create(:service, organization: parent_org, service_provider: parent_service_provider) } + + it "includes cx_collections from the parent organization" do + parent_cx_collection = FactoryBot.create(:cx_collection, organization: parent_org, service: parent_service, service_provider: parent_service_provider, user: child_user) + result = child_user.cx_collections + expect(result).to include(parent_cx_collection) + end + end + end + describe "#ensure_organization" do before do @org2 = Organization.create(name: "Subdomain Example", domain: "sub.example.gov", abbreviation: "SUB")