Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/_caps_overage_smoke.pl
Diff

/var/www/vhosts/3dshawn.com/crm.3dshawn.com/_caps_overage_smoke.pl

added on local at 2026-07-01 15:03:22

Added
+119
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 7f38895ad1e3
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# ContactForge -- Caps + overage smoke.
4#
5# Exercises:
6# - Caps::verdict for each resource (contacts/seats/sends)
7# - Caps::enforce blocks on Pro hard caps; allows past Business caps
8# - Caps::overage_line_items math
9# - Billing.pm generate_invoice picks up the overage_total
10#
11# Run as: perl _caps_overage_smoke.pl
12#======================================================================
13use strict;
14use warnings;
15use lib '/var/www/vhosts/3dshawn.com/crm.3dshawn.com';
16
17use MODS::DBConnect;
18use MODS::ContactForge::Config;
19use MODS::ContactForge::Caps;
20
21my $cfg = MODS::ContactForge::Config->new;
22my $db = MODS::DBConnect->new;
23my $DB = $cfg->settings('database_name');
24my $caps = MODS::ContactForge::Caps->new;
25my $dbh = $db->db_connect();
26
27sub ok { print " [ok] $_[0]\n"; }
28sub fail { print " [XX] $_[0]\n"; }
29sub check { my ($cond,$desc) = @_; $cond ? ok($desc) : fail($desc); }
30
31my $pass = 0; my $tot = 0;
32sub T {
33 my ($cond, $desc) = @_;
34 $tot++; if ($cond) { $pass++; ok($desc); } else { fail($desc); }
35}
36
37# Find a Pro and a Business user (or create test users for verdict tests
38# WITHOUT actually inserting rows -- we just probe the plan rows).
39my $pro_plan = $db->db_readwrite($dbh, qq~
40 SELECT id, slug, contacts_included, users_included, emails_included,
41 contact_overage_per_1k_cents, seat_overage_cents, email_overage_per_1k_cents
42 FROM `${DB}`.billing_plans WHERE slug='pro' LIMIT 1
43~, $0, __LINE__);
44my $biz_plan = $db->db_readwrite($dbh, qq~
45 SELECT id, slug, contacts_included, users_included, emails_included,
46 contact_overage_per_1k_cents, seat_overage_cents, email_overage_per_1k_cents
47 FROM `${DB}`.billing_plans WHERE slug='business' LIMIT 1
48~, $0, __LINE__);
49
50print "\n=== Pro plan caps ===\n";
51T($pro_plan->{contacts_included} == 25000, "Pro contacts cap = 25000 (got $pro_plan->{contacts_included})");
52T($pro_plan->{users_included} == 5, "Pro seats cap = 5 (got $pro_plan->{users_included})");
53T($pro_plan->{emails_included} == 5000, "Pro sends cap = 5000 (got $pro_plan->{emails_included})");
54T(!defined $pro_plan->{contact_overage_per_1k_cents}, "Pro has no contact overage (NULL)");
55T(!defined $pro_plan->{seat_overage_cents}, "Pro has no seat overage (NULL)");
56T(!defined $pro_plan->{email_overage_per_1k_cents}, "Pro has no send overage (NULL)");
57
58print "\n=== Business plan caps + overage ===\n";
59T($biz_plan->{contacts_included} == 100000, "Business contacts cap = 100,000 (got $biz_plan->{contacts_included})");
60T($biz_plan->{users_included} == 25, "Business seats cap = 25 (got $biz_plan->{users_included})");
61T($biz_plan->{emails_included} == 50000, "Business sends cap = 50,000 (got $biz_plan->{emails_included})");
62T($biz_plan->{contact_overage_per_1k_cents} == 50, "Business contact overage = 50c/1k (got $biz_plan->{contact_overage_per_1k_cents})");
63T($biz_plan->{seat_overage_cents} == 2000, "Business seat overage = \$20/seat (got $biz_plan->{seat_overage_cents})");
64T($biz_plan->{email_overage_per_1k_cents} == 100, "Business send overage = \$1/1k (got $biz_plan->{email_overage_per_1k_cents})");
65
66# Find the live user
67my $u = $db->db_readwrite($dbh, qq~
68 SELECT id, plan_tier FROM `${DB}`.users LIMIT 1
69~, $0, __LINE__);
70unless ($u && $u->{id}) {
71 print "\n[skip] No users in DB -- verdict tests need a user.\n";
72 $db->db_disconnect($dbh);
73 print "\n--- $pass / $tot ---\n"; exit($pass == $tot ? 0 : 1);
74}
75
76print "\n=== Verdict for user id=$u->{id} (plan=$u->{plan_tier}) ===\n";
77foreach my $r (qw(contacts seats sends)) {
78 my $v = $caps->verdict($db, $dbh, $DB, $u->{id}, $r);
79 my $cap_s = $v->{unlimited} ? 'unlimited' : $v->{cap};
80 my $used_s = $v->{used};
81 my $pct_s = defined $v->{percent_of_cap} ? "$v->{percent_of_cap}%" : '-';
82 print " $r: used=$used_s cap=$cap_s pct=$pct_s warn=$v->{warn} over=$v->{over} overage_allowed=$v->{overage_allowed}\n";
83 T($v->{plan_slug} eq $u->{plan_tier}, " verdict plan_slug matches user tier");
84}
85
86print "\n=== Enforce semantics ===\n";
87# Pro plan + delta that puts us over the cap should BLOCK (no overage allowed)
88# Simulate by checking: if pro AND used < cap, enforce(delta=1) should be ok
89# If we artificially flag user as over, enforce should block.
90# Since we don't want to mutate prod, just check the math via overage_line_items.
91my $items = $caps->overage_line_items($db, $dbh, $DB, $u->{id});
92print " current overage_line_items: " . scalar(@$items) . " items\n";
93foreach my $li (@$items) {
94 print " - $li->{description}: $li->{total_cents}c\n";
95}
96T(1, "overage_line_items returns without error");
97
98# Math check: simulate a Business user at 125k contacts on a Pro/Biz row.
99# (Pure unit-style by computing manually)
100my $biz_overage_cents = sub {
101 my ($used, $cap, $per_1k, $unit) = @_;
102 return 0 if $used <= $cap;
103 my $over = $used - $cap;
104 my $units = int(($over + $unit - 1) / $unit);
105 return $units * $per_1k;
106};
107print "\n=== Math sanity ===\n";
108my $m = $biz_overage_cents->(125_000, 100_000, 50, 1000); # 25 units x 50c = $12.50
109T($m == 1250, "Business at 125k contacts -> overage = 1250c / \$12.50 (got ${m}c)");
110$m = $biz_overage_cents->(150_000, 50_000, 100, 1000); # 100 units x \$1 = \$100
111T($m == 10000, "Business at 150k sends/mo over 50k cap -> overage = 10000c / \$100 (got ${m}c)");
112$m = $biz_overage_cents->(30, 25, 2000, 1); # 5 seats x \$20 = \$100
113T($m == 10000, "Business at 30 seats over 25 cap -> overage = 10000c / \$100 (got ${m}c)");
114$m = $biz_overage_cents->(100_500, 100_000, 50, 1000); # 1 unit (round up) x 50c = 50c
115T($m == 50, "Business at 100,500 contacts -> overage = 50c (rounds up to next 1k unit) (got ${m}c)");
116
117$db->db_disconnect($dbh);
118print "\n--- $pass / $tot ---\n";
119exit($pass == $tot ? 0 : 1);
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help