mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 03:54:08 +00:00
Implement Reporting tab (#276)
* First reporting version * Fixes for all tabs * Transactions table * Budget section re-design * FIX exports Fix transactions table aggregation * Add support for google sheets Remove pdf and xlsx for now * Multiple fixes - Trends & Insights now follows top filter - Transactions Breakdown removed filters, implemented sort by amount. - The entire section follows top filters. - Export to CSV adds per month breakdown * Linter and tests * Fix amounts - Correctly handle amounts across the views and controller. - Pass proper values to do calculation on, and not loose precision * Update Gemfile.lock * Add support for api-key on reports Also fix custom date filter * Review fixes * Move budget status calculations out of the view. * fix ensures that quarterly reports end at the quarter boundary * Fix bugdet days remaining Fix raw css style * Fix test * Implement google sheets properly with hotwire * Improve UX on period comparison * FIX csv export for non API key auth
This commit is contained in:
196
test/controllers/reports_controller_test.rb
Normal file
196
test/controllers/reports_controller_test.rb
Normal file
@@ -0,0 +1,196 @@
|
||||
require "test_helper"
|
||||
|
||||
class ReportsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
sign_in @user = users(:family_admin)
|
||||
@family = @user.family
|
||||
end
|
||||
|
||||
test "index renders successfully" do
|
||||
get reports_path
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "index with monthly period" do
|
||||
get reports_path(period_type: :monthly)
|
||||
assert_response :ok
|
||||
assert_select "h1", text: I18n.t("reports.index.title")
|
||||
end
|
||||
|
||||
test "index with quarterly period" do
|
||||
get reports_path(period_type: :quarterly)
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "index with ytd period" do
|
||||
get reports_path(period_type: :ytd)
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "index with custom period and date range" do
|
||||
get reports_path(
|
||||
period_type: :custom,
|
||||
start_date: 1.month.ago.to_date.to_s,
|
||||
end_date: Date.current.to_s
|
||||
)
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "index with last 6 months period" do
|
||||
get reports_path(period_type: :last_6_months)
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "index shows empty state when no transactions" do
|
||||
# Delete all transactions for the family by deleting from accounts
|
||||
@family.accounts.each { |account| account.entries.destroy_all }
|
||||
|
||||
get reports_path
|
||||
assert_response :ok
|
||||
assert_select "h3", text: I18n.t("reports.empty_state.title")
|
||||
end
|
||||
|
||||
test "index with budget performance for current month" do
|
||||
# Create a budget for current month
|
||||
budget = Budget.find_or_bootstrap(@family, start_date: Date.current.beginning_of_month)
|
||||
category = @family.categories.expenses.first
|
||||
|
||||
# Fail fast if test setup is incomplete
|
||||
assert_not_nil category, "Test setup failed: no expense category found for family"
|
||||
assert_not_nil budget, "Test setup failed: budget could not be created or found"
|
||||
|
||||
# Find or create budget category to avoid duplicate errors
|
||||
budget_category = budget.budget_categories.find_or_initialize_by(category: category)
|
||||
budget_category.budgeted_spending = Money.new(50000, @family.currency)
|
||||
budget_category.save!
|
||||
|
||||
get reports_path(period_type: :monthly)
|
||||
assert_response :ok
|
||||
end
|
||||
|
||||
test "index calculates summary metrics correctly" do
|
||||
get reports_path(period_type: :monthly)
|
||||
assert_response :ok
|
||||
assert_select "h3", text: I18n.t("reports.summary.total_income")
|
||||
assert_select "h3", text: I18n.t("reports.summary.total_expenses")
|
||||
assert_select "h3", text: I18n.t("reports.summary.net_savings")
|
||||
end
|
||||
|
||||
test "index builds comparison data" do
|
||||
get reports_path(period_type: :monthly)
|
||||
assert_response :ok
|
||||
assert_select "h2", text: I18n.t("reports.comparison.title")
|
||||
assert_select "h3", text: I18n.t("reports.comparison.income")
|
||||
assert_select "h3", text: I18n.t("reports.comparison.expenses")
|
||||
end
|
||||
|
||||
test "index builds trends data" do
|
||||
get reports_path(period_type: :monthly)
|
||||
assert_response :ok
|
||||
assert_select "h2", text: I18n.t("reports.trends.title")
|
||||
assert_select "th", text: I18n.t("reports.trends.month")
|
||||
end
|
||||
|
||||
test "index handles invalid date parameters gracefully" do
|
||||
get reports_path(
|
||||
period_type: :custom,
|
||||
start_date: "invalid-date",
|
||||
end_date: "also-invalid"
|
||||
)
|
||||
assert_response :ok # Should not crash, uses defaults
|
||||
end
|
||||
|
||||
test "spending patterns returns data when expense transactions exist" do
|
||||
# Create expense category
|
||||
expense_category = @family.categories.create!(
|
||||
name: "Test Groceries",
|
||||
classification: "expense"
|
||||
)
|
||||
|
||||
# Create account
|
||||
account = @family.accounts.first
|
||||
|
||||
# Create expense transaction on a weekday (Monday)
|
||||
weekday_date = Date.current.beginning_of_month + 2.days
|
||||
weekday_date = weekday_date.next_occurring(:monday)
|
||||
|
||||
entry = account.entries.create!(
|
||||
name: "Grocery shopping",
|
||||
date: weekday_date,
|
||||
amount: -50.00,
|
||||
currency: "USD",
|
||||
entryable: Transaction.new(
|
||||
category: expense_category,
|
||||
kind: "standard"
|
||||
)
|
||||
)
|
||||
|
||||
# Create expense transaction on a weekend (Saturday)
|
||||
weekend_date = weekday_date.next_occurring(:saturday)
|
||||
|
||||
weekend_entry = account.entries.create!(
|
||||
name: "Weekend shopping",
|
||||
date: weekend_date,
|
||||
amount: -75.00,
|
||||
currency: "USD",
|
||||
entryable: Transaction.new(
|
||||
category: expense_category,
|
||||
kind: "standard"
|
||||
)
|
||||
)
|
||||
|
||||
get reports_path(period_type: :monthly)
|
||||
assert_response :ok
|
||||
|
||||
# Verify spending patterns shows data (not the "no data" message)
|
||||
assert_select ".text-center.py-8.text-tertiary", { text: /No spending data/, count: 0 }, "Should not show 'No spending data' message when transactions exist"
|
||||
end
|
||||
|
||||
test "export transactions with API key authentication" do
|
||||
# Use an active API key with read permissions
|
||||
api_key = api_keys(:active_key)
|
||||
|
||||
# Make sure the API key has the correct source
|
||||
api_key.update!(source: "web") unless api_key.source == "web"
|
||||
|
||||
get export_transactions_reports_path(
|
||||
format: :csv,
|
||||
period_type: :ytd,
|
||||
start_date: Date.current.beginning_of_year,
|
||||
end_date: Date.current,
|
||||
api_key: api_key.plain_key
|
||||
)
|
||||
|
||||
assert_response :ok
|
||||
assert_equal "text/csv", @response.media_type
|
||||
assert_match /Category/, @response.body
|
||||
end
|
||||
|
||||
test "export transactions with invalid API key" do
|
||||
get export_transactions_reports_path(
|
||||
format: :csv,
|
||||
period_type: :ytd,
|
||||
api_key: "invalid_key"
|
||||
)
|
||||
|
||||
assert_response :unauthorized
|
||||
assert_match /Invalid or expired API key/, @response.body
|
||||
end
|
||||
|
||||
test "export transactions without API key uses session auth" do
|
||||
# Should use normal session-based authentication
|
||||
# The setup already signs in @user = users(:family_admin)
|
||||
assert_not_nil @user, "User should be set in test setup"
|
||||
assert_not_nil @family, "Family should be set in test setup"
|
||||
|
||||
get export_transactions_reports_path(
|
||||
format: :csv,
|
||||
period_type: :ytd,
|
||||
start_date: Date.current.beginning_of_year,
|
||||
end_date: Date.current
|
||||
)
|
||||
|
||||
assert_response :ok, "Export should work with session auth. Response: #{@response.body}"
|
||||
assert_equal "text/csv", @response.media_type
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user