public void Dispose()
            {
                LocalResourceUtils.DeleteDirectoryTree(
                    Path.Combine(
                        SettingsUtility.GetPluginsCacheFolder(),
                        CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash(_pluginFilePath))),
                    new List <string>());
                PluginManager.Dispose();
                _testDirectory.Dispose();

                _reader.Verify();
                _pluginDiscoverer.Verify();

                foreach (var expectation in _expectations)
                {
                    _connection.Verify(x => x.SendRequestAndReceiveResponseAsync <GetOperationClaimsRequest, GetOperationClaimsResponse>(
                                           It.Is <MessageMethod>(m => m == MessageMethod.GetOperationClaims),
                                           It.Is <GetOperationClaimsRequest>(
                                               g => g.PackageSourceRepository == expectation.SourceRepository.PackageSource.Source),
                                           It.IsAny <CancellationToken>()), Times.Once());

                    var expectedSetCredentialsRequestCalls = expectation.OperationClaims.Any()
                        ? Times.Once() : Times.Never();

                    _connection.Verify(x => x.SendRequestAndReceiveResponseAsync <SetCredentialsRequest, SetCredentialsResponse>(
                                           It.Is <MessageMethod>(m => m == MessageMethod.SetCredentials),
                                           It.Is <SetCredentialsRequest>(
                                               g => g.PackageSourceRepository == expectation.SourceRepository.PackageSource.Source),
                                           It.IsAny <CancellationToken>()), expectedSetCredentialsRequestCalls);
                }

                _plugin.Verify();
                _factory.Verify();
            }
        /// <summary>
        /// Loads and processes the contet from the generated file if it exists.
        /// Even after this method is invoked, the operation claims might be null.
        /// </summary>
        public void LoadFromFile()
        {
            Stream content = null;

            try
            {
                content = CachingUtility.ReadCacheFile(MaxAge, CacheFileName);
                if (content != null)
                {
                    ProcessContent(content);
                }
            }
            finally
            {
                content?.Dispose();
            }
        }
        public void Dispose()
        {
            LocalResourceUtils.DeleteDirectoryTree(
                Path.Combine(
                    SettingsUtility.GetPluginsCacheFolder(),
                    CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash(_pluginFilePath))),
                new List <string>());
            PluginManager.Dispose();

            _reader.Verify();
            _pluginDiscoverer.Verify();
            if (_expectations.PluginLaunched)
            {
                _connection.Verify(x => x.SendRequestAndReceiveResponseAsync <GetOperationClaimsRequest, GetOperationClaimsResponse>(
                                       It.Is <MessageMethod>(m => m == MessageMethod.GetOperationClaims),
                                       It.Is <GetOperationClaimsRequest>(
                                           g => g.PackageSourceRepository == null), // The source repository should be null in the context of credential plugins
                                       It.IsAny <CancellationToken>()), Times.Once());

                if (_expectations.Success)
                {
                    _connection.Verify(x => x.SendRequestAndReceiveResponseAsync <GetAuthenticationCredentialsRequest, GetAuthenticationCredentialsResponse>(
                                           It.Is <MessageMethod>(m => m == MessageMethod.GetAuthenticationCredentials),
                                           It.IsAny <GetAuthenticationCredentialsRequest>(),
                                           It.IsAny <CancellationToken>()), Times.Once());
                }

                if (_expectations.ProxyUsername != null && _expectations.ProxyPassword != null)
                {
                    _connection.Verify(x => x.SendRequestAndReceiveResponseAsync <SetCredentialsRequest, SetCredentialsResponse>(
                                           It.Is <MessageMethod>(m => m == MessageMethod.SetCredentials),
                                           It.Is <SetCredentialsRequest>(e => e.PackageSourceRepository.Equals(_expectations.Uri.AbsolutePath) && e.Password == null && e.Username == null && e.ProxyPassword.Equals(_expectations.ProxyPassword) && e.ProxyUsername.Equals(_expectations.ProxyUsername)),
                                           It.IsAny <CancellationToken>()),
                                       Times.Once());
                }
            }
            _connection.Verify();

            _plugin.Verify();
            _factory.Verify();

            _testDirectory.Dispose();
        }
        /// <summary>
        /// Updates the cache file with the current value in the operation claims if the operationn claims is not null.
        /// </summary>
        /// <returns>Task</returns>
        public async Task UpdateCacheFileAsync()
        {
            if (OperationClaims != null)
            {
                // Make sure the cache file directory is created before writing a file to it.
                DirectoryUtility.CreateSharedDirectory(RootFolder);

                // The update of a cached file is divided into two steps:
                // 1) Delete the old file.
                // 2) Create a new file with the same name.
                using (var fileStream = new FileStream(
                           NewCacheFileName,
                           FileMode.Create,
                           FileAccess.ReadWrite,
                           FileShare.None,
                           CachingUtility.BufferSize,
                           useAsync: true))
                {
                    var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(OperationClaims, Formatting.Indented));
                    await fileStream.WriteAsync(json, 0, json.Length).ConfigureAwait(false);
                }

                if (File.Exists(CacheFileName))
                {
                    // Process B can perform deletion on an opened file if the file is opened by process A
                    // with FileShare.Delete flag. However, the file won't be actually deleted until A close it.
                    // This special feature can cause race condition, so we never delete an opened file.
                    if (!CachingUtility.IsFileAlreadyOpen(CacheFileName))
                    {
                        File.Delete(CacheFileName);
                    }
                }

                // If the destination file doesn't exist, we can safely perform moving operation.
                // Otherwise, moving operation will fail.
                if (!File.Exists(CacheFileName))
                {
                    File.Move(
                        NewCacheFileName,
                        CacheFileName);
                }
            }
        }
        public async Task PluginCacheEntry_DoesNotDeleteAnOpenedFile()
        {
            var list = new List <OperationClaim>()
            {
                OperationClaim.Authentication
            };

            using (var testDirectory = TestDirectory.Create())
            {
                var entry = new PluginCacheEntry(testDirectory.Path, "a", "b");
                entry.LoadFromFile();
                entry.OperationClaims = list;
                await entry.UpdateCacheFileAsync();

                var CacheFileName = Path.Combine(
                    Path.Combine(testDirectory.Path, CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash("a", false))),
                    CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash("b", false)) + ".dat");

                Assert.True(File.Exists(CacheFileName));

                using (var fileStream = new FileStream(
                           CacheFileName,
                           FileMode.Open,
                           FileAccess.ReadWrite,
                           FileShare.None,
                           CachingUtility.BufferSize,
                           useAsync: true))
                {
                    list.Add(OperationClaim.DownloadPackage);
                    entry.OperationClaims = list;
                    await entry.UpdateCacheFileAsync(); // this should not update
                }

                entry.LoadFromFile();
                Assert.True(EqualityUtility.SequenceEqualWithNullCheck(entry.OperationClaims, new List <OperationClaim>()
                {
                    OperationClaim.Authentication
                }));
            }
        }
 /// <summary>
 /// Create a plugin cache entry.
 /// </summary>
 /// <param name="rootCacheFolder">The root cache folder, normally /localappdata/nuget/plugins-cache</param>
 /// <param name="pluginFilePath">The full plugin file path, which will be used to create a key for the folder created in the root folder itself </param>
 /// <param name="requestKey">A unique request key for the operation claims. Ideally the packageSourceRepository value of the PluginRequestKey. Example https://protected.package.feed/index.json, or Source-Agnostic</param>
 public PluginCacheEntry(string rootCacheFolder, string pluginFilePath, string requestKey)
 {
     RootFolder       = Path.Combine(rootCacheFolder, CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash(pluginFilePath, addIdentifiableCharacters: false)));
     CacheFileName    = Path.Combine(RootFolder, CachingUtility.RemoveInvalidFileNameChars(CachingUtility.ComputeHash(requestKey, addIdentifiableCharacters: false)) + ".dat");
     NewCacheFileName = CacheFileName + "-new";
 }