Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/Predictor.Tests.Integration/BasePredictionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,14 @@ protected async Task<HttpStatusCode> GetResponseStatusCode(PredictionRequest req
protected static PredictionRequest CreateBasicRequest(int months = 3, decimal initialBudget = 0m) => new(
PredictionMonths: months,
InitialBudget: initialBudget,
OutputCurrency: "USD",
StartPredictionMonth: new MonthDate(1, 2025),
Incomes: [],
Expenses: []);

protected static PaymentItem CreateIncome(string name, decimal value, int month = 1, int year = 2025,
Frequency frequency = Frequency.OneTime, MonthDate? endDate = null) =>
new(name, value, new MonthDate(month, year), frequency, endDate);
protected static PaymentItem CreateIncome(string name, decimal value, string currency, int month = 1, int year = 2025,
Frequency frequency = Frequency.OneTime, MonthDate? endDate = null) => new(name, value, new MonthDate(month, year), currency, frequency, endDate);

protected static PaymentItem CreateExpense(string name, decimal value, int month = 1, int year = 2025,
Frequency frequency = Frequency.OneTime, MonthDate? endDate = null) =>
new(name, value, new MonthDate(month, year), frequency, endDate);
protected static PaymentItem CreateExpense(string name, decimal value, string currency, int month = 1, int year = 2025,
Frequency frequency = Frequency.OneTime, MonthDate? endDate = null) => new(name, value, new MonthDate(month, year), currency, frequency, endDate);
}
59 changes: 49 additions & 10 deletions src/Predictor.Tests.Integration/BudgetCalculationTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FluentAssertions;
using Predictor.Web.Models;
using System.Net;

namespace Predictor.Tests.Integration;

Expand All @@ -20,8 +21,8 @@ public async Task Prediction_ShouldAccumulateBudgetCorrectly(
// Arrange
var request = CreateBasicRequest(months, initialBudget) with
{
Incomes = [CreateIncome("Income", income, frequency: Frequency.Monthly)],
Expenses = [CreateExpense("Expense", expense, frequency: Frequency.Monthly)]
Incomes = [CreateIncome("Income", income, "USD", frequency: Frequency.Monthly)],
Expenses = [CreateExpense("Expense", expense, "USD", frequency: Frequency.Monthly)]
};

// Act
Expand All @@ -47,12 +48,12 @@ public async Task Prediction_WithMultipleSources_ShouldSumCorrectly()
var request = CreateBasicRequest(1) with
{
Incomes = [
CreateIncome("Salary", 3000m),
CreateIncome("Bonus", 2000m)
CreateIncome("Salary", 3000m, "USD"),
CreateIncome("Bonus", 2000m, "USD")
],
Expenses = [
CreateExpense("Rent", 1200m),
CreateExpense("Food", 800m)
CreateExpense("Rent", 1200m, "USD"),
CreateExpense("Food", 800m, "USD")
]
};

Expand All @@ -71,7 +72,7 @@ public async Task Prediction_WithOnlyIncomes_ShouldIncreaseBalance()
// Arrange
var request = CreateBasicRequest(2, 1000m) with
{
Incomes = [CreateIncome("Salary", 2000m, frequency: Frequency.Monthly)],
Incomes = [CreateIncome("Salary", 2000m, "USD", frequency: Frequency.Monthly)],
Expenses = []
};

Expand All @@ -92,7 +93,7 @@ public async Task Prediction_WithOnlyExpenses_ShouldDecreaseBalance()
var request = CreateBasicRequest(2, 5000m) with
{
Incomes = [],
Expenses = [CreateExpense("Rent", 1500m, frequency: Frequency.Monthly)]
Expenses = [CreateExpense("Rent", 1500m, "USD", frequency: Frequency.Monthly)]
};

// Act
Expand All @@ -111,8 +112,8 @@ public async Task Prediction_WithZeroInitialBudget_ShouldCalculateCorrectly()
// Arrange
var request = CreateBasicRequest(1, 0m) with
{
Incomes = [CreateIncome("Income", 1000m)],
Expenses = [CreateExpense("Expense", 300m)]
Incomes = [CreateIncome("Income", 1000m, "USD")],
Expenses = [CreateExpense("Expense", 300m, "USD")]
};

// Act
Expand All @@ -122,4 +123,42 @@ public async Task Prediction_WithZeroInitialBudget_ShouldCalculateCorrectly()
_ = result.Months[0].Balance.Should().Be(700m);
_ = result.Months[0].BudgetAfter.Should().Be(700m);
}

