public async Task GetGuidInfo_ExpiredGuidFromCache() { // use guid created in the past var cachedEntity = new GuidInfoEntity() { Guid = System.Guid.NewGuid(), User = "******", Expire = _now.AddDays(-10) }; var mockRepo = new Mock <IRepository <GuidInfoEntity> >(); var mockContext = new Mock <IGuidRepositoryContext>(); // return entity from cache var mockCache = new Mock <IEntityCache <GuidInfoEntity> >(); mockCache.Setup(r => r.GetEntityAsync(It.IsAny <string>(), cachedEntity.Guid.ToString())) .ReturnsAsync(cachedEntity) .Verifiable(); var controller = new GuidInfosController(mockContext.Object, mockCache.Object, _mockClock.Object); var info = await controller.GetGuidInfoAsync(cachedEntity.Guid); Assert.IsType <ActionResult <GuidInfo> >(info); var result = Assert.IsType <ObjectResult>(info.Result); Assert.Equal(result.StatusCode, (int)HttpStatusCode.Gone); var value = Assert.IsType <GuidApiError>(result.Value); Assert.Equal((int)value.Code, (int)GuidErrorCode.GuidExpired); mockCache.Verify(); }
public async Task CreateOrUpdateGuidInfo_Update() { // entity in database var dbEntity = new GuidInfoEntity() { Guid = System.Guid.NewGuid(), User = "******", Expire = new DateTime(2020, 2, 2, 2, 2, 2, DateTimeKind.Utc) }; // modified properties var infoBase = new GuidInfoBase() { User = "******", Expire = new DateTime(2022, 1, 1, 1, 1, 1, DateTimeKind.Utc) }; // find entity in database var mockRepo = new Mock <IRepository <GuidInfoEntity> >(); mockRepo.Setup(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >())) .ReturnsAsync(new List <GuidInfoEntity>() { dbEntity }) .Verifiable(); // update it mockRepo.Setup(r => r.Update(It.IsAny <GuidInfoEntity>())) .Verifiable(); // save it var mockContext = new Mock <IGuidRepositoryContext>(); mockContext.Setup(r => r.SaveChangesAsync()) .Returns(Task.CompletedTask) .Verifiable(); mockContext.Setup(r => r.GuidInfos) .Returns(mockRepo.Object); // and invalidate cache var mockCache = new Mock <IEntityCache <GuidInfoEntity> >(); mockCache.Setup(r => r.InvalidateEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString())) .Returns(Task.CompletedTask) .Verifiable(); var controller = new GuidInfosController(mockContext.Object, mockCache.Object, _mockClock.Object); var info = await controller.CreateOrUpdateGuidInfoAsync(dbEntity.Guid, infoBase); Assert.IsType <ActionResult <GuidInfo> >(info); var value = Assert.IsType <GuidInfo>(info.Value); Assert.Equal(value.User, infoBase.User); Assert.Equal(value.Expire, infoBase.Expire); mockRepo.Verify(); mockContext.Verify(); mockCache.Verify(); }
private async Task <ActionResult <GuidInfo> > ProcessGuidInfo(GuidInfoEntity info, bool fromCache) { if (info.Expire < _clock.UtcNow) { // retrieved info has expired if (fromCache) { // remove it from cache await InvalidateCache(info.Guid); } // and return 410 code (Gone) return(StatusCode((int)HttpStatusCode.Gone, new GuidApiError(GuidErrorCode.GuidExpired))); } else { // this is a valid (not expired) guid if (!fromCache) { // retrieved from database, add it to cache // NOTE: Assuming Redis LRU caching, see https://redis.io/topics/lru-cache // NOTE: Alternatively, we could set an expiration time for entries await _cache.SetEntityAsync(_cachePrefix, info.Guid.ToString(), info); } return(info.ToGuidInfo()); } }
public async Task GetGuidInfo_FromDatabase() { var dbEntity = new GuidInfoEntity() { Guid = System.Guid.NewGuid(), User = "******", Expire = _now.AddDays(10) }; // return entity from database var mockRepo = new Mock <IRepository <GuidInfoEntity> >(); mockRepo.Setup(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >())) .ReturnsAsync(new List <GuidInfoEntity>() { dbEntity }) .Verifiable(); var mockContext = new Mock <IGuidRepositoryContext>(); mockContext.Setup(r => r.GuidInfos) .Returns(mockRepo.Object); // return no hit from cache var mockCache = new Mock <IEntityCache <GuidInfoEntity> >(); mockCache.Setup(r => r.GetEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString())) .ReturnsAsync((GuidInfoEntity)null) .Verifiable(); // make sure we cache found database entry mockCache.Setup(r => r.SetEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString(), dbEntity)) .Returns(Task.CompletedTask) .Verifiable(); var controller = new GuidInfosController(mockContext.Object, mockCache.Object, _mockClock.Object); var info = await controller.GetGuidInfoAsync(dbEntity.Guid); Assert.IsType <ActionResult <GuidInfo> >(info); var value = Assert.IsType <GuidInfo>(info.Value); Assert.Equal(value.Guid, dbEntity.Guid.ToString("N").ToUpper()); Assert.Equal(value.User, dbEntity.User); Assert.Equal(value.Expire, dbEntity.Expire); // verify we read cache once (which returned null) mockCache.Verify(r => r.GetEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString()), Times.Once); // and accessed database once mockRepo.Verify(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >()), Times.Once); // and stored in cache once mockCache.Verify(r => r.SetEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString(), dbEntity), Times.Once); }
public async Task GetGuidInfo_ExpiredGuidFromDatabase() { // use guid created in the past var dbEntity = new GuidInfoEntity() { Guid = System.Guid.NewGuid(), User = "******", Expire = _now.AddDays(-10) }; // retrieve entity from database var mockRepo = new Mock <IRepository <GuidInfoEntity> >(); mockRepo.Setup(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >())) .ReturnsAsync(new List <GuidInfoEntity>() { dbEntity }) .Verifiable(); var mockContext = new Mock <IGuidRepositoryContext>(); mockContext.Setup(r => r.GuidInfos) .Returns(mockRepo.Object); // make sure we get no cache hits var mockCache = new Mock <IEntityCache <GuidInfoEntity> >(); mockCache.Setup(r => r.GetEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString())) .ReturnsAsync((GuidInfoEntity)null) .Verifiable(); var controller = new GuidInfosController(mockContext.Object, mockCache.Object, _mockClock.Object); var info = await controller.GetGuidInfoAsync(dbEntity.Guid); Assert.IsType <ActionResult <GuidInfo> >(info); var result = Assert.IsType <ObjectResult>(info.Result); Assert.Equal(result.StatusCode, (int)HttpStatusCode.Gone); var value = Assert.IsType <GuidApiError>(result.Value); Assert.Equal((int)value.Code, (int)GuidErrorCode.GuidExpired); mockRepo.Verify(); mockCache.Verify(); }
public async Task DeleteGuidInfo_Found() { var dbEntity = new GuidInfoEntity() { Guid = System.Guid.NewGuid(), User = "******", Expire = new DateTime(2020, 2, 2, 2, 2, 2, DateTimeKind.Utc) }; // find entity in database var mockRepo = new Mock <IRepository <GuidInfoEntity> >(); mockRepo.Setup(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >())) .ReturnsAsync(new List <GuidInfoEntity>() { dbEntity }) .Verifiable(); // remove it from database mockRepo.Setup(r => r.Delete(It.IsAny <GuidInfoEntity>())) .Verifiable(); var mockContext = new Mock <IGuidRepositoryContext>(); mockContext.Setup(r => r.GuidInfos) .Returns(mockRepo.Object); // and invalidate cache var mockCache = new Mock <IEntityCache <GuidInfoEntity> >(); mockCache.Setup(r => r.InvalidateEntityAsync(It.IsAny <string>(), dbEntity.Guid.ToString())) .Returns(Task.CompletedTask) .Verifiable(); var controller = new GuidInfosController(mockContext.Object, mockCache.Object, _mockClock.Object); var info = await controller.DeleteGuidInfoAsync(dbEntity.Guid); var result = Assert.IsType <OkResult>(info); Assert.Equal(result.StatusCode, (int)HttpStatusCode.OK); mockRepo.Verify(); mockCache.Verify(); }
public async Task GetGuidInfo_FromCache() { // return no hits from database var mockRepo = new Mock <IRepository <GuidInfoEntity> >(); mockRepo.Setup(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >())) .ReturnsAsync(new List <GuidInfoEntity>()) .Verifiable(); var mockContext = new Mock <IGuidRepositoryContext>(); mockContext.Setup(r => r.GuidInfos) .Returns(mockRepo.Object); var cachedEntity = new GuidInfoEntity() { Guid = System.Guid.NewGuid(), User = "******", Expire = _now.AddDays(10) }; // return entity from cache var mockCache = new Mock <IEntityCache <GuidInfoEntity> >(); mockCache.Setup(r => r.GetEntityAsync(It.IsAny <string>(), cachedEntity.Guid.ToString())) .ReturnsAsync(cachedEntity) .Verifiable(); var controller = new GuidInfosController(mockContext.Object, mockCache.Object, _mockClock.Object); var info = await controller.GetGuidInfoAsync(cachedEntity.Guid); Assert.IsType <ActionResult <GuidInfo> >(info); var value = Assert.IsType <GuidInfo>(info.Value); Assert.Equal(value.Guid, cachedEntity.Guid.ToString("N").ToUpper()); Assert.Equal(value.User, cachedEntity.User); Assert.Equal(value.Expire, cachedEntity.Expire); // verify we accessed cache mockCache.Verify(r => r.GetEntityAsync(It.IsAny <string>(), cachedEntity.Guid.ToString()), Times.Once); // but not database mockRepo.Verify(r => r.GetAsync(It.IsAny <Expression <Func <GuidInfoEntity, bool> > >()), Times.Never); }
private async Task <ActionResult <GuidInfo> > CreateGuidInfo(System.Guid guid, GuidInfoBase info) { if (string.IsNullOrWhiteSpace(info.User)) { return(BadRequest(new GuidApiError(GuidErrorCode.InvalidUser))); } else { var entity = new GuidInfoEntity() { Guid = guid, Expire = info.Expire ?? GetDefaultExpireDate(), User = info.User }; await _context.GuidInfos.AddAsync(entity); await _context.SaveChangesAsync(); var guidInfo = entity.ToGuidInfo(); return(CreatedAtRoute("GetGuidInfo", new { id = guidInfo.Guid }, guidInfo)); } }