public async Task FileSystemPublicSuffixDataSource_GetDataAsync_WhenDiskDataExistsAndIsStaleButNotExpired_ReturnsStaleDataAndFetchesNewFromUpstream()
        {
            Mocked<IFileSystem>()
                .Setup(fs => fs.Exists(_cacheFilePath))
                .Returns(true);
            Mocked<IFileSystem>()
                .Setup(fs => fs.GetLastWriteTime(_cacheFilePath))
                .Returns(DateTime.Now.AddDays(-11));
            Mocked<IFileSystem>()
                .Setup(fs => fs.OpenRead(_cacheFilePath))
                .Returns(new MemoryStream(Encoding.UTF8.GetBytes("{ \"com\": { } }")));

            var latestData = new DomainSegmentTree
            {
                Children = new DomainSegmentNodeCollection
                {
                    new DomainSegmentNode {Segment = "com"},
                    new DomainSegmentNode {Segment = "net"}
                }
            };
            var upstreamSource = new Mock<IPublicSuffixDataSource>();
            upstreamSource.Setup(s => s.GetDataAsync()).Returns(Task.FromResult(latestData));
            Subject.Upstream = upstreamSource.Object;

            var data = await Subject.GetDataAsync();

            data.Children.Should().HaveCount(1);
            data.Contains("com").Should().BeTrue();
            upstreamSource.Verify(s => s.GetDataAsync());

            // Wait for cache to be asynchronously written
            await Task.Delay(100);
            Mocked<IFileSystem>().Verify(fs => fs.OpenWrite(_cacheFilePath));
        }
        public async Task GetDataAsync_AlwaysGetsDataFromUpstreamAtFirst()
        {
            var upstreamData = new DomainSegmentTree();
            Mocked<IPublicSuffixDataSource>()
                .Setup(s => s.GetDataAsync())
                .Returns(Task.FromResult(upstreamData));

            var data = await Subject.GetDataAsync();

            data.Should().Be(upstreamData);
        }
        protected override Task CacheUpstreamDataAsync(DomainSegmentTree data)
        {
            return Task.Run(() => {
                var serializer = new JsonSerializer();

                using (var stream = _fileSystem.OpenWrite(_config.CacheFilePath))
                using (var writer = new StreamWriter(stream))
                {
                    var json = ConvertToJsonRecursive(data);
                    serializer.Serialize(writer, json);
                }
            });
        }
        public async Task GetDataAsync_CachesUpstreamData()
        {
            var upstreamData = new DomainSegmentTree();
            Mocked<IPublicSuffixDataSource>()
                .Setup(s => s.GetDataAsync())
                .Returns(Task.FromResult(upstreamData));
            Subject.Upstream = Mocked<IPublicSuffixDataSource>().Object;

            await Subject.GetDataAsync();
            await Task.Delay(100);

            await Subject.GetDataAsync();

            Mocked<IPublicSuffixDataSource>().Verify(ds => ds.GetDataAsync(), Times.Once());
        }
 private static IEnumerable<string> GetTldSegments(DomainSegmentTree tree, IEnumerable<string> segments)
 {
     var node = tree;
     foreach (var segment in segments)
     {
         if (node.Contains(segment))
         {
             yield return segment;
             node = node[segment];
         }
         else if (!node.Contains("!" + segment) && node.Contains("*"))
         {
             yield return segment;
             node = node["*"];
         }
         else
         {
             break;
         }
     }
 }
        protected override async Task<DomainSegmentTree> GetCurrentDataAsync()
        {
            var tree = new DomainSegmentTree();

            using (var client = _httpFactory.GetHttpClient())
            using (var stream = await client.GetStreamAsync(_config.DataSourceUrl))
            using (var reader = new StreamReader(stream))
            {
                while (!reader.EndOfStream)
                {
                    var line = await reader.ReadLineAsync();
                    if (!IsOfSubstance(line)) continue;

                    line.Split('.')
                        .Reverse()
                        .Aggregate(tree, (current, segment) => current[segment] ?? current.Add(segment));
                }
            }

            return tree;
        }
 protected abstract Task CacheUpstreamDataAsync(DomainSegmentTree data);
 protected override Task CacheUpstreamDataAsync(DomainSegmentTree data)
 {
     throw new NotImplementedException();
 }
 private static JObject ConvertToJsonRecursive(DomainSegmentTree tree)
 {
     return new JObject(
         from node in tree.Children ?? Enumerable.Empty<DomainSegmentNode>()
         select new JProperty(node.Segment, ConvertToJsonRecursive(node)));
 }
 protected override async Task CacheUpstreamDataAsync(DomainSegmentTree data)
 {
     _cache = data;
     _cacheTime = DateTime.Now;
 }
        public async Task FileSystemPublicSuffixDataSource_GetDataAsync_RaisesAnEventWhenReceivingErrorCachingUpstreamData()
        {
            var ioException = new IOException();
            Mocked<IFileSystem>()
                .Setup(fs => fs.Exists(_cacheFilePath))
                .Returns(false);
            Mocked<IFileSystem>()
                .Setup(fs => fs.OpenWrite(_cacheFilePath))
                .Throws(ioException);

            var latestData = new DomainSegmentTree
            {
                Children = new DomainSegmentNodeCollection
                {
                    new DomainSegmentNode {Segment = "com"},
                    new DomainSegmentNode {Segment = "net"}
                }
            };

            var upstreamSource = new Mock<IPublicSuffixDataSource>();
            upstreamSource.Setup(s => s.GetDataAsync()).Returns(Task.FromResult(latestData));

            Exception cacheException = null;
            Subject.CacheError += (s, e) => cacheException = e.Exception;
            Subject.Upstream = upstreamSource.Object;

            await Subject.GetDataAsync();
            await Task.Delay(100);

            cacheException.Should().Be(ioException);
        }
        public async Task FileSystemPublicSuffixDataSource_GetDataAsync_CachesAfterGettingDataFromUpstream()
        {
            var mockStream = new MemoryStream();
            Mocked<IFileSystem>()
                .Setup(fs => fs.Exists(_cacheFilePath))
                .Returns(false);
            Mocked<IFileSystem>()
                .Setup(fs => fs.OpenWrite(_cacheFilePath))
                .Returns(mockStream);

            var latestData = new DomainSegmentTree
            {
                Children = new DomainSegmentNodeCollection
                {
                    new DomainSegmentNode {Segment = "com"},
                    new DomainSegmentNode {Segment = "net"}
                }
            };
            var upstreamSource = new Mock<IPublicSuffixDataSource>();
            upstreamSource.Setup(s => s.GetDataAsync()).Returns(Task.FromResult(latestData));
            Subject.Upstream = upstreamSource.Object;

            await Subject.GetDataAsync();

            // Hack... wait for the cache to be written
            await Task.Delay(100);

            var serializer = new JsonSerializer();
            var buffer = mockStream.GetBuffer();
            using (var readStream = new MemoryStream(buffer))
            using (var textReader = new StreamReader(readStream))
            using (var reader = new JsonTextReader(textReader))
            {
                var obj = serializer.Deserialize<JObject>(reader);
                obj.Properties().Should().HaveCount(2);
                obj["com"].Should().NotBeNull();
                obj["net"].Should().NotBeNull();
            }
        }
        public async Task FileSystemPublicSuffixDataSource_GetDataAsync_WhenReadingDiskDataFails_ReturnsDataFromUpstream()
        {
            Mocked<IFileSystem>()
                .Setup(fs => fs.Exists(_cacheFilePath))
                .Returns(true);
            Mocked<IFileSystem>()
                .Setup(fs => fs.GetLastWriteTime(_cacheFilePath))
                .Returns(DateTime.Now);
            Mocked<IFileSystem>()
                .Setup(fs => fs.OpenRead(_cacheFilePath))
                .Throws(new IOException());

            var latestData = new DomainSegmentTree
            {
                Children = new DomainSegmentNodeCollection
                {
                    new DomainSegmentNode {Segment = "com"},
                    new DomainSegmentNode {Segment = "net"}
                }
            };
            var upstreamSource = new Mock<IPublicSuffixDataSource>();
            upstreamSource.Setup(s => s.GetDataAsync()).Returns(Task.FromResult(latestData));
            Subject.Upstream = upstreamSource.Object;

            var data = await Subject.GetDataAsync();

            data.Should().Be(latestData);
        }
        public async Task FileSystemPublicSuffixDataSource_GetDataAsync_WhenDiskDataDoesNotExist_ReturnsNewDataFromUpstream()
        {
            Mocked<IFileSystem>()
                .Setup(fs => fs.Exists(_cacheFilePath))
                .Returns(false);

            var latestData = new DomainSegmentTree
            {
                Children = new DomainSegmentNodeCollection
                {
                    new DomainSegmentNode {Segment = "com"},
                    new DomainSegmentNode {Segment = "net"}
                }
            };
            var upstreamSource = new Mock<IPublicSuffixDataSource>();
            upstreamSource.Setup(s => s.GetDataAsync()).Returns(Task.FromResult(latestData));
            Subject.Upstream = upstreamSource.Object;

            var data = await Subject.GetDataAsync();

            data.Should().Be(latestData);
        }
        public async Task FileSystemPublicSuffixDataSource_GetDataAsync_WhenDiskDataExistsAndIsExpired_ReturnsNewDataFromUpstream()
        {
            Mocked<IFileSystem>()
                .Setup(fs => fs.GetLastWriteTime(_cacheFilePath))
                .Returns(DateTime.Now.AddDays(-11));
            Mocked<IFileSystem>()
                .Setup(fs => fs.OpenRead(_cacheFilePath))
                .Returns(new MemoryStream(Encoding.UTF8.GetBytes("{ \"com\": { } }")));

            var latestData = new DomainSegmentTree
            {
                Children = new DomainSegmentNodeCollection
                {
                    new DomainSegmentNode {Segment = "com"},
                    new DomainSegmentNode {Segment = "net"}
                }
            };
            var upstreamSource = new Mock<IPublicSuffixDataSource>();
            upstreamSource.Setup(s => s.GetDataAsync()).Returns(Task.FromResult(latestData));
            Subject.Upstream = upstreamSource.Object;

            var data = await Subject.GetDataAsync();

            data.Should().Be(latestData);
        }