[Test]
public async Task Prediction_WithDifferentInputAndOutputCurrencies_ShouldReturnOk()
{
// Arrange
var request = CreateBasicRequest() with
{
OutputCurrency = "USD",
Incomes = [CreateIncome("Salary", 1000m, "AUD")], // different from output
Expenses = [CreateExpense("Rent", 500m, "CAD")]
};

// Act
var status = await this.GetResponseStatusCode(request);

// Assert
_ = status.Should().Be(HttpStatusCode.OK);
}
[Test]
public async Task Prediction_WithCurrencyConversion_ShouldConvertCorrectly()
{
// Arrange
var request = CreateBasicRequest() with
{
OutputCurrency = "AUD",
Incomes = [CreateIncome("Salary", 1000m, "USD")]
};

// Act
var result = await this.GetPredictionResult(request);

// Assert
result.Months.Should().NotBeNullOrEmpty();

var incomeInUsd = result.Months[0].Income;

incomeInUsd.Should().BeGreaterThan(1000m); // Conversion from USD to AUD should increase value
}
}
74 changes: 37 additions & 37 deletions src/Predictor.Tests.Integration/BusinessScenariosTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ public async Task Prediction_SavingForHouseDownPayment_ShouldCalculateTimeframe(
var targetAmount = 50_000m;
var request = CreateBasicRequest(40, 15_000m) with
{
Incomes = [CreateIncome("Monthly Salary", 7_000m, frequency: Frequency.Monthly)],
Incomes = [CreateIncome("Monthly Salary", 7_000m, "USD", frequency: Frequency.Monthly)],
Expenses = [
CreateExpense("Living Expenses", 4_000m, frequency: Frequency.Monthly), CreateExpense("Savings Goal", 2_000m, frequency: Frequency.Monthly) ]
CreateExpense("Living Expenses", 4_000m, "USD", frequency: Frequency.Monthly), CreateExpense("Savings Goal", 2_000m, "USD", frequency: Frequency.Monthly) ]
};

