| 37579 |
amit |
1 |
package com.spice.profitmandi.web.offercircular;
|
|
|
2 |
|
|
|
3 |
import static org.junit.Assert.assertEquals;
|
|
|
4 |
import static org.junit.Assert.assertNotEquals;
|
|
|
5 |
import static org.junit.Assert.assertTrue;
|
|
|
6 |
|
|
|
7 |
import java.time.LocalDate;
|
|
|
8 |
|
|
|
9 |
import org.junit.Test;
|
|
|
10 |
|
|
|
11 |
import com.spice.profitmandi.web.offercircular.WebOfferSyncService.Line;
|
|
|
12 |
import com.spice.profitmandi.web.offercircular.WebOfferSyncService.ModelOffer;
|
|
|
13 |
|
|
|
14 |
/**
|
|
|
15 |
* Guards the consolidated badge - the one offer line a partner reads on a product card.
|
|
|
16 |
*
|
|
|
17 |
* Before consolidation a model carried one badge per circular row that named it; Edge 60
|
|
|
18 |
* Pro 12+256 had five, and the partner had to reconcile them. These cases are taken from
|
|
|
19 |
* the Sep'26 circular, because the failures that matter here are all real ones: a bank
|
|
|
20 |
* that does not offer a scheme, a tenure that belongs to a different bank's offer, an
|
|
|
21 |
* amount that stops earlier than the badge does.
|
|
|
22 |
*
|
|
|
23 |
* Constructed with no collaborators - the rendering must not need Spring or a database.
|
|
|
24 |
*/
|
|
|
25 |
public class ModelOfferTest {
|
|
|
26 |
|
|
|
27 |
private final WebOfferSyncService service = new WebOfferSyncService(null, null);
|
|
|
28 |
|
|
|
29 |
private static final LocalDate SEP_1 = LocalDate.of(2026, 9, 1);
|
|
|
30 |
private static final LocalDate SEP_16 = LocalDate.of(2026, 9, 16);
|
|
|
31 |
private static final LocalDate SEP_20 = LocalDate.of(2026, 9, 20);
|
|
|
32 |
private static final LocalDate SEP_26 = LocalDate.of(2026, 9, 26);
|
|
|
33 |
|
|
|
34 |
private static Line cashback(String mode, String amount, String banks,
|
|
|
35 |
String tenures, LocalDate end) {
|
|
|
36 |
Line line = new Line();
|
|
|
37 |
line.txnMode = mode;
|
|
|
38 |
line.calcType = "FLAT";
|
|
|
39 |
line.flatAmount = new java.math.BigDecimal(amount);
|
|
|
40 |
line.timing = "INSTANT";
|
|
|
41 |
line.banks = banks;
|
|
|
42 |
line.tenures = tenures;
|
|
|
43 |
line.start = SEP_1;
|
|
|
44 |
line.end = end;
|
|
|
45 |
return line;
|
|
|
46 |
}
|
|
|
47 |
|
|
|
48 |
private static Line scheme(String tenures, String banks) {
|
|
|
49 |
Line line = new Line();
|
|
|
50 |
line.timing = "INSTANT";
|
|
|
51 |
line.banks = banks;
|
|
|
52 |
line.tenures = tenures;
|
|
|
53 |
line.start = SEP_1;
|
|
|
54 |
line.end = SEP_20;
|
|
|
55 |
return line;
|
|
|
56 |
}
|
|
|
57 |
|
|
|
58 |
private static ModelOffer model(String brand, Line... lines) {
|
|
|
59 |
ModelOffer offer = new ModelOffer();
|
|
|
60 |
offer.brand = brand;
|
|
|
61 |
for (Line line : lines) {
|
|
|
62 |
offer.add(line);
|
|
|
63 |
}
|
|
|
64 |
return offer;
|
|
|
65 |
}
|
|
|
66 |
|
|
|
67 |
// ------------------------------------------------------------------ Edge 60 Pro
|
|
|
68 |
|
|
|
69 |
/**
|
|
|
70 |
* Motorola Edge 60 Pro (12GB 256GB), Sep'26 page 9 - five badges before, one now.
|
|
|
71 |
*
|
|
|
72 |
* The banks differ on every line, so none can be hoisted: the EMI cashback is
|
|
|
73 |
* IDFC/Kotak, no-cost EMI is ICICI on 3 and 6 months but SBI on 3 only, and low-cost
|
|
|
74 |
* is SBI. Stating one bank list for the badge would offer every bank every term.
|
|
|
75 |
*/
|
|
|
76 |
@Test
|
|
|
77 |
public void consolidatesFiveBadgesIntoOneWithBanksPerLine() {
|
|
|
78 |
ModelOffer offer = model("Motorola",
|
|
|
79 |
cashback("CC_EMI", "2500", "IDFC First Bank, Kotak Mahindra Bank", null, SEP_20),
|
|
|
80 |
cashback("CC_FULL_SWIPE", "2000", "IDFC First Bank, State Bank of India", null, SEP_20),
|
|
|
81 |
scheme("NCE 3, 6 months", "ICICI Bank"),
|
|
|
82 |
scheme("NCE 3 month", "State Bank of India"),
|
|
|
83 |
scheme("LCE 6, 9, 12, 18, 24 months", "State Bank of India"));
|
|
|
84 |
|
|
|
85 |
String title = service.modelTitle(offer);
|
|
|
86 |
assertEquals("SEPTEMBER 2026 Motorola Cashback Offer-2,500 On Credit Card EMI"
|
|
|
87 |
+ " & 2,000 On Credit Card Full Swipe", title);
|
|
|
88 |
|
|
|
89 |
String detail = service.modelDetail(offer);
|
|
|
90 |
assertTrue(detail, detail.contains("Rs.2,500 on Credit Card EMI with IDFC First Bank"));
|
|
|
91 |
assertTrue(detail, detail.contains("NCE 3, 6 months with ICICI Bank"));
|
|
|
92 |
assertTrue(detail, detail.contains("NCE 3 month with State Bank of India"));
|
|
|
93 |
// no single trailing bank list, because no two lines share one
|
|
|
94 |
assertTrue(detail, !detail.contains("Banks: "));
|
|
|
95 |
}
|
|
|
96 |
|
|
|
97 |
// ------------------------------------------------------------------ bank hoisting
|
|
|
98 |
|
|
|
99 |
/** When every line shares one bank set it is stated once, not on each line. */
|
|
|
100 |
@Test
|
|
|
101 |
public void hoistsTheBankListWhenEveryLineSharesIt() {
|
|
|
102 |
ModelOffer offer = model("Samsung",
|
|
|
103 |
cashback("CC_FULL_SWIPE", "3000", "HDFC Bank, ICICI Bank", null, SEP_20),
|
|
|
104 |
cashback("CC_EMI", "3000", "HDFC Bank, ICICI Bank", "LCE 3, 6 months", SEP_20));
|
|
|
105 |
|
|
|
106 |
String detail = service.modelDetail(offer);
|
|
|
107 |
assertTrue(detail, detail.endsWith("Banks: HDFC Bank, ICICI Bank"));
|
|
|
108 |
assertTrue(detail, !detail.contains("with HDFC Bank"));
|
|
|
109 |
}
|
|
|
110 |
|
|
|
111 |
// ------------------------------------------------------------------ "Upto"
|
|
|
112 |
|
|
|
113 |
/**
|
|
|
114 |
* "Upto" only where a mode carries more than one value - which is what happens when
|
|
|
115 |
* banks pay differently for the same thing. A single value is stated plainly, or the
|
|
|
116 |
* badge would understate a firm offer.
|
|
|
117 |
*/
|
|
|
118 |
@Test
|
|
|
119 |
public void saysUptoOnlyWhenAModeHasSeveralValues() {
|
|
|
120 |
assertTrue(service.modelTitle(model("Apple",
|
|
|
121 |
cashback("CC_EMI", "6000", "Axis Bank", null, SEP_16),
|
|
|
122 |
cashback("CC_EMI", "4000", "Axis Bank", null, SEP_26)))
|
|
|
123 |
.contains("Upto 6,000 On Credit Card EMI"));
|
|
|
124 |
|
|
|
125 |
String single = service.modelTitle(model("Apple",
|
|
|
126 |
cashback("CC_EMI", "6000", "Axis Bank", null, SEP_26)));
|
|
|
127 |
assertTrue(single, single.contains("6,000 On Credit Card EMI"));
|
|
|
128 |
assertTrue(single, !single.contains("Upto"));
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
// ------------------------------------------------------------------ windows
|
|
|
132 |
|
|
|
133 |
/**
|
|
|
134 |
* Apple 1025780, Sep'26: Rs.6,000 to the 16th, then Rs.4,000 to the 26th - the same
|
|
|
135 |
* banks and the same mode, stepping down.
|
|
|
136 |
*
|
|
|
137 |
* The badge spans 1-26 Sep, so the earlier amount MUST carry its own end date or the
|
|
|
138 |
* badge advertises Rs.6,000 for ten days it is not available. The later amount runs to
|
|
|
139 |
* the badge's own end and says nothing, which keeps the common case clean.
|
|
|
140 |
*/
|
|
|
141 |
@Test
|
|
|
142 |
public void anAmountThatEndsEarlyStatesItsOwnValidity() {
|
|
|
143 |
ModelOffer offer = model("Apple",
|
|
|
144 |
cashback("CC_EMI", "6000", "Axis Bank", null, SEP_16),
|
|
|
145 |
cashback("CC_EMI", "4000", "Axis Bank", null, SEP_26));
|
|
|
146 |
|
|
|
147 |
assertEquals(SEP_1, offer.start);
|
|
|
148 |
assertEquals(SEP_26, offer.end);
|
|
|
149 |
|
|
|
150 |
String detail = service.modelDetail(offer);
|
|
|
151 |
assertTrue(detail, detail.contains("Rs.6,000 on Credit Card EMI (till 16 Sep)"));
|
|
|
152 |
assertTrue(detail, detail.contains("Rs.4,000 on Credit Card EMI"));
|
|
|
153 |
assertTrue(detail, !detail.contains("Rs.4,000 on Credit Card EMI (till"));
|
|
|
154 |
}
|
|
|
155 |
|
|
|
156 |
// ------------------------------------------------------------------ ordering
|
|
|
157 |
|
|
|
158 |
/** Highest value first, so the number a partner is looking for leads. */
|
|
|
159 |
@Test
|
|
|
160 |
public void statesHigherValuesFirst() {
|
|
|
161 |
String detail = service.modelDetail(model("Motorola",
|
|
|
162 |
cashback("CC_EMI", "1500", "SBI", null, SEP_20),
|
|
|
163 |
cashback("CC_EMI", "2500", "SBI", null, SEP_20)));
|
|
|
164 |
assertTrue(detail, detail.indexOf("Rs.2,500") < detail.indexOf("Rs.1,500"));
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
// ------------------------------------------------------------------ scheme-only
|
|
|
168 |
|
|
|
169 |
/** A model with no cashback at all is titled by its scheme, never "Cashback Offer". */
|
|
|
170 |
@Test
|
|
|
171 |
public void aSchemeOnlyModelIsTitledByItsScheme() {
|
|
|
172 |
String title = service.modelTitle(model("Motorola",
|
|
|
173 |
scheme("NCE 3, 6 months", "State Bank of India")));
|
|
|
174 |
assertEquals("SEPTEMBER 2026 Motorola NCE 3, 6 months", title);
|
|
|
175 |
assertTrue(title, !title.contains("Cashback Offer"));
|
|
|
176 |
assertTrue(title, !title.contains("NA"));
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
// ------------------------------------------------------------------ tenure origin
|
|
|
180 |
|
|
|
181 |
/**
|
|
|
182 |
* A tenure belongs to the offer that granted it, never to the model.
|
|
|
183 |
*
|
|
|
184 |
* Edge 60 Pro's EMI cashback is IDFC/Kotak with NO tenure of its own, while its
|
|
|
185 |
* schemes are ICICI's and SBI's. Attaching the model's tenures to that cashback would
|
|
|
186 |
* promise no-cost EMI on a bank that never offered it.
|
|
|
187 |
*/
|
|
|
188 |
@Test
|
|
|
189 |
public void doesNotAttachAnotherOffersTenuresToACashback() {
|
|
|
190 |
String detail = service.modelDetail(model("Motorola",
|
|
|
191 |
cashback("CC_EMI", "2500", "IDFC First Bank", null, SEP_20),
|
|
|
192 |
scheme("NCE 3, 6 months", "ICICI Bank")));
|
|
|
193 |
assertTrue(detail, detail.contains("Rs.2,500 on Credit Card EMI with IDFC First Bank"));
|
|
|
194 |
assertTrue(detail, !detail.contains("Credit Card EMI (NCE"));
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
// ------------------------------------------------------------------ length
|
|
|
198 |
|
|
|
199 |
/** detailedText is VARCHAR(1024); a model with many lines must not overflow it. */
|
|
|
200 |
@Test
|
|
|
201 |
public void neverExceedsTheColumnWidth() {
|
|
|
202 |
ModelOffer offer = new ModelOffer();
|
|
|
203 |
offer.brand = "Samsung";
|
|
|
204 |
for (int i = 0; i < 40; i++) {
|
|
|
205 |
offer.add(cashback("CC_EMI", String.valueOf(1000 + i),
|
|
|
206 |
"HDFC Bank, ICICI Bank, State Bank of India, Axis Bank " + i, null, SEP_20));
|
|
|
207 |
}
|
|
|
208 |
assertTrue(service.modelDetail(offer).length() <= 1024);
|
|
|
209 |
assertTrue(service.modelTitle(offer).length() <= 256);
|
|
|
210 |
}
|
| 37580 |
amit |
211 |
|
|
|
212 |
// ------------------------------------------------------ scheme without months
|
|
|
213 |
|
|
|
214 |
/**
|
|
|
215 |
* Edge 60 Pro's Rs.2,500, Sep'26 page 9 row 21: the tenure cell is a bare "CIB" -
|
|
|
216 |
* the circular names the EMI type and gives no months.
|
|
|
217 |
*
|
|
|
218 |
* 52 offers are of this shape and every one carries a cashback, so dropping the cell
|
|
|
219 |
* left the badge silent about the EMI it applies to. The type is stated because the
|
|
|
220 |
* circular stated it; the months are NOT invented, because it did not give them.
|
|
|
221 |
*/
|
|
|
222 |
@Test
|
|
|
223 |
public void statesASchemeThatWasGivenWithoutMonths() {
|
|
|
224 |
Line line = cashback("CC_EMI", "2500", "IDFC First Bank", null, SEP_20);
|
|
|
225 |
line.schemeOnly = "CIB";
|
|
|
226 |
String detail = service.modelDetail(model("Motorola", line));
|
|
|
227 |
assertTrue(detail, detail.contains("Rs.2,500 on Credit Card EMI (CIB)"));
|
|
|
228 |
// no fabricated tenure
|
|
|
229 |
assertTrue(detail, !detail.matches(".*CIB \\d.*"));
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
/** A scheme code is never attached to a mode that has no EMI at all. */
|
|
|
233 |
@Test
|
|
|
234 |
public void doesNotPutASchemeOnAFullSwipeLine() {
|
|
|
235 |
Line line = cashback("CC_FULL_SWIPE", "2000", "IDFC First Bank", null, SEP_20);
|
|
|
236 |
line.schemeOnly = "CIB";
|
|
|
237 |
String detail = service.modelDetail(model("Motorola", line));
|
|
|
238 |
assertTrue(detail, detail.contains("Rs.2,000 on Credit Card Full Swipe"));
|
|
|
239 |
assertTrue(detail, !detail.contains("(CIB)"));
|
|
|
240 |
}
|
|
|
241 |
|
|
|
242 |
/** Real months always win over a bare code. */
|
|
|
243 |
@Test
|
|
|
244 |
public void prefersRealTenuresOverTheBareCode() {
|
|
|
245 |
Line line = cashback("CC_EMI", "3000", "HDFC Bank", "LCE 3, 6 months", SEP_20);
|
|
|
246 |
line.schemeOnly = "CIB";
|
|
|
247 |
String detail = service.modelDetail(model("Samsung", line));
|
|
|
248 |
assertTrue(detail, detail.contains("(LCE 3, 6 months)"));
|
|
|
249 |
assertTrue(detail, !detail.contains("(CIB)"));
|
|
|
250 |
}
|
| 37579 |
amit |
251 |
}
|