Subversion Repositories SmartDukaan

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37337 amit 1
package com.spice.profitmandi.web.offercircular;
2
 
3
import static org.junit.Assert.assertEquals;
4
 
5
import java.util.Arrays;
6
import java.util.LinkedHashSet;
7
import java.util.Set;
8
 
9
import org.junit.Test;
10
 
11
/**
12
 * Guards the product-name parsing, which decides which SKU an offer's money lands on.
13
 *
14
 * Every case here corresponds to a real mis-parse found against the Aug'26 circular.
15
 * They all share one root cause worth remembering: **characters and digits belonging to
16
 * the model name being consumed as memory or stripped as punctuation.** If a change
17
 * here breaks a test, the fix is almost certainly to add a word-boundary guard rather
18
 * than to relax the expectation.
19
 */
20
public class ProductNamesTest {
21
 
22
    private static Set<String> memory(String... tokens) {
23
        return new LinkedHashSet<>(Arrays.asList(tokens));
24
    }
25
 
26
    private static void check(String input, Set<String> expectedMemory, String expectedKey) {
27
        assertEquals("memory for " + input, expectedMemory, ProductNames.variantTokens(input));
28
        assertEquals("modelKey for " + input, expectedKey, ProductNames.modelKey(input));
29
    }
30
 
31
    /**
32
     * The rule: memory stated -> variant-specific; memory absent -> all variants.
33
     * So detecting memory correctly is what separates "this exact SKU" from
34
     * "every size of this model", and the OEMs use three different notations.
35
     */
36
    @Test
37
    public void detectsAllThreeMemoryNotations() {
38
        // Motorola: no unit at all. Missed before, which put a Rs.1,000 8+256 offer
39
        // and a Rs.2,000 12+256 offer on the SAME SKU.
40
        check("Edge 60 Pro (8+256)", memory("8X", "256X"), "EDGE60PRO");
41
        check("Edge 60) Pro (12+256", memory("12X", "256X"), "EDGE60PRO");
42
        // vivo: single-letter unit
43
        check("V60(12+256G)", memory("12X", "256X"), "V60");
44
        check("X300 Pro(16+512G)", memory("16X", "512X"), "X300PRO");
45
        // Oppo: explicit unit, and unit on both sides
46
        check("RENO16C 12+256GB", memory("12X", "256X"), "RENO16C");
47
        check("A5X(4GB+128GB)", memory("4X", "128X"), "A5X");
48
    }
49
 
50
    /** A bare 4G/5G is a network marker, never a capacity - but 4 GB of RAM is real. */
51
    @Test
52
    public void doesNotConfuseNetworkMarkerWithCapacity() {
53
        check("Y05(4+64G)", memory("4X", "64X"), "Y05");
54
        check("Reno 16C 5G (12GB 256GB)", memory("12X", "256X"), "RENO16C");
55
        check("Redmi Note 15 Pro 5G", memory(), "REDMINOTE15PRO");
56
    }
57
 
58
    /** Terabyte capacities must stay distinct from gigabyte ones. */
59
    @Test
60
    public void handlesTerabytes() {
61
        check("S26 Ultra (16/1TB)", memory("16X", "1Y"), "S26ULTRA");
62
        check("iPhone 17 Pro (1 TB)", memory("1Y"), "IPHONE17PRO");
63
        check("iPhone 17 Pro (256 GB)", memory("256X"), "IPHONE17PRO");
64
    }
65
 
66
    /**
67
     * A digit glued to letters is part of the name. Without word-boundary guards the
68
     * "5X" inside A5X read as 5 GB and collapsed the key to "A", making Oppo A5X and
69
     * A6X indistinguishable.
70
     */
71
    @Test
72
    public void doesNotEatDigitsOutOfModelNames() {
73
        assertEquals("A5X", ProductNames.modelKey("A5X(4GB+128GB)"));
74
        assertEquals("A6X", ProductNames.modelKey("A6X(6GB+128GB)"));
75
        assertEquals("XFOLD5", ProductNames.modelKey("X Fold5(16+512G)"));
76
    }
77
 
78
    /**
79
     * '+' in a model name is significant. Stripping it as punctuation made
80
     * "Realme 16 Pro" and "Realme 16 Pro+" the same key and cross-matched 6 SKUs
81
     * across 12 offers - 1,188 false collisions from one character.
82
     */
83
    @Test
84
    public void keepsModelNamePlusSignificant() {
85
        assertEquals("16PRO", ProductNames.modelKey("Realme 16 Pro"));
86
        assertEquals("16PROPLUS", ProductNames.modelKey("Realme 16 Pro+"));
87
        assertEquals("16PRO", ProductNames.modelKey("Realme 16 Pro 5G (8GB 256GB)"));
88
        assertEquals("16PROPLUS", ProductNames.modelKey("Realme 16 Pro+ 5G (8GB 256GB)"));
89
        assertEquals("REDMINOTE15PRO", ProductNames.modelKey("Redmi Note 15 Pro 5G"));
90
        assertEquals("REDMINOTE15PROPLUS", ProductNames.modelKey("Redmi Note 15 Pro+ 5G"));
91
    }
92
 
93
    /**
94
     * The circular writes "S25+" while the catalog writes "S25 Plus" - the same phone,
95
     * so they MUST converge. And all three must stay distinct from plain S25.
96
     * "S25+ 5G" once keyed as "S25X" and then "SG" while these guards were incomplete.
97
     */
98
    @Test
99
    public void s25PlusAndS25PlusSpelledOutAreTheSamePhone() {
100
        assertEquals("S25PLUS", ProductNames.modelKey("S25+ (12/256GB)"));
101
        assertEquals("S25PLUS", ProductNames.modelKey("S25 Plus (12GB 256GB)"));
102
        assertEquals("S25PLUS", ProductNames.modelKey("S25+ 5G (12GB 256GB)"));
103
 
104
        assertEquals("S25", ProductNames.modelKey("S25 (12/128 GB)"));
105
        assertEquals("S25ULTRA", ProductNames.modelKey("S25 Ultra (12/512GB)"));
106
        assertEquals("S25FE", ProductNames.modelKey("S25 FE (128GB)"));
107
        assertEquals("S25EDGE", ProductNames.modelKey("S25 Edge (12/256GB)"));
108
    }
109
 
110
    /**
111
     * Spaces are insignificant so the circular's "RENO16C" matches the catalog's
112
     * "Reno 16C 5G"; digits are significant so a one-token difference never matches.
113
     */
114
    @Test
115
    public void spacesInsignificantDigitsSignificant() {
116
        assertEquals(ProductNames.modelKey("RENO16C 12+256GB"),
117
                ProductNames.modelKey("Reno 16C 5G (12GB 256GB)"));
118
        assertEquals("IPHONE17E", ProductNames.modelKey("iPhone 17e"));
119
        assertEquals("IPHONE7", ProductNames.modelKey("iPhone 7"));
120
    }
121
 
122
    /** WIFI/LTE are NOT noise: for tablets they are the only variant discriminator. */
123
    @Test
124
    public void keepsConnectivityDistinctForTablets() {
125
        assertEquals("TABS11ULTRAWIFI",
126
                ProductNames.modelKey("Tab S11 Ultra Wifi (12/256GB)"));
127
        assertEquals("TABS11ULTRALTE",
128
                ProductNames.modelKey("Tab S11 Ultra LTE (12/256GB)"));
129
    }
130
 
131
    /**
132
     * Splitting must never cut through a variant spec - doing so invented a phantom
133
     * product literally called "256GB" - and trailing variant-only fragments inherit
134
     * the preceding model name.
135
     */
136
    @Test
137
    public void splitsMultiProductCellsWithoutCuttingVariants() {
138
        assertEquals(Arrays.asList("Tab S11 Ultra Wifi (12/256GB)",
139
                        "Tab S11 Ultra Wifi (12/512GB)"),
140
                ProductNames.split(
141
                        "Tab S11 Ultra Wifi (12/256GB)/Tab S11 Ultra Wifi (12/512GB)"));
142
 
143
        assertEquals(Arrays.asList("RENO16C 12+256GB", "RENO16C 8+256GB", "RENO16C 8+128GB"),
144
                ProductNames.split("RENO16C 12+256GB, 8+256GB, 8+128GB"));
145
 
146
        // two products run together with no delimiter at all
147
        assertEquals(Arrays.asList("A37 5G (8GB/128GB)", "A37 5G (12GB/256GB)"),
148
                ProductNames.split("A37 5G (8GB/128GB)A37 5G (12GB/256GB)"));
149
    }
150
 
151
    /**
152
     * Two variant groups written back-to-back are two variants of one model. Merged,
153
     * their memory tokens ran together and the offer bound to one arbitrary SKU while
154
     * the other variant silently got nothing - money on the wrong SKU, same failure
155
     * class as the missing-unit bug.
156
     */
157
    @Test
158
    public void splitsAdjacentVariantGroupsIntoSeparateProducts() {
159
        assertEquals(Arrays.asList("Edge 70 Pro (8+256)", "Edge 70 Pro 12+256"),
160
                ProductNames.split("Edge 70 Pro (8+256)(12+256)"));
161
        assertEquals(Arrays.asList("G37 Power (4+64)", "G37 Power 4+128"),
162
                ProductNames.split("G37 Power (4+64)(4+128)"));
163
        assertEquals(Arrays.asList("A6 (6+128GB)", "A6 4+128GB"),
164
                ProductNames.split("A6 (6+128GB)(4+128GB)"));
165
 
166
        // a '/' between the groups is already a split point and must stay one
167
        assertEquals(Arrays.asList("X300 FE(12+512G)", "X300 FE 12+256G"),
168
                ProductNames.split("X300 FE(12+512G)/(12+256G)"));
169
    }
170
 
171
    /**
172
     * A bundled accessory suffix must SURVIVE splitting, so the bundle stays a distinct
173
     * product from the bare phone.
174
     *
175
     * Stripping it was implemented and reverted: it resolved 30 CATALOG_GAP rows, but on
176
     * Aug'26 vivo caps X300 Pro(16+512G) at Rs.10,000 on its own row and Rs.11,000 on the
177
     * "+Extender" row - the difference being the Extender. Merging them lets the bundle's
178
     * cap be claimed on a phone sold without the accessory, which is money on the wrong
179
     * SKU. Whether a bundle offer transfers to the bare SKU is a commercial question the
180
     * PDF does not answer, so it is a manual coverage decision, not a parsing rule.
181
     */
182
    @Test
183
    public void keepsBundledAccessoryDistinctFromTheBarePhone() {
184
        assertEquals(Arrays.asList("RENO16C 12+256GB +Bubble"),
185
                ProductNames.split("RENO16C 12+256GB +Bubble"));
186
        assertEquals(Arrays.asList("X300 Pro(16+512G)+Extender"),
187
                ProductNames.split("X300 Pro(16+512G)+Extender"));
188
 
189
        // and the bare phone in the same cell stays separate from the bundled one
190
        assertEquals(Arrays.asList("X300 Pro(16+512G)", "X300 Pro(16+512G)+Extender"),
191
                ProductNames.split("X300 Pro(16+512G), X300 Pro(16+512G)+Extender"));
192
 
193
        // standalone accessories are whole product names, never truncated to a phone;
194
        // they are removed as IGNORE aliases instead
195
        assertEquals(Arrays.asList("X300 FE Telephoto Extender"),
196
                ProductNames.split("X300 FE Telephoto Extender"));
197
        assertEquals(Arrays.asList("Moto Buds 2"), ProductNames.split("Moto Buds 2"));
198
    }
199
}