From 4d8c2ecbe6a7dee64f2dfe47ce0f2b5aff2c1821 Mon Sep 17 00:00:00 2001 From: Christy Henriksson Date: Fri, 27 Oct 2017 07:58:24 -0700 Subject: [PATCH] Fix readme telemetry (#4905) --- .../App_Start/DefaultDependenciesModule.cs | 17 +- src/NuGetGallery/App_Start/OwinStartup.cs | 5 +- .../Diagnostics/DiagnosticsService.cs | 13 +- .../Diagnostics/TraceDiagnosticsSource.cs | 7 +- src/NuGetGallery/NuGetGallery.csproj | 3 +- .../OData/QueryAllowed/ODataQueryVerifier.cs | 11 +- .../Services/CloudDownloadCountService.cs | 11 +- src/NuGetGallery/Services/ITelemetryClient.cs | 18 ++ .../Services/ITelemetryService.cs | 9 + .../Services/TelemetryClientWrapper.cs | 56 ++++++ src/NuGetGallery/Services/TelemetryService.cs | 53 +++-- src/NuGetGallery/Telemetry/QuietLog.cs | 2 + src/NuGetGallery/Telemetry/Telemetry.cs | 43 ----- tests/NuGetGallery.Facts/Framework/Fakes.cs | 10 +- .../Services/TelemetryServiceFacts.cs | 182 +++++++++++++++++- 15 files changed, 348 insertions(+), 92 deletions(-) create mode 100644 src/NuGetGallery/Services/ITelemetryClient.cs create mode 100644 src/NuGetGallery/Services/TelemetryClientWrapper.cs delete mode 100644 src/NuGetGallery/Telemetry/Telemetry.cs diff --git a/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs b/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs index ece7e29543..92c72423fc 100644 --- a/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs +++ b/src/NuGetGallery/App_Start/DefaultDependenciesModule.cs @@ -39,7 +39,12 @@ public class DefaultDependenciesModule : Module [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:CyclomaticComplexity", Justification = "This code is more maintainable in the same function.")] protected override void Load(ContainerBuilder builder) { - var diagnosticsService = new DiagnosticsService(); + var telemetryClient = TelemetryClientWrapper.Instance; + builder.RegisterInstance(telemetryClient) + .As() + .SingleInstance(); + + var diagnosticsService = new DiagnosticsService(telemetryClient); builder.RegisterInstance(diagnosticsService) .AsSelf() .As() @@ -295,7 +300,7 @@ protected override void Load(ContainerBuilder builder) defaultAuditingService = GetAuditingServiceForLocalFileSystem(configuration); break; case StorageType.AzureStorage: - ConfigureForAzureStorage(builder, configuration); + ConfigureForAzureStorage(builder, configuration, telemetryClient); defaultAuditingService = GetAuditingServiceForAzureStorage(builder, configuration); break; } @@ -502,7 +507,7 @@ private static IAuditingService GetAuditingServiceForLocalFileSystem(IGalleryCon return new FileSystemAuditingService(auditingPath, AuditActor.GetAspNetOnBehalfOfAsync); } - private static void ConfigureForAzureStorage(ContainerBuilder builder, IGalleryConfigurationService configuration) + private static void ConfigureForAzureStorage(ContainerBuilder builder, IGalleryConfigurationService configuration, ITelemetryClient telemetryClient) { /// The goal here is to initialize a and /// instance for each unique connection string. Each dependent of (that @@ -563,7 +568,11 @@ private static void ConfigureForAzureStorage(ContainerBuilder builder, IGalleryC .SingleInstance(); // when running on Windows Azure, download counts come from the downloads.v1.json blob - var downloadCountService = new CloudDownloadCountService(configuration.Current.AzureStorage_Statistics_ConnectionString, configuration.Current.AzureStorageReadAccessGeoRedundant); + var downloadCountService = new CloudDownloadCountService( + telemetryClient, + configuration.Current.AzureStorage_Statistics_ConnectionString, + configuration.Current.AzureStorageReadAccessGeoRedundant); + builder.RegisterInstance(downloadCountService) .AsSelf() .As() diff --git a/src/NuGetGallery/App_Start/OwinStartup.cs b/src/NuGetGallery/App_Start/OwinStartup.cs index 882254beda..15edafcc60 100644 --- a/src/NuGetGallery/App_Start/OwinStartup.cs +++ b/src/NuGetGallery/App_Start/OwinStartup.cs @@ -78,9 +78,10 @@ public static void Configuration(IAppBuilder app) return processor; }); - + + var telemetry = dependencyResolver.GetService(); telemetryProcessorChainBuilder.Use( - next => new ExceptionTelemetryProcessor(next, Telemetry.TelemetryClient)); + next => new ExceptionTelemetryProcessor(next, telemetry.UnderlyingClient)); // Note: sampling rate must be a factor 100/N where N is a whole number // e.g.: 50 (= 100/2), 33.33 (= 100/3), 25 (= 100/4), ... diff --git a/src/NuGetGallery/Diagnostics/DiagnosticsService.cs b/src/NuGetGallery/Diagnostics/DiagnosticsService.cs index 0784afbf13..06029ed49a 100644 --- a/src/NuGetGallery/Diagnostics/DiagnosticsService.cs +++ b/src/NuGetGallery/Diagnostics/DiagnosticsService.cs @@ -8,11 +8,20 @@ namespace NuGetGallery.Diagnostics { public class DiagnosticsService : IDiagnosticsService { - public DiagnosticsService() + private readonly ITelemetryClient _telemetryClient; + + public DiagnosticsService(ITelemetryClient telemetryClient) { + _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); + Trace.AutoFlush = true; } + // Test constructor + internal DiagnosticsService() : this(TelemetryClientWrapper.Instance) + { + } + public IDiagnosticsSource GetSource(string name) { if (String.IsNullOrEmpty(name)) @@ -20,7 +29,7 @@ public IDiagnosticsSource GetSource(string name) throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Strings.ParameterCannotBeNullOrEmpty, "name"), nameof(name)); } - return new TraceDiagnosticsSource(name); + return new TraceDiagnosticsSource(name, _telemetryClient); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Diagnostics/TraceDiagnosticsSource.cs b/src/NuGetGallery/Diagnostics/TraceDiagnosticsSource.cs index 62ea746a44..afd811647c 100644 --- a/src/NuGetGallery/Diagnostics/TraceDiagnosticsSource.cs +++ b/src/NuGetGallery/Diagnostics/TraceDiagnosticsSource.cs @@ -18,11 +18,14 @@ public class TraceDiagnosticsSource : IDiagnosticsSource, IDisposable { private const string ObjectName = "TraceDiagnosticsSource"; private TraceSource _source; + private ITelemetryClient _telemetryClient; public string Name { get; private set; } - public TraceDiagnosticsSource(string name) + public TraceDiagnosticsSource(string name, ITelemetryClient telemetryClient) { + _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); + Name = name; _source = new TraceSource(name, SourceLevels.All); @@ -43,7 +46,7 @@ public virtual void ExceptionEvent(Exception exception) throw new ArgumentNullException(nameof(exception)); } - Telemetry.TrackException(exception); + _telemetryClient.TrackException(exception); } /// diff --git a/src/NuGetGallery/NuGetGallery.csproj b/src/NuGetGallery/NuGetGallery.csproj index da11128f26..9277eabb8d 100644 --- a/src/NuGetGallery/NuGetGallery.csproj +++ b/src/NuGetGallery/NuGetGallery.csproj @@ -785,6 +785,7 @@ + @@ -869,6 +870,7 @@ + @@ -986,7 +988,6 @@ - diff --git a/src/NuGetGallery/OData/QueryAllowed/ODataQueryVerifier.cs b/src/NuGetGallery/OData/QueryAllowed/ODataQueryVerifier.cs index 7d0aea71a9..29ccf3a9ff 100644 --- a/src/NuGetGallery/OData/QueryAllowed/ODataQueryVerifier.cs +++ b/src/NuGetGallery/OData/QueryAllowed/ODataQueryVerifier.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Web.Http.OData.Query; namespace NuGetGallery.OData.QueryFilter @@ -124,12 +123,10 @@ public static bool AreODataOptionsAllowed(ODataQueryOptions od } catch (Exception ex) { - var telemetryProperties = new Dictionary(); - telemetryProperties.Add(TelemetryService.CallContext, $"{telemetryContext}:{nameof(AreODataOptionsAllowed)}"); - telemetryProperties.Add(TelemetryService.IsEnabled, $"{isFeatureEnabled}"); - - // Log and do not throw - Telemetry.TrackException(ex, telemetryProperties); + _telemetryService.TrackException(ex, properties => { + properties.Add(TelemetryService.CallContext, $"{telemetryContext}:{nameof(AreODataOptionsAllowed)}"); + properties.Add(TelemetryService.IsEnabled, $"{isFeatureEnabled}"); + }); } _telemetryService.TrackODataQueryFilterEvent( diff --git a/src/NuGetGallery/Services/CloudDownloadCountService.cs b/src/NuGetGallery/Services/CloudDownloadCountService.cs index d2b88cf19a..8d019752d0 100644 --- a/src/NuGetGallery/Services/CloudDownloadCountService.cs +++ b/src/NuGetGallery/Services/CloudDownloadCountService.cs @@ -20,6 +20,7 @@ public class CloudDownloadCountService : IDownloadCountService private const string DownloadCountBlobName = "downloads.v1.json"; private const string TelemetryOriginForRefreshMethod = "CloudDownloadCountService.Refresh"; + private readonly ITelemetryClient _telemetryClient; private readonly string _connectionString; private readonly bool _readAccessGeoRedundant; @@ -30,8 +31,10 @@ public class CloudDownloadCountService : IDownloadCountService public DateTime LastRefresh { get; protected set; } - public CloudDownloadCountService(string connectionString, bool readAccessGeoRedundant) + public CloudDownloadCountService(ITelemetryClient telemetryClient, string connectionString, bool readAccessGeoRedundant) { + _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); + _connectionString = connectionString; _readAccessGeoRedundant = readAccessGeoRedundant; } @@ -181,7 +184,7 @@ private void RefreshCore() } catch (JsonReaderException ex) { - Telemetry.TrackException(ex, new Dictionary + _telemetryClient.TrackException(ex, new Dictionary { { "Origin", TelemetryOriginForRefreshMethod }, { "AdditionalInfo", "Invalid entry found in downloads.v1.json." } @@ -191,7 +194,7 @@ private void RefreshCore() } catch (JsonReaderException ex) { - Telemetry.TrackException(ex, new Dictionary + _telemetryClient.TrackException(ex, new Dictionary { { "Origin", TelemetryOriginForRefreshMethod }, { "AdditionalInfo", "Data present in downloads.v1.json is invalid. Couldn't get download data." } @@ -201,7 +204,7 @@ private void RefreshCore() } catch (Exception ex) { - Telemetry.TrackException(ex, new Dictionary + _telemetryClient.TrackException(ex, new Dictionary { { "Origin", TelemetryOriginForRefreshMethod }, { "AdditionalInfo", "Unknown exception." } diff --git a/src/NuGetGallery/Services/ITelemetryClient.cs b/src/NuGetGallery/Services/ITelemetryClient.cs new file mode 100644 index 0000000000..8a88ad5536 --- /dev/null +++ b/src/NuGetGallery/Services/ITelemetryClient.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace NuGetGallery +{ + /// + /// Interface for the Application Insights TelemetryClient class, for unit tests. + /// + public interface ITelemetryClient + { + void TrackEvent(string eventName, IDictionary properties = null, IDictionary metrics = null); + + void TrackException(Exception exception, IDictionary properties = null, IDictionary metrics = null); + } +} diff --git a/src/NuGetGallery/Services/ITelemetryService.cs b/src/NuGetGallery/Services/ITelemetryService.cs index 77164b1d0a..d2846bf29a 100644 --- a/src/NuGetGallery/Services/ITelemetryService.cs +++ b/src/NuGetGallery/Services/ITelemetryService.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Security.Principal; namespace NuGetGallery @@ -18,6 +19,14 @@ public interface ITelemetryService void TrackVerifyPackageKeyEvent(string packageId, string packageVersion, User user, IIdentity identity, int statusCode); + /// + /// Create a trace for an exception. These are informational for support requests. + /// void TraceException(Exception exception); + + /// + /// Create a log for an exception. These are warnings for live site. + /// + void TrackException(Exception exception, Action> addProperties); } } \ No newline at end of file diff --git a/src/NuGetGallery/Services/TelemetryClientWrapper.cs b/src/NuGetGallery/Services/TelemetryClientWrapper.cs new file mode 100644 index 0000000000..ebc6822b30 --- /dev/null +++ b/src/NuGetGallery/Services/TelemetryClientWrapper.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.ApplicationInsights; + +namespace NuGetGallery +{ + /// + /// Wrapper for the Application Insights TelemetryClient class. + /// + public class TelemetryClientWrapper : ITelemetryClient + { + private static Lazy Singleton = new Lazy(() => new TelemetryClientWrapper()); + + internal static TelemetryClientWrapper Instance + { + get + { + return Singleton.Value; + } + } + + private TelemetryClientWrapper() + { + UnderlyingClient = new TelemetryClient(); + } + + internal TelemetryClient UnderlyingClient { get; } + + public void TrackEvent(string eventName, IDictionary properties = null, IDictionary metrics = null) + { + try + { + UnderlyingClient.TrackEvent(eventName, properties, metrics); + } + catch + { + // logging failed, don't allow exception to escape + } + } + + public void TrackException(Exception exception, IDictionary properties = null, IDictionary metrics = null) + { + try + { + UnderlyingClient.TrackException(exception, properties, metrics); + } + catch + { + // logging failed, don't allow exception to escape + } + } + } +} \ No newline at end of file diff --git a/src/NuGetGallery/Services/TelemetryService.cs b/src/NuGetGallery/Services/TelemetryService.cs index f75436dfda..c6eb46430e 100644 --- a/src/NuGetGallery/Services/TelemetryService.cs +++ b/src/NuGetGallery/Services/TelemetryService.cs @@ -5,20 +5,24 @@ using System.Collections.Generic; using System.Security.Principal; using System.Web; +using Elmah; using NuGetGallery.Diagnostics; namespace NuGetGallery { public class TelemetryService : ITelemetryService { - private IDiagnosticsSource _trace; + internal class Events + { + public const string ODataQueryFilter = "ODataQueryFilter"; + public const string PackagePush = "PackagePush"; + public const string CreatePackageVerificationKey = "CreatePackageVerificationKey"; + public const string VerifyPackageKey = "VerifyPackageKey"; + public const string PackageReadMeChanged = "PackageReadMeChanged"; + } - // Event types - public const string ODataQueryFilterEvent = "ODataQueryFilter"; - public const string PackagePushEvent = "PackagePush"; - public const string CreatePackageVerificationKeyEvent = "CreatePackageVerificationKeyEvent"; - public const string VerifyPackageKeyEvent = "VerifyPackageKeyEvent"; - public const string PackageReadMeChangeEvent = "PackageReadMeChanged"; + private IDiagnosticsSource _diagnosticsSource; + private ITelemetryClient _telemetryClient; // ODataQueryFilter properties public const string CallContext = "CallContext"; @@ -45,18 +49,20 @@ public class TelemetryService : ITelemetryService public const string ReadMeSourceType = "ReadMeSourceType"; public const string ReadMeState = "ReadMeState"; - public TelemetryService(IDiagnosticsService diagnosticsService) + public TelemetryService(IDiagnosticsService diagnosticsService, ITelemetryClient telemetryClient = null) { if (diagnosticsService == null) { throw new ArgumentNullException(nameof(diagnosticsService)); } - _trace = diagnosticsService.GetSource("TelemetryService"); + _telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); + + _diagnosticsSource = diagnosticsService.GetSource("TelemetryService"); } // Used by ODataQueryVerifier. Should consider refactoring to make this non-static. - internal TelemetryService() : this(new DiagnosticsService()) + internal TelemetryService() : this(new DiagnosticsService(), TelemetryClientWrapper.Instance) { } @@ -67,12 +73,12 @@ public void TraceException(Exception exception) throw new ArgumentNullException(nameof(exception)); } - _trace.Warning(exception.ToString()); + _diagnosticsSource.Warning(exception.ToString()); } public void TrackODataQueryFilterEvent(string callContext, bool isEnabled, bool isAllowed, string queryPattern) { - TrackEvent(ODataQueryFilterEvent, properties => + TrackEvent(Events.ODataQueryFilter, properties => { properties.Add(CallContext, callContext); properties.Add(IsEnabled, $"{isEnabled}"); @@ -93,8 +99,8 @@ public void TrackPackageReadMeChangeEvent(Package package, string readMeSourceTy { throw new ArgumentNullException(nameof(readMeSourceType)); } - - TrackEvent(PackagePushEvent, properties => { + + TrackEvent(Events.PackageReadMeChanged, properties => { properties.Add(PackageId, package.PackageRegistration.Id); properties.Add(PackageVersion, package.Version); properties.Add(ReadMeSourceType, readMeSourceType); @@ -119,7 +125,7 @@ public void TrackPackagePushEvent(Package package, User user, IIdentity identity throw new ArgumentNullException(nameof(identity)); } - TrackEvent(PackagePushEvent, properties => { + TrackEvent(Events.PackagePush, properties => { properties.Add(ClientVersion, GetClientVersion()); properties.Add(ProtocolVersion, GetProtocolVersion()); properties.Add(ClientInformation, GetClientInformation()); @@ -144,7 +150,7 @@ public void TrackCreatePackageVerificationKeyEvent(string packageId, string pack throw new ArgumentNullException(nameof(identity)); } - TrackEvent(CreatePackageVerificationKeyEvent, properties => { + TrackEvent(Events.CreatePackageVerificationKey, properties => { properties.Add(ClientVersion, GetClientVersion()); properties.Add(ProtocolVersion, GetProtocolVersion()); properties.Add(ClientInformation, GetClientInformation()); @@ -168,7 +174,7 @@ public void TrackVerifyPackageKeyEvent(string packageId, string packageVersion, throw new ArgumentNullException(nameof(identity)); } - TrackEvent(VerifyPackageKeyEvent, properties => + TrackEvent(Events.VerifyPackageKey, properties => { properties.Add(PackageId, packageId); properties.Add(PackageVersion, packageVersion); @@ -210,13 +216,22 @@ private static string GetApiKeyCreationDate(User user, IIdentity identity) return apiKey?.Created.ToString("O") ?? "N/A"; } - private static void TrackEvent(string eventName, Action> addProperties) + protected virtual void TrackEvent(string eventName, Action> addProperties) + { + var telemetryProperties = new Dictionary(); + + addProperties(telemetryProperties); + + _telemetryClient.TrackEvent(eventName, telemetryProperties, metrics: null); + } + + public void TrackException(Exception exception, Action> addProperties) { var telemetryProperties = new Dictionary(); addProperties(telemetryProperties); - Telemetry.TrackEvent(eventName, telemetryProperties, metrics: null); + _telemetryClient.TrackException(exception, telemetryProperties, metrics: null); } } } \ No newline at end of file diff --git a/src/NuGetGallery/Telemetry/QuietLog.cs b/src/NuGetGallery/Telemetry/QuietLog.cs index 0d9f6323af..21885339a1 100644 --- a/src/NuGetGallery/Telemetry/QuietLog.cs +++ b/src/NuGetGallery/Telemetry/QuietLog.cs @@ -10,6 +10,8 @@ namespace NuGetGallery { internal static class QuietLog { + public static ITelemetryClient Telemetry = TelemetryClientWrapper.Instance; + public static void LogHandledException(Exception e) { var aggregateExceptionId = Guid.NewGuid().ToString(); diff --git a/src/NuGetGallery/Telemetry/Telemetry.cs b/src/NuGetGallery/Telemetry/Telemetry.cs deleted file mode 100644 index 08ace7c533..0000000000 --- a/src/NuGetGallery/Telemetry/Telemetry.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.ApplicationInsights; - -namespace NuGetGallery -{ - internal static class Telemetry - { - static Telemetry() - { - TelemetryClient = new TelemetryClient(); - } - - internal static TelemetryClient TelemetryClient { get; } - - public static void TrackEvent(string eventName, IDictionary properties = null, IDictionary metrics = null) - { - try - { - TelemetryClient.TrackEvent(eventName, properties, metrics); - } - catch - { - // logging failed, don't allow exception to escape - } - } - - public static void TrackException(Exception exception, IDictionary properties = null, IDictionary metrics = null) - { - try - { - TelemetryClient.TrackException(exception, properties, metrics); - } - catch - { - // logging failed, don't allow exception to escape - } - } - } -} \ No newline at end of file diff --git a/tests/NuGetGallery.Facts/Framework/Fakes.cs b/tests/NuGetGallery.Facts/Framework/Fakes.cs index 09227086ac..55623e8156 100644 --- a/tests/NuGetGallery.Facts/Framework/Fakes.cs +++ b/tests/NuGetGallery.Facts/Framework/Fakes.cs @@ -107,11 +107,11 @@ public Fakes() { Id = "FakePackage", Owners = new List {Owner}, - Packages = new List - { - new Package {Version = "1.0"}, - new Package {Version = "2.0"} - } + }; + Package.Packages = new List + { + new Package { Version = "1.0", PackageRegistration = Package }, + new Package { Version = "2.0", PackageRegistration = Package } }; } diff --git a/tests/NuGetGallery.Facts/Services/TelemetryServiceFacts.cs b/tests/NuGetGallery.Facts/Services/TelemetryServiceFacts.cs index 9d2bef7b95..3dfe81a30e 100644 --- a/tests/NuGetGallery.Facts/Services/TelemetryServiceFacts.cs +++ b/tests/NuGetGallery.Facts/Services/TelemetryServiceFacts.cs @@ -2,11 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Moq; using NuGetGallery.Diagnostics; +using NuGetGallery.Framework; using Xunit; +using TrackAction = System.Action; + namespace NuGetGallery { public class TelemetryServiceFacts @@ -20,6 +25,167 @@ public void ThrowsIfDiagnosticsServiceIsNull() } } + public class TheTrackEventMethod : BaseFacts + { + private static Fakes fakes = new Fakes(); + + public static IEnumerable TrackEventNames_Data + { + get + { + var package = fakes.Package.Packages.First(); + var identity = Fakes.ToIdentity(fakes.User); + + yield return new object[] { "ODataQueryFilter", + (TrackAction)(s => s.TrackODataQueryFilterEvent("callContext", true, true, "queryPattern")) + }; + + yield return new object[] { "PackagePush", + (TrackAction)(s => s.TrackPackagePushEvent(package, fakes.User, identity)) + }; + + yield return new object[] { "CreatePackageVerificationKey", + (TrackAction)(s => s.TrackCreatePackageVerificationKeyEvent(fakes.Package.Id, package.Version, fakes.User, identity)) + }; + + yield return new object[] { "VerifyPackageKey", + (TrackAction)(s => s.TrackVerifyPackageKeyEvent(fakes.Package.Id, package.Version, fakes.User, identity, 0)) + }; + + yield return new object[] { "PackageReadMeChanged", + (TrackAction)(s => s.TrackPackageReadMeChangeEvent(package, "written", PackageEditReadMeState.Changed)) + }; + } + } + + [Fact] + public void TrackEventNamesIncludesAllEvents() + { + var count = typeof(TelemetryService.Events).GetFields().Length; + var testData = TrackEventNames_Data; + + Assert.Equal(count, testData.Count()); + } + + [Theory] + [MemberData(nameof(TrackEventNames_Data))] + public void TrackEventNames(string eventName, TrackAction track) + { + // Arrange + var service = CreateService(); + + // Act + track(service); + + // Assert + service.TelemetryClient.Verify(c => c.TrackEvent(eventName, + It.IsAny>(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void TrackPackageReadMeChangeEventThrowsIfPackageIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackPackageReadMeChangeEvent(null, "written", PackageEditReadMeState.Changed)); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void TrackPackageReadMeChangeEventThrowsIfSourceTypeIsNullOrEmpty(string sourceType) + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackPackageReadMeChangeEvent(fakes.Package.Packages.First(), sourceType, PackageEditReadMeState.Changed)); + } + + [Fact] + public void TrackPackagePushEventThrowsIfPackageIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackPackagePushEvent(null, fakes.User, Fakes.ToIdentity(fakes.User))); + } + + [Fact] + public void TrackPackagePushEventThrowsIfUserIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackPackagePushEvent(fakes.Package.Packages.First(), null, Fakes.ToIdentity(fakes.User))); + } + + [Fact] + public void TrackPackagePushEventThrowsIfIdentityIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackPackagePushEvent(fakes.Package.Packages.First(), fakes.User, null)); + } + + [Fact] + public void TrackCreatePackageVerificationKeyEventThrowsIfUserIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackCreatePackageVerificationKeyEvent("id", "1.0.0", null, Fakes.ToIdentity(fakes.User))); + } + + [Fact] + public void TrackCreatePackageVerificationKeyEventThrowsIfIdentityIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackCreatePackageVerificationKeyEvent("id", "1.0.0", fakes.User, null)); + } + + [Fact] + public void TrackVerifyPackageKeyEventThrowsIfUserIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackVerifyPackageKeyEvent("id", "1.0.0", null, Fakes.ToIdentity(fakes.User), 200)); + } + + [Fact] + public void TrackVerifyPackageKeyEventThrowsIfIdentityIsNull() + { + // Arrange + var service = CreateService(); + + // Act & Assert + Assert.Throws(() => + service.TrackVerifyPackageKeyEvent("id", "1.0.0", fakes.User, null, 200)); + } + } + public class TheTraceExceptionMethod : BaseFacts { [Fact] @@ -58,13 +224,15 @@ public class BaseFacts { public class TelemetryServiceWrapper : TelemetryService { - public TelemetryServiceWrapper(IDiagnosticsService diagnosticsService) - : base(diagnosticsService) + public TelemetryServiceWrapper(IDiagnosticsService diagnosticsService, ITelemetryClient telemetryClient) + : base(diagnosticsService, telemetryClient) { } public Mock TraceSource { get; set; } + public Mock TelemetryClient { get; set; } + public string LastTraceMessage { get; set; } } @@ -72,13 +240,21 @@ public TelemetryServiceWrapper CreateService() { var traceSource = new Mock(); var traceService = new Mock(); + var telemetryClient = new Mock(); traceService.Setup(s => s.GetSource(It.IsAny())) .Returns(traceSource.Object); - var telemetryService = new TelemetryServiceWrapper(traceService.Object); + telemetryClient.Setup(c => c.TrackEvent( + It.IsAny(), + It.IsAny>(), + It.IsAny>())) + .Verifiable(); + + var telemetryService = new TelemetryServiceWrapper(traceService.Object, telemetryClient.Object); telemetryService.TraceSource = traceSource; + telemetryService.TelemetryClient = telemetryClient; traceSource.Setup(t => t.TraceEvent( It.IsAny(),