From 5206ada77673daa24196b31748b637f0c349016a Mon Sep 17 00:00:00 2001 From: Alessio Dore <57567806+AleDore@users.noreply.github.com> Date: Tue, 3 Nov 2020 12:14:15 +0100 Subject: [PATCH] [#175497433] Add Profile unsubscription on feed (#86) * [#175497433] Add Profile unsubscription on feed * [#175497433] Removed useless check * [#175497433] Add some subscription feed tests * [#175497433] add date-fns utility methods --- .../__tests__/handler.test.ts | 330 ++++++++++++++++++ GetSubscriptionsFeed/handler.ts | 26 +- 2 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 GetSubscriptionsFeed/__tests__/handler.test.ts diff --git a/GetSubscriptionsFeed/__tests__/handler.test.ts b/GetSubscriptionsFeed/__tests__/handler.test.ts new file mode 100644 index 00000000..9ec9f902 --- /dev/null +++ b/GetSubscriptionsFeed/__tests__/handler.test.ts @@ -0,0 +1,330 @@ +/* tslint:disable:no-any */ +/* tslint:disable:no-duplicate-string */ +/* tslint:disable:no-big-function */ +/* tslint:disable: no-identical-functions */ + +import { TableService } from "azure-storage"; +import * as dateFmt from "date-fns"; +import * as endOfTomorrow from "date-fns/end_of_tomorrow"; +import * as startOfYesterday from "date-fns/start_of_yesterday"; +import { ServiceId } from "io-functions-commons/dist/generated/definitions/ServiceId"; +import { FiscalCodeHash } from "../../generated/definitions/FiscalCodeHash"; +import { GetSubscriptionsFeedHandler } from "../handler"; + +const tomorrow = endOfTomorrow(); + +const yesterday = startOfYesterday(); +const aServiceId = "MyServiceId" as ServiceId; + +const yesterdayUTC = dateFmt.format(yesterday, "YYYY-MM-DD"); + +const userAttrs = { + email: "example@mail.com", + kind: "IAzureUserAttributes", + service: { + serviceId: aServiceId + } +}; + +const anHashedFiscalCode = "77408089123C62362C2D70E4C262BB45E268A3D477335D9C4A383521FA772AAE" as FiscalCodeHash; +const anotherHashedFiscalCode = "77408089123C62362C2D70E4C262BB45E268A3D477335D9C4A383521FA772AAA" as FiscalCodeHash; +const anotherThirdHashedFiscalCode = "77408089123C62362C2D70E4C262BB45E268A3D477335D9C4A383521FA772BBB" as FiscalCodeHash; + +const queryEntitiesProfileSubscriptionMock = ( + entries: ReadonlyArray, + subscriptionSuffix: "S" | "U" +) => + jest.fn((_, __, ___, cb) => { + return cb( + null, + { + entries: + entries.length > 0 + ? entries.map(e => ({ + RowKey: { _: `P-${yesterdayUTC}-${subscriptionSuffix}-${e}` } + })) + : [] + }, + { isSuccessful: true } + ); + }); + +const queryEntitiesServiceSubscriptionMock = ( + entries: ReadonlyArray, + subscriptionSuffix: "S" | "U" +) => + jest.fn((_, __, ___, cb) => { + return cb( + null, + { + entries: + entries.length > 0 + ? entries.map(e => ({ + RowKey: { + _: `S-${yesterdayUTC}-${aServiceId}-${subscriptionSuffix}-${e}` + } + })) + : [] + }, + { isSuccessful: true } + ); + }); + +const emptyQueryEntities = queryEntitiesProfileSubscriptionMock([], "S"); + +describe("GetSubscriptionsFeedHandler", () => { + it("should respond with Not Found if Date.now() is lower than given subscriptionDate", async () => { + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + {} as any, + "subscriptionFeedByDay" + ); + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + dateFmt.format(tomorrow, "YYYY-MM-DD") + ); + expect(result.kind).toBe("IResponseErrorNotFound"); + }); + + it("should return an empty feed json if no changes happened for the given subscriptionDate", async () => { + const tableServiceMock = ({ + queryEntities: queryEntitiesProfileSubscriptionMock([], "S") + } as any) as TableService; + + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + tableServiceMock, + "subscriptionFeedByDay" + ); + + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + yesterdayUTC + ); + expect(result.kind).toBe("IResponseSuccessJson"); + if (result.kind === "IResponseSuccessJson") { + expect(result.value).toEqual({ + dateUTC: yesterdayUTC, + subscriptions: [], + unsubscriptions: [] + }); + } + }); + + it("should return a correct feed json if there are only profile registrations", async () => { + const queryEntities = jest.fn(); + queryEntities.mockImplementationOnce( + queryEntitiesProfileSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "S" + ) + ); + queryEntities.mockImplementation(emptyQueryEntities); + const tableServiceMock = ({ + queryEntities + } as any) as TableService; + + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + tableServiceMock, + "subscriptionFeedByDay" + ); + + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + yesterdayUTC + ); + expect(result.kind).toBe("IResponseSuccessJson"); + if (result.kind === "IResponseSuccessJson") { + expect(result.value).toEqual({ + dateUTC: yesterdayUTC, + subscriptions: [anHashedFiscalCode, anotherHashedFiscalCode], + unsubscriptions: [] + }); + } + }); + + it("should return a correct feed json if there are profile registrations and another service subscription", async () => { + const queryEntities = jest.fn(); + // Profile subscriptions + queryEntities.mockImplementationOnce( + queryEntitiesProfileSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "S" + ) + ); + // profile unsubscriptions + queryEntities.mockImplementationOnce(emptyQueryEntities); + // service subscriptions + queryEntities.mockImplementationOnce( + queryEntitiesServiceSubscriptionMock([anotherThirdHashedFiscalCode], "S") + ); + queryEntities.mockImplementation(emptyQueryEntities); + const tableServiceMock = ({ + queryEntities + } as any) as TableService; + + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + tableServiceMock, + "subscriptionFeedByDay" + ); + + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + yesterdayUTC + ); + expect(result.kind).toBe("IResponseSuccessJson"); + if (result.kind === "IResponseSuccessJson") { + expect(result.value).toEqual({ + dateUTC: yesterdayUTC, + subscriptions: [ + anHashedFiscalCode, + anotherHashedFiscalCode, + anotherThirdHashedFiscalCode + ], + unsubscriptions: [] + }); + } + }); + + it("should return a correct feed json if there are profile registrations and the same fiscal codes in service subscriptions", async () => { + const queryEntities = jest.fn(); + // Profile subscriptions + queryEntities.mockImplementationOnce( + queryEntitiesProfileSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "S" + ) + ); + // profile unsubscriptions + queryEntities.mockImplementationOnce(emptyQueryEntities); + // service subscriptions + queryEntities.mockImplementationOnce( + queryEntitiesServiceSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "S" + ) + ); + queryEntities.mockImplementation(emptyQueryEntities); + const tableServiceMock = ({ + queryEntities + } as any) as TableService; + + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + tableServiceMock, + "subscriptionFeedByDay" + ); + + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + yesterdayUTC + ); + expect(result.kind).toBe("IResponseSuccessJson"); + if (result.kind === "IResponseSuccessJson") { + expect(result.value).toEqual({ + dateUTC: yesterdayUTC, + subscriptions: [anHashedFiscalCode, anotherHashedFiscalCode], + unsubscriptions: [] + }); + } + }); + + it("should return a correct feed json if there are profile delete and the same fiscal codes in service subscriptions", async () => { + const queryEntities = jest.fn(); + // Profile subscriptions + queryEntities.mockImplementationOnce(emptyQueryEntities); + // profile unsubscriptions + queryEntities.mockImplementationOnce( + queryEntitiesProfileSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "U" + ) + ); + // service subscriptions + queryEntities.mockImplementationOnce( + queryEntitiesServiceSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "S" + ) + ); + queryEntities.mockImplementation(emptyQueryEntities); + const tableServiceMock = ({ + queryEntities + } as any) as TableService; + + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + tableServiceMock, + "subscriptionFeedByDay" + ); + + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + yesterdayUTC + ); + expect(result.kind).toBe("IResponseSuccessJson"); + if (result.kind === "IResponseSuccessJson") { + expect(result.value).toEqual({ + dateUTC: yesterdayUTC, + subscriptions: [], + unsubscriptions: [anHashedFiscalCode, anotherHashedFiscalCode] + }); + } + }); + + it("should return a correct feed json if there are profile subscription skipping the same fiscal codes in service unsubscriptions", async () => { + const queryEntities = jest.fn(); + // Profile subscriptions + queryEntities.mockImplementationOnce( + queryEntitiesProfileSubscriptionMock( + [ + anHashedFiscalCode, + anotherHashedFiscalCode, + anotherThirdHashedFiscalCode + ], + "S" + ) + ); + // profile unsubscriptions + queryEntities.mockImplementationOnce(emptyQueryEntities); + // service subscriptions + queryEntities.mockImplementationOnce(emptyQueryEntities); + queryEntities.mockImplementation( + queryEntitiesServiceSubscriptionMock( + [anHashedFiscalCode, anotherHashedFiscalCode], + "U" + ) + ); + const tableServiceMock = ({ + queryEntities + } as any) as TableService; + + const getSubscriptionsFeedHandler = GetSubscriptionsFeedHandler( + tableServiceMock, + "subscriptionFeedByDay" + ); + + const result = await getSubscriptionsFeedHandler( + {} as any, + {} as any, + userAttrs as any, + yesterdayUTC + ); + expect(result.kind).toBe("IResponseSuccessJson"); + if (result.kind === "IResponseSuccessJson") { + expect(result.value).toEqual({ + dateUTC: yesterdayUTC, + subscriptions: [anotherThirdHashedFiscalCode], + unsubscriptions: [] + }); + } + }); +}); diff --git a/GetSubscriptionsFeed/handler.ts b/GetSubscriptionsFeed/handler.ts index ccbbaece..a64475fe 100644 --- a/GetSubscriptionsFeed/handler.ts +++ b/GetSubscriptionsFeed/handler.ts @@ -107,6 +107,9 @@ export function GetSubscriptionsFeedHandler( const profileSubscriptionsQuery: PagedQuery = pagedQuery( queryFilterForKey(`P-${subscriptionsDateUTC}-S`) ); + const profileUnsubscriptionsQuery: PagedQuery = pagedQuery( + queryFilterForKey(`P-${subscriptionsDateUTC}-U`) + ); const serviceSubscriptionsQuery: PagedQuery = pagedQuery( queryFilterForKey(`S-${subscriptionsDateUTC}-${serviceId}-S`) ); @@ -117,6 +120,11 @@ export function GetSubscriptionsFeedHandler( // users that created their account on date const profileSubscriptionsSet = await queryUsers(profileSubscriptionsQuery); + // users that deleted their account on date + const profileUnsubscriptionsSet = await queryUsers( + profileUnsubscriptionsQuery + ); + // users that subscribed to the client service on date const serviceSubscriptionsSet = await queryUsers(serviceSubscriptionsQuery); @@ -134,7 +142,10 @@ export function GetSubscriptionsFeedHandler( } }); serviceSubscriptionsSet.forEach(ss => { - if (!profileSubscriptionsSet.has(ss)) { + if ( + !profileSubscriptionsSet.has(ss) && + !profileUnsubscriptionsSet.has(ss) + ) { // add all users that subscribed to this service, skipping those that // are new users as they're yet counted in as new subscribers in the // previous step @@ -143,11 +154,20 @@ export function GetSubscriptionsFeedHandler( }); const unsubscriptions = new Array(); + + profileUnsubscriptionsSet.forEach(pu => + // add all users that deleted its own account + unsubscriptions.push(pu) + ); + serviceUnsubscriptionsSet.forEach(su => { - if (!profileSubscriptionsSet.has(su)) { + if ( + !profileSubscriptionsSet.has(su) && + !profileUnsubscriptionsSet.has(su) + ) { // add all users that unsubscribed from this service, skipping those // that created the profile on the same day as the service will not - // yet know they exist + // yet know they exist or deleted their account unsubscriptions.push(su); } });