// Act
Expand All @@ -40,10 +40,10 @@ public async Task Prediction_PayingOffStudentLoan_ShouldShowDebtFreedom()
// Arrange - Student loan paid off in 18 months
var request = CreateBasicRequest(24, 2_000m) with
{
Incomes = [CreateIncome("Job Income", 4_500m, frequency: Frequency.Monthly)],
Incomes = [CreateIncome("Job Income", 4_500m, "USD", frequency: Frequency.Monthly)],
Expenses = [
CreateExpense("Living Costs", 3_000m, frequency: Frequency.Monthly),
CreateExpense("Student Loan", 800m, frequency: Frequency.Monthly,
CreateExpense("Living Costs", 3_000m, "USD", frequency: Frequency.Monthly),
CreateExpense("Student Loan", 800m, "USD", frequency: Frequency.Monthly,
endDate: new MonthDate(6, 2026)) ]
};

Expand All @@ -68,13 +68,13 @@ public async Task Prediction_RetirementPlanning_ShouldShowLongTermGrowth()
var request = CreateBasicRequest(60, 50_000m) with
{
Incomes = [
CreateIncome("Salary", 8_000m, frequency: Frequency.Monthly),
CreateIncome("Annual Bonus", 10_000m, frequency: Frequency.Annually)
CreateIncome("Salary", 8_000m, "USD", frequency: Frequency.Monthly),
CreateIncome("Annual Bonus", 10_000m, "USD", frequency: Frequency.Annually)
],
Expenses = [
CreateExpense("Living Expenses", 5_500m, frequency: Frequency.Monthly),
CreateExpense("401k Contribution", 1_200m, frequency: Frequency.Monthly),
CreateExpense("IRA Contribution", 500m, frequency: Frequency.Monthly)
CreateExpense("Living Expenses", 5_500m, "USD", frequency: Frequency.Monthly),
CreateExpense("401k Contribution", 1_200m, "USD", frequency: Frequency.Monthly),
CreateExpense("IRA Contribution", 500m, "USD", frequency: Frequency.Monthly)
]
};

Expand All @@ -101,10 +101,10 @@ public async Task Prediction_SeasonalBusinessIncome_ShouldHandleIrregularity()
var request = CreateBasicRequest(12, 15_000m) with
{
Incomes = [
CreateIncome("Summer Revenue", 20_000m, month: 6, frequency: Frequency.OneTime), CreateIncome("Summer Revenue", 25_000m, month: 7, frequency: Frequency.OneTime), CreateIncome("Summer Revenue", 18_000m, month: 8, frequency: Frequency.OneTime), CreateIncome("Holiday Revenue", 12_000m, month: 11, frequency: Frequency.OneTime), CreateIncome("Holiday Revenue", 15_000m, month: 12, frequency: Frequency.OneTime), CreateIncome("Base Income", 3_000m, frequency: Frequency.Monthly) ],
CreateIncome("Summer Revenue", 20_000m, "USD", month: 6, frequency: Frequency.OneTime), CreateIncome("Summer Revenue", 25_000m, "USD", month: 7, frequency: Frequency.OneTime), CreateIncome("Summer Revenue", 18_000m, "USD", month: 8, frequency: Frequency.OneTime), CreateIncome("Holiday Revenue", 12_000m, "USD", month: 11, frequency: Frequency.OneTime), CreateIncome("Holiday Revenue", 15_000m, "USD", month: 12, frequency: Frequency.OneTime), CreateIncome("Base Income", 3_000m, "USD", frequency: Frequency.Monthly) ],
Expenses = [
CreateExpense("Fixed Costs", 4_000m, frequency: Frequency.Monthly),
CreateExpense("Summer Marketing", 2_000m, month: 5, frequency: Frequency.OneTime), CreateExpense("Holiday Inventory", 3_000m, month: 10, frequency: Frequency.OneTime) ]
CreateExpense("Fixed Costs", 4_000m, "USD", frequency: Frequency.Monthly),
CreateExpense("Summer Marketing", 2_000m, "USD", month: 5, frequency: Frequency.OneTime), CreateExpense("Holiday Inventory", 3_000m, "USD", month: 10, frequency: Frequency.OneTime) ]
};

// Act
Expand All @@ -126,13 +126,13 @@ public async Task Prediction_FreelancerWithIrregularWork_ShouldManageCashFlow()
var request = CreateBasicRequest(12, 8_000m) with
{
Incomes = [
CreateIncome("Project A Payment", 8_000m, month: 2, frequency: Frequency.OneTime),
CreateIncome("Project B Payment", 12_000m, month: 5, frequency: Frequency.OneTime),
CreateIncome("Project C Payment", 6_000m, month: 8, frequency: Frequency.OneTime),
CreateIncome("Project D Payment", 15_000m, month: 11, frequency: Frequency.OneTime),
CreateIncome("Retainer Client", 2_500m, frequency: Frequency.Monthly) ],
CreateIncome("Project A Payment", 8_000m, "USD", month: 2, frequency: Frequency.OneTime),
CreateIncome("Project B Payment", 12_000m, "USD", month: 5, frequency: Frequency.OneTime),
CreateIncome("Project C Payment", 6_000m, "USD", month: 8, frequency: Frequency.OneTime),
CreateIncome("Project D Payment", 15_000m, "USD", month: 11, frequency: Frequency.OneTime),
CreateIncome("Retainer Client", 2_500m, "USD", frequency: Frequency.Monthly) ],
Expenses = [
CreateExpense("Living Expenses", 3_000m, frequency: Frequency.Monthly), CreateExpense("Business Expenses", 500m, frequency: Frequency.Monthly), CreateExpense("Quarterly Taxes", 2_000m, frequency: Frequency.Quarterly) ]
CreateExpense("Living Expenses", 3_000m, "USD", frequency: Frequency.Monthly), CreateExpense("Business Expenses", 500m, "USD", frequency: Frequency.Monthly), CreateExpense("Quarterly Taxes", 2_000m, "USD", frequency: Frequency.Quarterly) ]
};

