|
| 1 | +import { AnalyticsService } from "../../../lib/services/analytics/analytics-service"; |
| 2 | +import { Yok } from "../../../lib/common/yok"; |
| 3 | +import * as stubs from "../../stubs"; |
| 4 | +import { assert } from "chai"; |
| 5 | +import { EventEmitter } from "events"; |
| 6 | +import { AnalyticsClients } from "../../../lib/common/constants"; |
| 7 | + |
| 8 | +const helpers = require("../../../lib/common/helpers"); |
| 9 | +const originalIsInteractive = helpers.isInteractive; |
| 10 | + |
| 11 | +const trackFeatureUsage = "TrackFeatureUsage"; |
| 12 | +const createTestInjector = (): IInjector => { |
| 13 | + const testInjector = new Yok(); |
| 14 | + testInjector.register("options", {}); |
| 15 | + testInjector.register("logger", stubs.LoggerStub); |
| 16 | + |
| 17 | + testInjector.register("staticConfig", { |
| 18 | + disableAnalytics: false, |
| 19 | + TRACK_FEATURE_USAGE_SETTING_NAME: trackFeatureUsage, |
| 20 | + PATH_TO_BOOTSTRAP: "pathToBootstrap.js" |
| 21 | + }); |
| 22 | + |
| 23 | + testInjector.register("prompter", { |
| 24 | + |
| 25 | + }); |
| 26 | + |
| 27 | + testInjector.register("userSettingsService", { |
| 28 | + getSettingValue: async (settingName: string): Promise<any> => { |
| 29 | + return "true"; |
| 30 | + } |
| 31 | + }); |
| 32 | + testInjector.register("analyticsSettingsService", { |
| 33 | + canDoRequest: (): Promise<boolean> => Promise.resolve(true) |
| 34 | + }); |
| 35 | + testInjector.register("osInfo", {}); |
| 36 | + testInjector.register("childProcess", {}); |
| 37 | + testInjector.register("processService", { |
| 38 | + attachToProcessExitSignals: (context: any, callback: () => void): void => undefined |
| 39 | + }); |
| 40 | + testInjector.register("projectDataService", {}); |
| 41 | + testInjector.register("mobileHelper", {}); |
| 42 | + |
| 43 | + return testInjector; |
| 44 | +}; |
| 45 | + |
| 46 | +describe("analyticsService", () => { |
| 47 | + afterEach(() => { |
| 48 | + helpers.isInteractive = originalIsInteractive; |
| 49 | + }); |
| 50 | + |
| 51 | + describe("trackInGoogleAnalytics", () => { |
| 52 | + describe("does not track", () => { |
| 53 | + const testScenario = async (configuration: { |
| 54 | + disableAnalytics: boolean, |
| 55 | + assertMessage: string, |
| 56 | + userSettingsServiceOpts?: { trackFeatureUsageValue: string, defaultValue: string } |
| 57 | + }) => { |
| 58 | + const testInjector = createTestInjector(); |
| 59 | + const staticConfig = testInjector.resolve<Config.IStaticConfig>("staticConfig"); |
| 60 | + staticConfig.disableAnalytics = configuration.disableAnalytics; |
| 61 | + |
| 62 | + configuration.userSettingsServiceOpts = configuration.userSettingsServiceOpts || { trackFeatureUsageValue: "false", defaultValue: "true" }; |
| 63 | + const userSettingsService = testInjector.resolve<any>("userSettingsService"); |
| 64 | + userSettingsService.getSettingValue = async (settingName: string): Promise<string> => { |
| 65 | + if (settingName === trackFeatureUsage) { |
| 66 | + return configuration.userSettingsServiceOpts.trackFeatureUsageValue; |
| 67 | + } |
| 68 | + |
| 69 | + return configuration.userSettingsServiceOpts.defaultValue; |
| 70 | + }; |
| 71 | + |
| 72 | + let isChildProcessSpawned = false; |
| 73 | + const childProcess = testInjector.resolve<IChildProcess>("childProcess"); |
| 74 | + childProcess.spawn = (command: string, args?: string[], options?: any): any => { |
| 75 | + isChildProcessSpawned = true; |
| 76 | + }; |
| 77 | + |
| 78 | + const analyticsService = testInjector.resolve<IAnalyticsService>(AnalyticsService); |
| 79 | + await analyticsService.trackInGoogleAnalytics({ |
| 80 | + googleAnalyticsDataType: GoogleAnalyticsDataType.Page, |
| 81 | + customDimensions: { |
| 82 | + customDimension1: "value1" |
| 83 | + } |
| 84 | + }); |
| 85 | + |
| 86 | + assert.isFalse(isChildProcessSpawned, configuration.assertMessage); |
| 87 | + }; |
| 88 | + |
| 89 | + it("does not track when staticConfig's disableAnalytics is true", () => { |
| 90 | + return testScenario({ |
| 91 | + disableAnalytics: true, |
| 92 | + assertMessage: "When staticConfig.disableAnalytics is true, no child process should be started, i.e. we should not track anything." |
| 93 | + }); |
| 94 | + }); |
| 95 | + |
| 96 | + it(`does not track when ${trackFeatureUsage} is not true`, async () => { |
| 97 | + await testScenario({ |
| 98 | + disableAnalytics: false, |
| 99 | + assertMessage: `When ${trackFeatureUsage} is false, no child process should be started, i.e. we should not track anything.`, |
| 100 | + userSettingsServiceOpts: { |
| 101 | + trackFeatureUsageValue: "false", defaultValue: "true" |
| 102 | + } |
| 103 | + }); |
| 104 | + |
| 105 | + await testScenario({ |
| 106 | + disableAnalytics: false, |
| 107 | + assertMessage: `When ${trackFeatureUsage} is undefined, no child process should be started, i.e. we should not track anything.`, |
| 108 | + userSettingsServiceOpts: { |
| 109 | + trackFeatureUsageValue: undefined, defaultValue: "true" |
| 110 | + } |
| 111 | + }); |
| 112 | + }); |
| 113 | + |
| 114 | + }); |
| 115 | + |
| 116 | + const getSpawnedProcess = (): any => { |
| 117 | + const spawnedProcess: any = new EventEmitter(); |
| 118 | + spawnedProcess.stdout = new EventEmitter(); |
| 119 | + spawnedProcess.stderr = new EventEmitter(); |
| 120 | + spawnedProcess.unref = (): void => undefined; |
| 121 | + return spawnedProcess; |
| 122 | + }; |
| 123 | + |
| 124 | + describe("does not fail", () => { |
| 125 | + const assertExpectedError = async (testInjector: IInjector, opts: { isChildProcessSpawned: boolean, expectedErrorMessage: string }) => { |
| 126 | + const analyticsService = testInjector.resolve<IAnalyticsService>(AnalyticsService); |
| 127 | + await analyticsService.trackInGoogleAnalytics({ |
| 128 | + googleAnalyticsDataType: GoogleAnalyticsDataType.Page, |
| 129 | + customDimensions: { |
| 130 | + customDimension1: "value1" |
| 131 | + } |
| 132 | + }); |
| 133 | + |
| 134 | + assert.isTrue(opts.isChildProcessSpawned); |
| 135 | + const logger = testInjector.resolve<stubs.LoggerStub>("logger"); |
| 136 | + assert.isTrue(logger.traceOutput.indexOf(opts.expectedErrorMessage) !== -1); |
| 137 | + }; |
| 138 | + |
| 139 | + const setupTest = (expectedErrorMessage: string): any => { |
| 140 | + const testInjector = createTestInjector(); |
| 141 | + const opts = { |
| 142 | + isChildProcessSpawned: false, |
| 143 | + expectedErrorMessage |
| 144 | + }; |
| 145 | + |
| 146 | + const childProcess = testInjector.resolve<IChildProcess>("childProcess"); |
| 147 | + return { |
| 148 | + testInjector, |
| 149 | + opts, |
| 150 | + childProcess |
| 151 | + }; |
| 152 | + }; |
| 153 | + |
| 154 | + it("when unable to start broker process", async () => { |
| 155 | + const { testInjector, childProcess, opts } = setupTest("Unable to get broker instance due to error: Error: custom error"); |
| 156 | + childProcess.spawn = (command: string, args?: string[], options?: any): any => { |
| 157 | + opts.isChildProcessSpawned = true; |
| 158 | + throw new Error("custom error"); |
| 159 | + }; |
| 160 | + |
| 161 | + await assertExpectedError(testInjector, opts); |
| 162 | + }); |
| 163 | + |
| 164 | + it("when broker cannot start for required timeout", async () => { |
| 165 | + const { testInjector, childProcess, opts } = setupTest("Unable to get broker instance due to error: Error: Unable to start Analytics Broker process."); |
| 166 | + const originalSetTimeout = setTimeout; |
| 167 | + childProcess.spawn = (command: string, args?: string[], options?: any): any => { |
| 168 | + opts.isChildProcessSpawned = true; |
| 169 | + global.setTimeout = (callback: (...args: any[]) => void, ms: number, ...otherArgs: any[]) => originalSetTimeout(callback, 1); |
| 170 | + return getSpawnedProcess(); |
| 171 | + }; |
| 172 | + |
| 173 | + await assertExpectedError(testInjector, opts); |
| 174 | + |
| 175 | + global.setTimeout = originalSetTimeout; |
| 176 | + }); |
| 177 | + |
| 178 | + it("when broker is not connected", async () => { |
| 179 | + const { testInjector, childProcess, opts } = setupTest("Broker not found or not connected."); |
| 180 | + |
| 181 | + childProcess.spawn = (command: string, args?: string[], options?: any): any => { |
| 182 | + opts.isChildProcessSpawned = true; |
| 183 | + const spawnedProcess: any = getSpawnedProcess(); |
| 184 | + |
| 185 | + spawnedProcess.connected = false; |
| 186 | + spawnedProcess.send = (): void => undefined; |
| 187 | + setTimeout(() => spawnedProcess.emit("message", AnalyticsMessages.BrokerReadyToReceive), 1); |
| 188 | + return spawnedProcess; |
| 189 | + }; |
| 190 | + |
| 191 | + await assertExpectedError(testInjector, opts); |
| 192 | + }); |
| 193 | + |
| 194 | + it("when sending message fails", async () => { |
| 195 | + const { testInjector, childProcess, opts } = setupTest("Error while trying to send message to broker: Error: Failed to sent data."); |
| 196 | + |
| 197 | + childProcess.spawn = (command: string, args?: string[], options?: any): any => { |
| 198 | + opts.isChildProcessSpawned = true; |
| 199 | + const spawnedProcess: any = getSpawnedProcess(); |
| 200 | + |
| 201 | + spawnedProcess.connected = true; |
| 202 | + spawnedProcess.send = (): void => { |
| 203 | + throw new Error("Failed to sent data."); |
| 204 | + }; |
| 205 | + |
| 206 | + setTimeout(() => spawnedProcess.emit("message", AnalyticsMessages.BrokerReadyToReceive), 1); |
| 207 | + return spawnedProcess; |
| 208 | + }; |
| 209 | + |
| 210 | + await assertExpectedError(testInjector, opts); |
| 211 | + }); |
| 212 | + }); |
| 213 | + |
| 214 | + describe("sends correct message to broker", () => { |
| 215 | + const setupTest = (expectedResult: any, dataToSend: any, terminalOpts?: { isInteractive: boolean }): { testInjector: IInjector, opts: any } => { |
| 216 | + helpers.isInteractive = () => terminalOpts ? terminalOpts.isInteractive : true; |
| 217 | + |
| 218 | + const testInjector = createTestInjector(); |
| 219 | + const opts = { |
| 220 | + isChildProcessSpawned: false, |
| 221 | + expectedResult, |
| 222 | + dataToSend, |
| 223 | + messageSent: <any>null |
| 224 | + }; |
| 225 | + |
| 226 | + const childProcess = testInjector.resolve<IChildProcess>("childProcess"); |
| 227 | + childProcess.spawn = (command: string, args?: string[], options?: any): any => { |
| 228 | + opts.isChildProcessSpawned = true; |
| 229 | + const spawnedProcess: any = getSpawnedProcess(); |
| 230 | + |
| 231 | + spawnedProcess.connected = true; |
| 232 | + spawnedProcess.send = (msg: any, action: () => void): void => { |
| 233 | + opts.messageSent = msg; |
| 234 | + action(); |
| 235 | + }; |
| 236 | + |
| 237 | + setTimeout(() => spawnedProcess.emit("message", AnalyticsMessages.BrokerReadyToReceive), 1); |
| 238 | + |
| 239 | + return spawnedProcess; |
| 240 | + }; |
| 241 | + |
| 242 | + return { |
| 243 | + testInjector, |
| 244 | + opts |
| 245 | + }; |
| 246 | + }; |
| 247 | + |
| 248 | + const assertExpectedResult = async (testInjector: IInjector, opts: { isChildProcessSpawned: boolean, expectedResult: any, messageSent: any, dataToSend: any }) => { |
| 249 | + const analyticsService = testInjector.resolve<IAnalyticsService>(AnalyticsService); |
| 250 | + await analyticsService.trackInGoogleAnalytics(opts.dataToSend); |
| 251 | + |
| 252 | + assert.isTrue(opts.isChildProcessSpawned); |
| 253 | + assert.deepEqual(opts.messageSent, opts.expectedResult); |
| 254 | + }; |
| 255 | + |
| 256 | + const getDataToSend = (gaDataType: string): any => ({ |
| 257 | + googleAnalyticsDataType: gaDataType, |
| 258 | + customDimensions: { |
| 259 | + customDimension1: "value1" |
| 260 | + } |
| 261 | + }); |
| 262 | + |
| 263 | + const getExpectedResult = (gaDataType: string, analyticsClient?: string): any => ({ |
| 264 | + type: "googleAnalyticsData", |
| 265 | + category: "CLI", |
| 266 | + googleAnalyticsDataType: gaDataType, |
| 267 | + customDimensions: { customDimension1: "value1", cd5: analyticsClient || "CLI" } |
| 268 | + }); |
| 269 | + |
| 270 | + _.each([GoogleAnalyticsDataType.Page, GoogleAnalyticsDataType.Event], (googleAnalyticsDataType: string) => { |
| 271 | + it(`when data is ${googleAnalyticsDataType}`, async () => { |
| 272 | + const { testInjector, opts } = setupTest(getExpectedResult(googleAnalyticsDataType), getDataToSend(googleAnalyticsDataType)); |
| 273 | + await assertExpectedResult(testInjector, opts); |
| 274 | + }); |
| 275 | + |
| 276 | + it(`when data is ${googleAnalyticsDataType} and terminal is not interactive`, async () => { |
| 277 | + const { testInjector, opts } = setupTest(getExpectedResult(googleAnalyticsDataType, AnalyticsClients.Unknown), getDataToSend(googleAnalyticsDataType), { isInteractive: false }); |
| 278 | + await assertExpectedResult(testInjector, opts); |
| 279 | + }); |
| 280 | + |
| 281 | + _.each([true, false], (isInteractive) => { |
| 282 | + it(`when data is ${googleAnalyticsDataType} terminal is ${isInteractive ? "" : "not "}interactive and --analyticsClient is passed`, async () => { |
| 283 | + const analyticsClient = "AnalyticsClient"; |
| 284 | + |
| 285 | + const { testInjector, opts } = setupTest(getExpectedResult(googleAnalyticsDataType, analyticsClient), getDataToSend(googleAnalyticsDataType), { isInteractive }); |
| 286 | + const options = testInjector.resolve<IOptions>("options"); |
| 287 | + options.analyticsClient = analyticsClient; |
| 288 | + |
| 289 | + await assertExpectedResult(testInjector, opts); |
| 290 | + }); |
| 291 | + }); |
| 292 | + }); |
| 293 | + }); |
| 294 | + }); |
| 295 | +}); |
0 commit comments