fix how estimate range is fetched, /calculate route

This commit is contained in:
aecsocket
2026-08-26 14:36:07 +01:00
parent 97c6ece304
commit 4ff6ebbe6c
20 changed files with 848 additions and 196 deletions
+379
View File
@@ -21,6 +21,8 @@ struct TestBalance {
withdrawn_ytd: Decimal,
pending: Decimal,
dates: std::collections::HashMap<DateTime<Utc>, Decimal>,
estimated_dates: std::collections::HashMap<DateTime<Utc>, Decimal>,
actual_dates: std::collections::HashMap<DateTime<Utc>, Decimal>,
}
async fn get_balance(test_env: &TestEnvironment<ApiV3>) -> TestBalance {
@@ -33,6 +35,383 @@ async fn get_balance(test_env: &TestEnvironment<ApiV3>) -> TestBalance {
test::read_body_json(response).await
}
fn amount_delta(
current: &std::collections::HashMap<DateTime<Utc>, Decimal>,
initial: &std::collections::HashMap<DateTime<Utc>, Decimal>,
date: DateTime<Utc>,
) -> Decimal {
current.get(&date).copied().unwrap_or_default()
- initial.get(&date).copied().unwrap_or_default()
}
async fn insert_succeeded_period(
pool: &sqlx::PgPool,
period: NaiveDate,
run_id: i64,
) {
sqlx::query!(
r#"
INSERT INTO payout_periods (
period,
raw_actual_aditude_revenue_usd,
adjustments
)
VALUES ($1, 100, '[]'::jsonb)
"#,
period,
)
.execute(pool)
.await
.unwrap();
let started_at = Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0).unwrap();
sqlx::query!(
r#"
INSERT INTO payout_runs (
id,
period,
payload,
status,
started_at,
started_by,
execute_at,
processing_started_at,
finished_at
)
VALUES (
$1,
$2,
'{"raw_actual_revenue_usd":100,"adjustments":[]}'::jsonb,
'succeeded',
$3,
$4,
$5,
$5,
$6
)
"#,
run_id,
period,
started_at,
USER_USER_ID_PARSED,
started_at + Duration::minutes(2),
started_at + Duration::minutes(3),
)
.execute(pool)
.await
.unwrap();
}
#[actix_rt::test]
async fn estimate_only_period_uses_estimate_as_pending_revenue() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
let pool = &test_env.db.pool;
let project_id =
parse_base62(&test_env.dummy.project_alpha.project_id).unwrap()
as i64;
let now = Utc.timestamp_opt(Utc::now().timestamp(), 0).unwrap();
let date_available = now - Duration::days(20);
let initial_balance = get_balance(&test_env).await;
sqlx::query!(
r#"
INSERT INTO payout_estimates (
period,
user_id,
mod_id,
amount,
created,
date_available
)
VALUES ($1, $2, $3, 30, $4, $5)
"#,
NaiveDate::from_ymd_opt(2001, 1, 1).unwrap(),
USER_USER_ID_PARSED,
project_id,
now - Duration::days(50),
date_available,
)
.execute(pool)
.await
.unwrap();
let balance = get_balance(&test_env).await;
assert_eq!(balance.available, initial_balance.available);
assert_eq!(
balance.pending,
initial_balance.pending + Decimal::from(30),
);
assert_eq!(
amount_delta(
&balance.dates,
&initial_balance.dates,
date_available,
),
Decimal::from(30),
);
assert_eq!(
amount_delta(
&balance.estimated_dates,
&initial_balance.estimated_dates,
date_available,
),
Decimal::from(30),
);
assert_eq!(
amount_delta(
&balance.actual_dates,
&initial_balance.actual_dates,
date_available,
),
Decimal::ZERO,
);
},
)
.await;
}
#[actix_rt::test]
async fn finalized_only_period_uses_actual_as_pending_revenue() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
let pool = &test_env.db.pool;
let project_id =
parse_base62(&test_env.dummy.project_alpha.project_id).unwrap()
as i64;
let now = Utc.timestamp_opt(Utc::now().timestamp(), 0).unwrap();
let date_available = now + Duration::days(20);
let period = NaiveDate::from_ymd_opt(2001, 2, 1).unwrap();
let initial_balance = get_balance(&test_env).await;
insert_succeeded_period(pool, period, 20_001).await;
sqlx::query!(
r#"
INSERT INTO payouts_values (
user_id,
mod_id,
amount,
created,
date_available,
payout_run_id
)
VALUES ($1, $2, 40, $3, $4, 20001)
"#,
USER_USER_ID_PARSED,
project_id,
now - Duration::days(50),
date_available,
)
.execute(pool)
.await
.unwrap();
let balance = get_balance(&test_env).await;
assert_eq!(balance.available, initial_balance.available);
assert_eq!(
balance.pending,
initial_balance.pending + Decimal::from(40),
);
assert_eq!(
amount_delta(
&balance.dates,
&initial_balance.dates,
date_available,
),
Decimal::from(40),
);
assert_eq!(
amount_delta(
&balance.estimated_dates,
&initial_balance.estimated_dates,
date_available,
),
Decimal::ZERO,
);
assert_eq!(
amount_delta(
&balance.actual_dates,
&initial_balance.actual_dates,
date_available,
),
Decimal::from(40),
);
},
)
.await;
}
#[actix_rt::test]
async fn finalized_period_retains_estimate_but_uses_actual_balance() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
let pool = &test_env.db.pool;
let project_id =
parse_base62(&test_env.dummy.project_alpha.project_id).unwrap()
as i64;
let now = Utc.timestamp_opt(Utc::now().timestamp(), 0).unwrap();
let date_available = now - Duration::days(20);
let period = NaiveDate::from_ymd_opt(2001, 3, 1).unwrap();
let initial_balance = get_balance(&test_env).await;
insert_succeeded_period(pool, period, 20_002).await;
sqlx::query!(
r#"
INSERT INTO payout_estimates (
period,
user_id,
mod_id,
amount,
created,
date_available
)
VALUES ($1, $2, $3, 100, $4, $5)
"#,
period,
USER_USER_ID_PARSED,
project_id,
now - Duration::days(50),
date_available,
)
.execute(pool)
.await
.unwrap();
sqlx::query!(
r#"
INSERT INTO payouts_values (
user_id,
mod_id,
amount,
created,
date_available,
payout_run_id
)
VALUES ($1, $2, 80, $3, $4, 20002)
"#,
USER_USER_ID_PARSED,
project_id,
now - Duration::days(50),
date_available,
)
.execute(pool)
.await
.unwrap();
let balance = get_balance(&test_env).await;
assert_eq!(
balance.available,
initial_balance.available + Decimal::from(80),
);
assert_eq!(balance.pending, initial_balance.pending);
assert_eq!(
amount_delta(
&balance.dates,
&initial_balance.dates,
date_available,
),
Decimal::from(80),
);
assert_eq!(
amount_delta(
&balance.estimated_dates,
&initial_balance.estimated_dates,
date_available,
),
Decimal::from(100),
);
assert_eq!(
amount_delta(
&balance.actual_dates,
&initial_balance.actual_dates,
date_available,
),
Decimal::from(80),
);
},
)
.await;
}
#[actix_rt::test]
async fn legacy_finalized_values_remain_available_and_pending() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
let pool = &test_env.db.pool;
let project_id =
parse_base62(&test_env.dummy.project_alpha.project_id).unwrap()
as i64;
let now = Utc.timestamp_opt(Utc::now().timestamp(), 0).unwrap();
let available_at = now - Duration::days(20);
let pending_at = now + Duration::days(20);
let initial_balance = get_balance(&test_env).await;
sqlx::query!(
r#"
INSERT INTO payouts_values (
user_id,
mod_id,
amount,
created,
date_available
)
VALUES
($1, $2, 70, $3, $4),
($1, $2, 30, $5, $6)
"#,
USER_USER_ID_PARSED,
project_id,
now - Duration::days(50),
available_at,
now - Duration::days(10),
pending_at,
)
.execute(pool)
.await
.unwrap();
let balance = get_balance(&test_env).await;
assert_eq!(
balance.available,
initial_balance.available + Decimal::from(70),
);
assert_eq!(
balance.pending,
initial_balance.pending + Decimal::from(30),
);
for (date, amount) in [
(available_at, Decimal::from(70)),
(pending_at, Decimal::from(30)),
] {
assert_eq!(
amount_delta(&balance.dates, &initial_balance.dates, date,),
amount,
);
assert_eq!(
amount_delta(
&balance.actual_dates,
&initial_balance.actual_dates,
date,
),
amount,
);
assert_eq!(
amount_delta(
&balance.estimated_dates,
&initial_balance.estimated_dates,
date,
),
Decimal::ZERO,
);
}
},
)
.await;
}
#[actix_rt::test]
async fn provisional_estimates_only_affect_pending_balance() {
with_test_environment(