// Act
Expand All @@ -156,18 +156,18 @@ public async Task Prediction_FamilyBudgetWithChildExpenses_ShouldAccountForGrowi
var request = CreateBasicRequest(36, 15_000m) with
{
Incomes = [
CreateIncome("Parent 1 Salary", 6_500m, frequency: Frequency.Monthly),
CreateIncome("Parent 2 Salary", 4_800m, frequency: Frequency.Monthly),
CreateIncome("Tax Refund", 3_500m, month: 4, frequency: Frequency.Annually) ],
CreateIncome("Parent 1 Salary", 6_500m, "USD", frequency: Frequency.Monthly),
CreateIncome("Parent 2 Salary", 4_800m, "USD", frequency: Frequency.Monthly),
CreateIncome("Tax Refund", 3_500m, "USD", month: 4, frequency: Frequency.Annually) ],
Expenses = [
CreateExpense("Mortgage", 2_800m, frequency: Frequency.Monthly),
CreateExpense("Utilities & Home", 600m, frequency: Frequency.Monthly),
CreateExpense("Food & Groceries", 1_200m, frequency: Frequency.Monthly),
CreateExpense("Childcare", 1_800m, frequency: Frequency.Monthly,
endDate: new MonthDate(6, 2027)), CreateExpense("School Supplies", 800m, month: 8, frequency: Frequency.Annually),
CreateExpense("Medical/Dental", 400m, frequency: Frequency.Quarterly),
CreateExpense("College Savings", 1_000m, frequency: Frequency.Monthly),
CreateExpense("Activities/Sports", 300m, month: 12, frequency: Frequency.Monthly) ]
CreateExpense("Mortgage", 2_800m, "USD",frequency: Frequency.Monthly),
CreateExpense("Utilities & Home", 600m, "USD", frequency: Frequency.Monthly),
CreateExpense("Food & Groceries", 1_200m, "USD", frequency: Frequency.Monthly),
CreateExpense("Childcare", 1_800m, "USD",frequency: Frequency.Monthly,
endDate: new MonthDate(6, 2027)), CreateExpense("School Supplies", 800m, "USD", month: 8, frequency: Frequency.Annually),
CreateExpense("Medical/Dental", 400m, "USD", frequency: Frequency.Quarterly),
CreateExpense("College Savings", 1_000m, "USD", frequency: Frequency.Monthly),
CreateExpense("Activities/Sports", 300m, "USD", month: 12, frequency: Frequency.Monthly) ]
};

// Act
Expand Down Expand Up @@ -198,17 +198,17 @@ public async Task Prediction_StartupFounderScenario_ShouldSurviveInitialLosses()
var request = CreateBasicRequest(18, 50_000m) with
{
Incomes = [
CreateIncome("First Revenue", 3_000m, month: 7, frequency: Frequency.Monthly,
CreateIncome("First Revenue", 3_000m, "USD", month: 7, frequency: Frequency.Monthly,
endDate: new MonthDate(12, 2025)),
CreateIncome("Growing Revenue", 10_000m, month: 1, year: 2026, frequency: Frequency.Monthly)
CreateIncome("Growing Revenue", 10_000m, "USD", month: 1, year: 2026, frequency: Frequency.Monthly)
],
Expenses = [
CreateExpense("Development Costs", 3_000m, frequency: Frequency.Monthly,
CreateExpense("Development Costs", 3_000m, "USD", frequency: Frequency.Monthly,
endDate: new MonthDate(6, 2025)),
CreateExpense("Office Setup", 8_000m, month: 1, frequency: Frequency.OneTime),
CreateExpense("Legal/Registration", 3_000m, month: 2, frequency: Frequency.OneTime),
CreateExpense("Minimal Living", 2_000m, frequency: Frequency.Monthly),
CreateExpense("Business Operations", 1_000m, month: 7, frequency: Frequency.Monthly)
CreateExpense("Office Setup", 8_000m, "USD", month: 1, frequency: Frequency.OneTime),
CreateExpense("Legal/Registration", 3_000m, "USD", month: 2, frequency: Frequency.OneTime),
CreateExpense("Minimal Living", 2_000m, "USD", frequency: Frequency.Monthly),
CreateExpense("Business Operations", 1_000m, "USD", month: 7, frequency: Frequency.Monthly)
]
};

Expand Down
Loading