Files
discourse-hledger/spec/lib/hledger/report_builder_spec.rb
T

181 lines
6.2 KiB
Ruby

# frozen_string_literal: true
class FakeHledgerRunner
FIXTURES = Rails.root.join("plugins/hledger/spec/fixtures")
FILES = {
"accounts" => "accounts.json",
"balance_sheet" => "balance_sheet.json",
"income_statement" => "income_statement.json",
"equity" => "equity.json",
"transactions" => "transactions.json",
}.freeze
attr_reader :version
def initialize(version: "1.52.1")
@version = version
end
def available?
version.present?
end
def execute(argv, stdin_data: nil)
Hledger::Runner::Result.new(
success: true,
stdout: File.read(FIXTURES.join(FILES.fetch(detect(argv)))),
stderr: "",
exit_status: 0,
timed_out: false,
)
end
def detect(argv)
return "equity" if argv.include?("^equity:")
return "balance_sheet" if argv.include?("bs")
return "income_statement" if argv.include?("is")
return "transactions" if argv.include?("print")
"accounts"
end
end
RSpec.describe Hledger::ReportBuilder do
subject(:builder) { described_class.new(runner: FakeHledgerRunner.new) }
describe "accounts" do
it "returns the full account tree with aggregated totals" do
report = builder.build("accounts", journal: "journal")
expect(report["report_type"]).to eq("accounts")
rows = report["sections"].first["rows"]
expect(rows.map { |row| row["account"] }).to start_with(
"assets",
"assets:bank",
"assets:bank:checking",
)
expect(rows.map { |row| row["label"] }).to include("Assets", "Bank", "Checking")
assets = rows.find { |row| row["account"] == "assets" }
expect(assets["depth"]).to eq(0)
expect(assets["root"]).to be(true)
expect(assets["amounts"].first["display"]).to eq("1029.50 EUR")
equity = rows.find { |row| row["account"] == "equity" }
expect(equity["amounts"].first["display"]).to eq("(1000.00 EUR)")
expect(equity["amounts"].first["quantity"]).to eq("-1000.0")
checking = rows.find { |row| row["account"] == "assets:bank:checking" }
expect(checking["depth"]).to eq(2)
expect(checking["amounts"].first["display"]).to eq("1029.50 EUR")
expect(report["sections"].first["total"].first["display"]).to eq("0.00 EUR")
end
end
describe "balance sheet" do
it "returns sections, totals and net" do
report = builder.build("balance_sheet", journal: "journal")
expect(report["sections"].map { |s| s["title"] }).to eq(%w[Assets Liabilities])
assets = report["sections"].first
expect(assets["rows"].map { |row| row["label"] }).to eq(%w[Bank Checking])
expect(assets["rows"].map { |row| row["depth"] }).to eq([0, 1])
expect(assets["rows"].map { |row| row["root"] }).to eq([false, false])
expect(assets["rows"].last["amounts"].first["display"]).to eq("1029.50 EUR")
expect(assets["total"].first["display"]).to eq("1029.50 EUR")
expect(report["net"].first["display"]).to eq("1029.50 EUR")
end
end
describe "income statement" do
it "returns sections, totals and net" do
report = builder.build("income_statement", journal: "journal")
expect(report["sections"].map { |s| s["title"] }).to eq(%w[Revenues Expenses])
expect(report["sections"].first["rows"].map { |row| row["label"] }).to eq(["Donations"])
expect(report["sections"].first["rows"].map { |row| row["depth"] }).to eq([0])
expect(report["sections"].last["rows"].map { |row| row["label"] }).to eq(%w[Hosting Server])
expect(report["sections"].last["rows"].map { |row| row["depth"] }).to eq([0, 1])
expect(report["net"].first["display"]).to eq("29.50 EUR")
end
end
describe "equity" do
it "shows the equity account tree with each row's share" do
report = builder.build("equity", journal: "journal")
group = report["groups"].first
expect(group["commodity"]).to eq("EUR")
expect(group["total"]["quantity"]).to eq("1000.0")
expect(group["rows"].map { |row| row["label"] }).to eq(%w[Alice Bob])
expect(group["rows"].map { |row| row["depth"] }).to eq([0, 0])
expect(group["rows"].map { |row| row["percentage"] }).to eq(%w[60.00 40.00])
expect(group["rows"].first["amounts"].first["display"]).to eq("600.00 EUR")
end
end
describe "transactions" do
it "returns each transaction with its postings" do
report = builder.build("transactions", journal: "journal")
transactions = report["transactions"]
expect(transactions.map { |txn| txn["date"] }).to eq(%w[2024-01-01 2024-02-10 2024-03-15])
expect(transactions.map { |txn| txn["description"] }).to eq(
["Opening balances", "Hosting", "Donation"],
)
opening = transactions.first
expect(opening["postings"].map { |posting| posting["account"] }).to eq(
%w[assets:bank:checking equity:alice equity:bob],
)
expect(opening["postings"].first["amounts"].first["display"]).to eq("1000.00 EUR")
end
end
describe "period" do
it "serializes the requested period" do
report =
builder.build(
"income_statement",
journal: "journal",
begin_date: Date.new(2024, 1, 1),
end_date: Date.new(2024, 3, 15),
)
expect(report["period"]).to eq("begin" => "2024-01-01", "end" => "2024-03-15")
expect(report["meta"]["hledger_version"]).to eq("1.52.1")
end
end
it "raises for unsupported report types" do
expect { builder.build("nope", journal: "journal") }.to raise_error(Hledger::Error)
end
it "raises when hledger is unavailable" do
unavailable = FakeHledgerRunner.new(version: nil)
expect {
described_class.new(runner: unavailable).build("accounts", journal: "x")
}.to raise_error(Hledger::UnavailableError)
end
it "raises when the captured output was truncated" do
runner = FakeHledgerRunner.new
allow(runner).to receive(:execute).and_return(
Hledger::Runner::Result.new(
success: true,
stdout: "",
stderr: "",
exit_status: 0,
timed_out: false,
output_limit_exceeded: true,
),
)
expect { described_class.new(runner: runner).build("accounts", journal: "x") }.to raise_error(
Hledger::OutputLimitError,
)
end
end