| 37526 |
amit |
1 |
package com.spice.profitmandi.web.offercircular;
|
|
|
2 |
|
|
|
3 |
import static org.junit.Assert.assertEquals;
|
|
|
4 |
import static org.junit.Assert.assertFalse;
|
|
|
5 |
import static org.junit.Assert.assertTrue;
|
|
|
6 |
|
|
|
7 |
import java.time.LocalDate;
|
|
|
8 |
import java.util.ArrayList;
|
|
|
9 |
import java.util.Arrays;
|
|
|
10 |
import java.util.LinkedHashMap;
|
|
|
11 |
import java.util.List;
|
|
|
12 |
import java.util.Map;
|
|
|
13 |
import java.util.Set;
|
|
|
14 |
|
|
|
15 |
import org.junit.Test;
|
|
|
16 |
|
|
|
17 |
import com.spice.profitmandi.dao.repository.offers.CircularIngestRepository;
|
|
|
18 |
|
|
|
19 |
/**
|
|
|
20 |
* Guards division scope and label aliasing - the decisions that control whether a
|
|
|
21 |
* circular row is ingested at all, and which division its money is filed under.
|
|
|
22 |
*
|
|
|
23 |
* The alias rules matter more than they look. A label that resolves to the wrong
|
|
|
24 |
* division files an offer against the wrong brand and category; a label that resolves to
|
|
|
25 |
* nothing drops a whole OEM's rows silently. Both failures look like a quiet circular
|
|
|
26 |
* rather than an error.
|
|
|
27 |
*/
|
|
|
28 |
public class ScopeConfigTest {
|
|
|
29 |
|
|
|
30 |
/** Divisions: label, in_scope, category, brand. */
|
|
|
31 |
private static final List<Object[]> DIVISIONS = Arrays.asList(
|
|
|
32 |
new Object[]{"Xiaomi", 1, 10006, "Xiaomi"},
|
|
|
33 |
new Object[]{"Xiaomi CE", 1, 10010, "Xiaomi"},
|
|
|
34 |
new Object[]{"REALME", 1, 10006, "Realme"},
|
|
|
35 |
new Object[]{"HP", 0, 0, ""});
|
|
|
36 |
|
|
|
37 |
private static ScopeConfig load(List<Object[]> aliases) {
|
|
|
38 |
return ScopeConfig.load(new StubRepository(DIVISIONS, aliases));
|
|
|
39 |
}
|
|
|
40 |
|
|
|
41 |
private static ScopeConfig loadWithAlias(String alias, String target) {
|
|
|
42 |
List<Object[]> aliases = new ArrayList<>();
|
|
|
43 |
aliases.add(new Object[]{alias, target});
|
|
|
44 |
return load(aliases);
|
|
|
45 |
}
|
|
|
46 |
|
|
|
47 |
@Test
|
|
|
48 |
public void resolvesAnAliasOntoTheDivisionItNames() {
|
|
|
49 |
ScopeConfig scope = loadWithAlias("XIAOMI CE", "Xiaomi CE");
|
|
|
50 |
|
|
|
51 |
assertEquals("Xiaomi CE", scope.canonicalLabel("XIAOMI CE"));
|
|
|
52 |
|
|
|
53 |
ScopeConfig.Decision decision = scope.evaluate("XIAOMI CE", "Xiaomi Pad 7");
|
|
|
54 |
assertTrue(decision.isKeep());
|
|
|
55 |
// the category must come from the division the alias points at, not from the
|
|
|
56 |
// label's own spelling - this is the whole point of aliasing rather than adding
|
|
|
57 |
// a second division row
|
|
|
58 |
assertEquals(10010, decision.getCategoryId());
|
|
|
59 |
assertEquals("Xiaomi", decision.getBrand());
|
|
|
60 |
}
|
|
|
61 |
|
|
|
62 |
/**
|
|
|
63 |
* Everything that looks a division up by name must use the canonical label. The
|
|
|
64 |
* offer insert resolves the division with WHERE pdf_label = :label, so an alias
|
|
|
65 |
* reaching it would match no row and silently attach the offer's children to the
|
|
|
66 |
* previous offer.
|
|
|
67 |
*/
|
|
|
68 |
@Test
|
|
|
69 |
public void decisionCarriesTheCanonicalLabelNotTheOneThePdfPrinted() {
|
|
|
70 |
ScopeConfig scope = loadWithAlias("Realme", "REALME");
|
|
|
71 |
|
|
|
72 |
ScopeConfig.Decision decision = scope.evaluate("Realme", "Realme 16 Pro");
|
|
|
73 |
assertTrue(decision.isKeep());
|
|
|
74 |
assertEquals("REALME", decision.getCanonicalLabel());
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
/**
|
|
|
78 |
* A label that is a division in its own right must never be shadowed by an alias -
|
|
|
79 |
* it would resolve two ways and which won would depend on row order. The controller
|
|
|
80 |
* refuses to create such a pair; this is the load-time guard for rows inserted by
|
|
|
81 |
* hand or predating that check.
|
|
|
82 |
*/
|
|
|
83 |
@Test
|
|
|
84 |
public void anAliasNeverShadowsARealDivision() {
|
|
|
85 |
ScopeConfig scope = loadWithAlias("Xiaomi CE", "Xiaomi");
|
|
|
86 |
|
|
|
87 |
assertEquals("Xiaomi CE", scope.canonicalLabel("Xiaomi CE"));
|
|
|
88 |
// still tablets, i.e. its own row won - not the alias target's 10006
|
|
|
89 |
assertEquals(10010, scope.evaluate("Xiaomi CE", "Xiaomi Pad 7").getCategoryId());
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
/** An unaliased unknown label stays unknown, and says which label it was. */
|
|
|
93 |
@Test
|
|
|
94 |
public void unknownLabelIsReportedVerbatimAndDropped() {
|
|
|
95 |
ScopeConfig scope = load(new ArrayList<>());
|
|
|
96 |
|
|
|
97 |
ScopeConfig.Decision decision = scope.evaluate("Xiaomi Auto", "Some Car");
|
|
|
98 |
assertFalse(decision.isKeep());
|
|
|
99 |
assertTrue(decision.getReason(), decision.getReason().contains("Xiaomi Auto"));
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
/** Taking a division out of scope still drops it, alias or not. */
|
|
|
103 |
@Test
|
|
|
104 |
public void outOfScopeDivisionIsDroppedEvenWhenReachedByAnAlias() {
|
|
|
105 |
ScopeConfig scope = loadWithAlias("HP Inc", "HP");
|
|
|
106 |
|
|
|
107 |
ScopeConfig.Decision direct = scope.evaluate("HP", "Pavilion 15");
|
|
|
108 |
assertFalse(direct.isKeep());
|
|
|
109 |
|
|
|
110 |
ScopeConfig.Decision viaAlias = scope.evaluate("HP Inc", "Pavilion 15");
|
|
|
111 |
assertFalse("an alias must not smuggle an out-of-scope division back in",
|
|
|
112 |
viaAlias.isKeep());
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
/** A label with no alias is its own canonical form. */
|
|
|
116 |
@Test
|
|
|
117 |
public void labelWithoutAnAliasIsUnchanged() {
|
|
|
118 |
ScopeConfig scope = loadWithAlias("XIAOMI CE", "Xiaomi CE");
|
|
|
119 |
assertEquals("Xiaomi", scope.canonicalLabel("Xiaomi"));
|
|
|
120 |
}
|
|
|
121 |
|
|
|
122 |
/**
|
|
|
123 |
* Only the config reads ScopeConfig needs. Every other method throws, so a change
|
|
|
124 |
* that makes ScopeConfig depend on more of the repository fails loudly here rather
|
|
|
125 |
* than silently widening what config loading touches.
|
|
|
126 |
*/
|
|
|
127 |
private static final class StubRepository implements CircularIngestRepository {
|
|
|
128 |
|
|
|
129 |
private final List<Object[]> divisions;
|
|
|
130 |
private final List<Object[]> aliases;
|
|
|
131 |
|
|
|
132 |
StubRepository(List<Object[]> divisions, List<Object[]> aliases) {
|
|
|
133 |
this.divisions = divisions;
|
|
|
134 |
this.aliases = aliases;
|
|
|
135 |
}
|
|
|
136 |
|
|
|
137 |
@Override public List<Object[]> selectDivisions() { return divisions; }
|
|
|
138 |
@Override public List<Object[]> selectDivisionAliases() { return aliases; }
|
|
|
139 |
@Override public List<Object[]> selectScopeRules() { return new ArrayList<>(); }
|
|
|
140 |
|
|
|
141 |
private static UnsupportedOperationException nope() {
|
|
|
142 |
return new UnsupportedOperationException("not needed to load ScopeConfig");
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
@Override public boolean claimForProcessing(int documentId) { throw nope(); }
|
|
|
146 |
@Override public void markPublished(int documentId, String summary) { throw nope(); }
|
|
|
147 |
@Override public void markFailed(int documentId, String error) { throw nope(); }
|
|
|
148 |
@Override public int reclaimStalledProcessing(int olderThanMinutes) { throw nope(); }
|
|
|
149 |
@Override public List<Integer> selectDraftDocumentIds() { throw nope(); }
|
|
|
150 |
@Override public Map<String, Object> selectDocument(int documentId) { throw nope(); }
|
|
|
151 |
@Override public void deleteDerivedRows(int documentId) { throw nope(); }
|
|
|
152 |
@Override public Map<String, Integer> selectBankAliases() { throw nope(); }
|
|
|
153 |
@Override public List<Object[]> selectCatalogCandidates() { throw nope(); }
|
|
|
154 |
@Override public List<Object[]> selectProductAliases() { throw nope(); }
|
|
|
155 |
@Override public java.util.Set<String> selectOwnChannels() { throw nope(); }
|
|
|
156 |
@Override public int insertOffer(int documentId, String pdfLabel, String benefitTiming, String descriptionRaw, boolean allBanks, LocalDate startDate, LocalDate endDate) { throw nope(); }
|
|
|
157 |
@Override public void insertRawRow(int documentId, int pageNo, int rowNo, String oemLabel, String additionalCashback, String description, String emiTenure, String debitCards, String creditCards, String eligibleProducts, String startDateRaw, String endDateRaw, String parseStatus, Integer offerId) { throw nope(); }
|
|
|
158 |
@Override public int insertBenefit(int offerId, String txnMode, String calcType, java.math.BigDecimal flatAmount, java.math.BigDecimal percent, java.math.BigDecimal maxAmount) { throw nope(); }
|
|
|
159 |
@Override public int insertTenure(int offerId, int tenureMonths, String emiScheme) { throw nope(); }
|
|
|
160 |
@Override public int insertBank(int offerId, int bankId, String cardType, String inclusion) { throw nope(); }
|
|
|
161 |
@Override public void insertProduct(int offerId, String rawText, String matchLevel, Integer catalogId, Integer superCatalogId, String matchStatus, java.math.BigDecimal matchScore) { throw nope(); }
|
|
|
162 |
@Override public void insertChannelScope(int offerId, String inclusion, String channelType, String channelRef, String rawText) { throw nope(); }
|
|
|
163 |
@Override public void insertCondition(int documentId, Integer pageNo, String conditionType, Integer bankId, Integer tenureMonths, String noteText, String rawText) { throw nope(); }
|
|
|
164 |
}
|
|
|
165 |
}
|