From e79a33b97938d7694fc17a9a28d8b55e759bd379 Mon Sep 17 00:00:00 2001 From: Charley_Campbell Date: Thu, 23 Jan 2025 09:57:54 +0000 Subject: [PATCH] Refactored the logic so that a country name can be passed in from the DB. we convert it to a country code so it can check against the user payload which has the country code Added in a number of unit tests to cover the new logic including the country code converter --- src/server/tests/epics/epicSelection.test.ts | 64 ++++++++++++++++++++ src/shared/lib/geolocation.test.ts | 22 ++++++- src/shared/lib/geolocation.ts | 43 +++++++------ 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/src/server/tests/epics/epicSelection.test.ts b/src/server/tests/epics/epicSelection.test.ts index 77c86ff10..cd5cd0839 100644 --- a/src/server/tests/epics/epicSelection.test.ts +++ b/src/server/tests/epics/epicSelection.test.ts @@ -333,6 +333,70 @@ describe('matchesCountryGroups filter', () => { expect(got).toBe(false); }); + + it('should pass if user is in country group or targeted country', () => { + const test: EpicTest = { + ...testDefault, + locations: ['EURCountries'], + targetedCountries: ['Canada'], + }; + const targeting: EpicTargeting = { + ...targetingDefault, + countryCode: 'DE', + }; + + const got = matchesCountryGroups.test(test, targeting); + + expect(got).toBe(true); + }); + + it('should pass if user is in targeted country', () => { + const test: EpicTest = { + ...testDefault, + locations: [], + targetedCountries: ['Germany'], + }; + const targeting: EpicTargeting = { + ...targetingDefault, + countryCode: 'DE', + }; + + const got = matchesCountryGroups.test(test, targeting); + + expect(got).toBe(true); + }); + + it('should pass if user is in both country group or targeted country', () => { + const test: EpicTest = { + ...testDefault, + locations: ['EURCountries'], + targetedCountries: ['Germany'], + }; + const targeting: EpicTargeting = { + ...targetingDefault, + countryCode: 'DE', + }; + + const got = matchesCountryGroups.test(test, targeting); + + expect(got).toBe(true); + }); + + it('should fail if user is not in country group or targeted country', () => { + const test: EpicTest = { + ...testDefault, + locations: ['EURCountries'], + targetedCountries: ['New Zealand'], + }; + const targeting: EpicTargeting = { + ...targetingDefault, + countryCode: 'GB', + }; + + const got = matchesCountryGroups.test(test, targeting); + + expect(got).toBe(false); + }); }); describe('withinArticleViewedSettings filter', () => { diff --git a/src/shared/lib/geolocation.test.ts b/src/shared/lib/geolocation.test.ts index 13e1908d6..24f60d75b 100644 --- a/src/shared/lib/geolocation.test.ts +++ b/src/shared/lib/geolocation.test.ts @@ -1,4 +1,9 @@ -import { getCountryName, getLocalCurrencySymbol, addRegionIdToSupportUrl } from './geolocation'; +import { + getCountryName, + getLocalCurrencySymbol, + addRegionIdToSupportUrl, + getCountryCodeFromName, +} from './geolocation'; describe('getLocalCurrencySymbol', () => { const currencySymbolTests = [ @@ -83,3 +88,18 @@ describe('addRegionIdToSupportUrl', () => { expect(modifiedUrl).toEqual(nonconformingUrl); }); }); + +describe('get Country Code from name', () => { + const testCases = [ + { input: 'the UK', expected: 'GB' }, + { input: 'the Czech Republic', expected: 'CZ' }, + { input: 'France', expected: 'FR' }, + { input: 'Nonexistent Country', expected: undefined }, + ]; + + testCases.forEach(({ input, expected }) => { + it(`returns ${expected} for country name ${input}`, () => { + expect(getCountryCodeFromName(input)).toEqual(expected); + }); + }); +}); diff --git a/src/shared/lib/geolocation.ts b/src/shared/lib/geolocation.ts index 8d79a3486..e6eee6d16 100644 --- a/src/shared/lib/geolocation.ts +++ b/src/shared/lib/geolocation.ts @@ -13,7 +13,7 @@ export const CountryGroupId = [ export type CountryGroupId = (typeof CountryGroupId)[number]; export const countryGroupIdSchema = z.enum(CountryGroupId); -export const targetedCountriesSchema = z.array(z.string()); +//export const targetedCountriesSchema = z.string(); // Used to internationalise 'Support the Guardian' links export type SupportRegionId = 'UK' | 'US' | 'AU' | 'EU' | 'INT' | 'NZ' | 'CA'; @@ -561,7 +561,6 @@ export const countryCodeToCountryGroupId = (countryCode?: string): CountryGroupI const foundCountryGroupId = availableCountryGroupIds.find((countryGroupId) => countryGroups[countryGroupId].countries.includes(countryCode ?? ''), ); - return foundCountryGroupId || 'International'; }; @@ -570,29 +569,29 @@ export const inCountryGroups = ( countryGroups: CountryGroupId[] = [], countryNames: string[] = [], // Accepts country names ): boolean => { - // Always True if no locations or targeted countries set for the test + // Always True if no locations or targeted countries set for the test (so always displays epic) if (countryGroups.length === 0 && countryNames.length === 0) { return true; } - - // Always False if user location unknown but test has locations set + // Always false if user location unknown but test has locations set (so never displays epic unless in country) if (!countryCode) { return false; } - const upperCaseCountryCode = countryCode.toUpperCase(); - console.log('upperCaseCountryCode', upperCaseCountryCode); - - // Convert country names to codes - const countryCodesFromNames = mapCountryNamesToCodes(countryNames); - - // Check if the country is directly targeted by name (converted to code) - if (countryCodesFromNames.includes(upperCaseCountryCode)) { + // Check if the country belongs to the specified country groups + if (countryGroups.includes(countryCodeToCountryGroupId(countryCode.toUpperCase()))) { return true; } - // Check if the country belongs to the specified country groups - return countryGroups.includes(countryCodeToCountryGroupId(upperCaseCountryCode)); + // Check if the country is in the targeted countries by name + for (const name of countryNames) { + const code = getCountryCodeFromName(name); + if (code && code.toUpperCase() === countryCode.toUpperCase()) { + return true; + } + } + + return false; }; const defaultCurrencySymbol = '£'; @@ -618,10 +617,6 @@ export const getCountryName = (geolocation?: string): string | undefined => { return undefined; }; -const mapCountryNamesToCodes = (names: string[]): string[] => { - return names.map((name) => countryNames[name] || null).filter((code): code is string => !!code); -}; - const countryCodeToSupportRegionId = (countryCode: string): SupportRegionId => countryGroups[countryCodeToCountryGroupId(countryCode)]?.supportRegionId; @@ -641,3 +636,13 @@ export const addRegionIdToSupportUrl = (originalUrl: string, countryCode?: strin return originalUrl; }; + +const countryNameToCodeMap: Record = {}; +for (const [code, name] of Object.entries(countryNames)) { + countryNameToCodeMap[name] = code; +} + +// Function to get country code from country name +export const getCountryCodeFromName = (countryName: string): string | undefined => { + return countryNameToCodeMap[countryName]; +};