Rev 37337 | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.spice.profitmandi.web.offercircular;import static org.junit.Assert.assertEquals;import java.util.Arrays;import java.util.LinkedHashSet;import java.util.Set;import org.junit.Test;/*** Guards the product-name parsing, which decides which SKU an offer's money lands on.** Every case here corresponds to a real mis-parse found against the Aug'26 circular.* They all share one root cause worth remembering: **characters and digits belonging to* the model name being consumed as memory or stripped as punctuation.** If a change* here breaks a test, the fix is almost certainly to add a word-boundary guard rather* than to relax the expectation.*/public class ProductNamesTest {private static Set<String> memory(String... tokens) {return new LinkedHashSet<>(Arrays.asList(tokens));}private static void check(String input, Set<String> expectedMemory, String expectedKey) {assertEquals("memory for " + input, expectedMemory, ProductNames.variantTokens(input));assertEquals("modelKey for " + input, expectedKey, ProductNames.modelKey(input));}/*** The rule: memory stated -> variant-specific; memory absent -> all variants.* So detecting memory correctly is what separates "this exact SKU" from* "every size of this model", and the OEMs use three different notations.*/@Testpublic void detectsAllThreeMemoryNotations() {// Motorola: no unit at all. Missed before, which put a Rs.1,000 8+256 offer// and a Rs.2,000 12+256 offer on the SAME SKU.check("Edge 60 Pro (8+256)", memory("8X", "256X"), "EDGE60PRO");check("Edge 60) Pro (12+256", memory("12X", "256X"), "EDGE60PRO");// vivo: single-letter unitcheck("V60(12+256G)", memory("12X", "256X"), "V60");check("X300 Pro(16+512G)", memory("16X", "512X"), "X300PRO");// Oppo: explicit unit, and unit on both sidescheck("RENO16C 12+256GB", memory("12X", "256X"), "RENO16C");check("A5X(4GB+128GB)", memory("4X", "128X"), "A5X");}/** A bare 4G/5G is a network marker, never a capacity - but 4 GB of RAM is real. */@Testpublic void doesNotConfuseNetworkMarkerWithCapacity() {check("Y05(4+64G)", memory("4X", "64X"), "Y05");check("Reno 16C 5G (12GB 256GB)", memory("12X", "256X"), "RENO16C");check("Redmi Note 15 Pro 5G", memory(), "REDMINOTE15PRO");}/** Terabyte capacities must stay distinct from gigabyte ones. */@Testpublic void handlesTerabytes() {check("S26 Ultra (16/1TB)", memory("16X", "1Y"), "S26ULTRA");check("iPhone 17 Pro (1 TB)", memory("1Y"), "IPHONE17PRO");check("iPhone 17 Pro (256 GB)", memory("256X"), "IPHONE17PRO");}/*** A digit glued to letters is part of the name. Without word-boundary guards the* "5X" inside A5X read as 5 GB and collapsed the key to "A", making Oppo A5X and* A6X indistinguishable.*/@Testpublic void doesNotEatDigitsOutOfModelNames() {assertEquals("A5X", ProductNames.modelKey("A5X(4GB+128GB)"));assertEquals("A6X", ProductNames.modelKey("A6X(6GB+128GB)"));assertEquals("XFOLD5", ProductNames.modelKey("X Fold5(16+512G)"));}/*** '+' in a model name is significant. Stripping it as punctuation made* "Realme 16 Pro" and "Realme 16 Pro+" the same key and cross-matched 6 SKUs* across 12 offers - 1,188 false collisions from one character.*/@Testpublic void keepsModelNamePlusSignificant() {assertEquals("16PRO", ProductNames.modelKey("Realme 16 Pro"));assertEquals("16PROPLUS", ProductNames.modelKey("Realme 16 Pro+"));assertEquals("16PRO", ProductNames.modelKey("Realme 16 Pro 5G (8GB 256GB)"));assertEquals("16PROPLUS", ProductNames.modelKey("Realme 16 Pro+ 5G (8GB 256GB)"));assertEquals("REDMINOTE15PRO", ProductNames.modelKey("Redmi Note 15 Pro 5G"));assertEquals("REDMINOTE15PROPLUS", ProductNames.modelKey("Redmi Note 15 Pro+ 5G"));}/*** The circular writes "S25+" while the catalog writes "S25 Plus" - the same phone,* so they MUST converge. And all three must stay distinct from plain S25.* "S25+ 5G" once keyed as "S25X" and then "SG" while these guards were incomplete.*/@Testpublic void s25PlusAndS25PlusSpelledOutAreTheSamePhone() {assertEquals("S25PLUS", ProductNames.modelKey("S25+ (12/256GB)"));assertEquals("S25PLUS", ProductNames.modelKey("S25 Plus (12GB 256GB)"));assertEquals("S25PLUS", ProductNames.modelKey("S25+ 5G (12GB 256GB)"));assertEquals("S25", ProductNames.modelKey("S25 (12/128 GB)"));assertEquals("S25ULTRA", ProductNames.modelKey("S25 Ultra (12/512GB)"));assertEquals("S25FE", ProductNames.modelKey("S25 FE (128GB)"));assertEquals("S25EDGE", ProductNames.modelKey("S25 Edge (12/256GB)"));}/*** Spaces are insignificant so the circular's "RENO16C" matches the catalog's* "Reno 16C 5G"; digits are significant so a one-token difference never matches.*/@Testpublic void spacesInsignificantDigitsSignificant() {assertEquals(ProductNames.modelKey("RENO16C 12+256GB"),ProductNames.modelKey("Reno 16C 5G (12GB 256GB)"));assertEquals("IPHONE17E", ProductNames.modelKey("iPhone 17e"));assertEquals("IPHONE7", ProductNames.modelKey("iPhone 7"));}/** WIFI/LTE are NOT noise: for tablets they are the only variant discriminator. */@Testpublic void keepsConnectivityDistinctForTablets() {assertEquals("TABS11ULTRAWIFI",ProductNames.modelKey("Tab S11 Ultra Wifi (12/256GB)"));assertEquals("TABS11ULTRALTE",ProductNames.modelKey("Tab S11 Ultra LTE (12/256GB)"));}/*** Splitting must never cut through a variant spec - doing so invented a phantom* product literally called "256GB" - and trailing variant-only fragments inherit* the preceding model name.*/@Testpublic void splitsMultiProductCellsWithoutCuttingVariants() {assertEquals(Arrays.asList("Tab S11 Ultra Wifi (12/256GB)","Tab S11 Ultra Wifi (12/512GB)"),ProductNames.split("Tab S11 Ultra Wifi (12/256GB)/Tab S11 Ultra Wifi (12/512GB)"));assertEquals(Arrays.asList("RENO16C 12+256GB", "RENO16C 8+256GB", "RENO16C 8+128GB"),ProductNames.split("RENO16C 12+256GB, 8+256GB, 8+128GB"));// two products run together with no delimiter at allassertEquals(Arrays.asList("A37 5G (8GB/128GB)", "A37 5G (12GB/256GB)"),ProductNames.split("A37 5G (8GB/128GB)A37 5G (12GB/256GB)"));}/*** A comma-separated part that starts with a LETTER is a product, never a memory* spec, however many digits and G/T characters follow it.** VARIANT_ONLY's unit group used to be reachable at position 0, so the "G" of a* Motorola G-series name read as the unit and "06"/"37" fell through the trailing* class. "G06, G37 , G37 Power" then collapsed to two entities - "G06" and* "G06 G37" - and G37, a real catalog SKU, lost its cashback on five offers.* Realme "GT 7" matched identically and escaped only by never following a comma.*/@Testpublic void keepsLetterLedModelNamesOutOfVariantInheritance() {assertEquals(Arrays.asList("G06", "G37", "G37 Power"),ProductNames.split("G06, G37 , G37 Power"));assertEquals(Arrays.asList("GT 7T", "GT 7"),ProductNames.split("GT 7T, GT 7"));assertEquals(Arrays.asList("G06", "G37", "G37 Power", "G77 Power", "G Max","Edge 70 Fusion (12/256)", "Edge 60 Pro (8+256)"),ProductNames.split("G06, G37, G37 Power , G77 Power, G Max ,"+ " Edge 70 Fusion (12/256) , Edge 60 Pro (8+256)"));// and the inheritance it guards must still fire for genuine memory specsassertEquals(Arrays.asList("RENO 15 PRO 256GB", "RENO 15 PRO 512GB"),ProductNames.split("RENO 15 PRO 256GB, 512GB"));assertEquals(Arrays.asList("F33 6+128GB", "F33 8+128GB", "F33 8+256GB"),ProductNames.split("F33 6+128GB, 8+128GB, 8+256GB"));}/*** Two variant groups written back-to-back are two variants of one model. Merged,* their memory tokens ran together and the offer bound to one arbitrary SKU while* the other variant silently got nothing - money on the wrong SKU, same failure* class as the missing-unit bug.*/@Testpublic void splitsAdjacentVariantGroupsIntoSeparateProducts() {assertEquals(Arrays.asList("Edge 70 Pro (8+256)", "Edge 70 Pro 12+256"),ProductNames.split("Edge 70 Pro (8+256)(12+256)"));assertEquals(Arrays.asList("G37 Power (4+64)", "G37 Power 4+128"),ProductNames.split("G37 Power (4+64)(4+128)"));assertEquals(Arrays.asList("A6 (6+128GB)", "A6 4+128GB"),ProductNames.split("A6 (6+128GB)(4+128GB)"));// a '/' between the groups is already a split point and must stay oneassertEquals(Arrays.asList("X300 FE(12+512G)", "X300 FE 12+256G"),ProductNames.split("X300 FE(12+512G)/(12+256G)"));}/*** A bundled accessory suffix must SURVIVE splitting, so the bundle stays a distinct* product from the bare phone.** Stripping it was implemented and reverted: it resolved 30 CATALOG_GAP rows, but on* Aug'26 vivo caps X300 Pro(16+512G) at Rs.10,000 on its own row and Rs.11,000 on the* "+Extender" row - the difference being the Extender. Merging them lets the bundle's* cap be claimed on a phone sold without the accessory, which is money on the wrong* SKU. Whether a bundle offer transfers to the bare SKU is a commercial question the* PDF does not answer, so it is a manual coverage decision, not a parsing rule.*/@Testpublic void keepsBundledAccessoryDistinctFromTheBarePhone() {assertEquals(Arrays.asList("RENO16C 12+256GB +Bubble"),ProductNames.split("RENO16C 12+256GB +Bubble"));assertEquals(Arrays.asList("X300 Pro(16+512G)+Extender"),ProductNames.split("X300 Pro(16+512G)+Extender"));// and the bare phone in the same cell stays separate from the bundled oneassertEquals(Arrays.asList("X300 Pro(16+512G)", "X300 Pro(16+512G)+Extender"),ProductNames.split("X300 Pro(16+512G), X300 Pro(16+512G)+Extender"));// standalone accessories are whole product names, never truncated to a phone;// they are removed as IGNORE aliases insteadassertEquals(Arrays.asList("X300 FE Telephoto Extender"),ProductNames.split("X300 FE Telephoto Extender"));assertEquals(Arrays.asList("Moto Buds 2"), ProductNames.split("Moto Buds 2"));}}