Files
sure/test/models/family_test.rb
BitToby e573896efe fix: locale-dependent category duplication bug (#956)
* fix: locale-dependent category duplication bug

* fix: use family locale for investment contributions category to prevent duplicates and handle legacy data

* Remove v* tag trigger from flutter-build to fix double-runs

publish.yml already calls flutter-build via workflow_call on v* tags,
so the direct push trigger was causing duplicate workflow runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Refactor mobile release asset flow

* fix: category uniqueness and workflow issues

* fix: fix test issue

* fix: solve test issue

* fix: resolve legacy problem

* fix: solve lint test issue

* fix: revert unrelated changes

---------

Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 10:33:51 +01:00

210 lines
7.4 KiB
Ruby

require "test_helper"
class FamilyTest < ActiveSupport::TestCase
include SyncableInterfaceTest
def setup
@syncable = families(:dylan_family)
end
test "investment_contributions_category creates category when missing" do
family = families(:dylan_family)
family.categories.where(name: Category.investment_contributions_name).destroy_all
assert_nil family.categories.find_by(name: Category.investment_contributions_name)
category = family.investment_contributions_category
assert category.persisted?
assert_equal Category.investment_contributions_name, category.name
assert_equal "#0d9488", category.color
assert_equal "expense", category.classification
assert_equal "trending-up", category.lucide_icon
end
test "investment_contributions_category returns existing category" do
family = families(:dylan_family)
existing = family.categories.find_or_create_by!(name: Category.investment_contributions_name) do |c|
c.color = "#0d9488"
c.classification = "expense"
c.lucide_icon = "trending-up"
end
assert_no_difference "Category.count" do
result = family.investment_contributions_category
assert_equal existing, result
end
end
test "investment_contributions_category uses family locale consistently" do
family = families(:dylan_family)
family.update!(locale: "fr")
family.categories.where(name: [ "Investment Contributions", "Contributions aux investissements" ]).destroy_all
# Simulate different request locales (e.g., from Accept-Language header)
# The category should always be created with the family's locale (French)
category_from_english_request = I18n.with_locale(:en) do
family.investment_contributions_category
end
assert_equal "Contributions aux investissements", category_from_english_request.name
# Second request with different locale should find the same category
assert_no_difference "Category.count" do
category_from_dutch_request = I18n.with_locale(:nl) do
family.investment_contributions_category
end
assert_equal category_from_english_request.id, category_from_dutch_request.id
assert_equal "Contributions aux investissements", category_from_dutch_request.name
end
end
test "investment_contributions_category prevents duplicate categories across locales" do
family = families(:dylan_family)
family.update!(locale: "en")
family.categories.where(name: [ "Investment Contributions", "Contributions aux investissements" ]).destroy_all
# Create category under English family locale
english_category = family.investment_contributions_category
assert_equal "Investment Contributions", english_category.name
# Simulate a request with French locale (e.g., from browser Accept-Language)
# Should still return the English category, not create a French one
assert_no_difference "Category.count" do
I18n.with_locale(:fr) do
french_request_category = family.investment_contributions_category
assert_equal english_category.id, french_request_category.id
assert_equal "Investment Contributions", french_request_category.name
end
end
end
test "investment_contributions_category reuses legacy category with wrong locale" do
family = families(:dylan_family)
family.update!(locale: "fr")
family.categories.where(name: [ "Investment Contributions", "Contributions aux investissements" ]).destroy_all
# Simulate legacy: category was created with English name (old bug behavior)
legacy_category = family.categories.create!(
name: "Investment Contributions",
color: "#0d9488",
classification: "expense",
lucide_icon: "trending-up"
)
# Should find and reuse the legacy category, updating its name to French
assert_no_difference "Category.count" do
result = family.investment_contributions_category
assert_equal legacy_category.id, result.id
assert_equal "Contributions aux investissements", result.name
end
end
test "investment_contributions_category merges multiple locale variants" do
family = families(:dylan_family)
family.update!(locale: "en")
family.categories.where(name: [ "Investment Contributions", "Contributions aux investissements" ]).destroy_all
# Simulate legacy: multiple categories created under different locales
english_category = family.categories.create!(
name: "Investment Contributions",
color: "#0d9488",
classification: "expense",
lucide_icon: "trending-up"
)
french_category = family.categories.create!(
name: "Contributions aux investissements",
color: "#0d9488",
classification: "expense",
lucide_icon: "trending-up"
)
# Create transactions pointing to both categories
account = family.accounts.first
txn1 = Transaction.create!(category: english_category)
Entry.create!(
account: account,
entryable: txn1,
amount: 100,
currency: "USD",
date: Date.current,
name: "Test 1"
)
txn2 = Transaction.create!(category: french_category)
Entry.create!(
account: account,
entryable: txn2,
amount: 200,
currency: "USD",
date: Date.current,
name: "Test 2"
)
# Should merge both categories into one, keeping the oldest
assert_difference "Category.count", -1 do
result = family.investment_contributions_category
assert_equal english_category.id, result.id
assert_equal "Investment Contributions", result.name
# Both transactions should now point to the keeper
assert_equal english_category.id, txn1.reload.category_id
assert_equal english_category.id, txn2.reload.category_id
# French category should be deleted
assert_nil Category.find_by(id: french_category.id)
end
end
test "moniker helpers return expected singular and plural labels" do
family = families(:dylan_family)
family.update!(moniker: "Family")
assert_equal "Family", family.moniker_label
assert_equal "Families", family.moniker_label_plural
family.update!(moniker: "Group")
assert_equal "Group", family.moniker_label
assert_equal "Groups", family.moniker_label_plural
end
test "available_merchants includes family merchants without transactions" do
family = families(:dylan_family)
new_merchant = family.merchants.create!(name: "New Test Merchant")
assert_includes family.available_merchants, new_merchant
end
test "upload_document stores provided metadata on family document" do
family = families(:dylan_family)
family.update!(vector_store_id: nil)
adapter = mock("vector_store_adapter")
adapter.expects(:create_store).with(name: "Family #{family.id} Documents").returns(
VectorStore::Response.new(success?: true, data: { id: "vs_test123" }, error: nil)
)
adapter.expects(:upload_file).with(
store_id: "vs_test123",
file_content: "hello",
filename: "notes.txt"
).returns(
VectorStore::Response.new(success?: true, data: { file_id: "file-xyz" }, error: nil)
)
VectorStore::Registry.stubs(:adapter).returns(adapter)
document = family.upload_document(
file_content: "hello",
filename: "notes.txt",
metadata: { "type" => "financial_document" }
)
assert_not_nil document
assert_equal({ "type" => "financial_document" }, document.metadata)
assert_equal "vs_test123", family.reload.vector_store_id
end
end