Subversion Repositories SmartDukaan

Rev

Rev 37526 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
 
37528 amit 145
        @Override public int expireOlderDocumentOffers(int documentId) { throw nope(); }
146
        @Override public int expireIfSuperseded(int documentId) { throw nope(); }
37526 amit 147
        @Override public boolean claimForProcessing(int documentId) { throw nope(); }
148
        @Override public void markPublished(int documentId, String summary) { throw nope(); }
149
        @Override public void markFailed(int documentId, String error) { throw nope(); }
150
        @Override public int reclaimStalledProcessing(int olderThanMinutes) { throw nope(); }
151
        @Override public List<Integer> selectDraftDocumentIds() { throw nope(); }
152
        @Override public Map<String, Object> selectDocument(int documentId) { throw nope(); }
153
        @Override public void deleteDerivedRows(int documentId) { throw nope(); }
154
        @Override public Map<String, Integer> selectBankAliases() { throw nope(); }
155
        @Override public List<Object[]> selectCatalogCandidates() { throw nope(); }
156
        @Override public List<Object[]> selectProductAliases() { throw nope(); }
157
        @Override public java.util.Set<String> selectOwnChannels() { throw nope(); }
158
        @Override public int insertOffer(int documentId, String pdfLabel, String benefitTiming, String descriptionRaw, boolean allBanks, LocalDate startDate, LocalDate endDate) { throw nope(); }
159
        @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(); }
160
        @Override public int insertBenefit(int offerId, String txnMode, String calcType, java.math.BigDecimal flatAmount, java.math.BigDecimal percent, java.math.BigDecimal maxAmount) { throw nope(); }
161
        @Override public int insertTenure(int offerId, int tenureMonths, String emiScheme) { throw nope(); }
162
        @Override public int insertBank(int offerId, int bankId, String cardType, String inclusion) { throw nope(); }
163
        @Override public void insertProduct(int offerId, String rawText, String matchLevel, Integer catalogId, Integer superCatalogId, String matchStatus, java.math.BigDecimal matchScore) { throw nope(); }
164
        @Override public void insertChannelScope(int offerId, String inclusion, String channelType, String channelRef, String rawText) { throw nope(); }
165
        @Override public void insertCondition(int documentId, Integer pageNo, String conditionType, Integer bankId, Integer tenureMonths, String noteText, String rawText) { throw nope(); }
166
    }
167
}