diff --git a/tests/Web.Tests/Handlers/CreateBlogPostHandlerTests.cs b/tests/Web.Tests/Handlers/CreateBlogPostHandlerTests.cs
index 452713fc..501c9419 100644
--- a/tests/Web.Tests/Handlers/CreateBlogPostHandlerTests.cs
+++ b/tests/Web.Tests/Handlers/CreateBlogPostHandlerTests.cs
@@ -68,4 +68,35 @@ public async Task Handle_Success_DoesNotCallInvalidateById()
 		await _cache.Received(1).InvalidateAllAsync(Arg.Any<CancellationToken>());
 		await _cache.DidNotReceive().InvalidateByIdAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>());
 	}
+
+	[Fact]
+	public async Task Handle_OperationCanceled_Rethrows()
+	{
+		// Arrange
+		var command = new CreateBlogPostCommand("Title", "Content", "Author");
+		_repo.AddAsync(Arg.Any<BlogPost>(), Arg.Any<CancellationToken>())
+			.ThrowsAsync(new OperationCanceledException());
+
+		// Act
+		Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
+
+		// Assert
+		await act.Should().ThrowAsync<OperationCanceledException>();
+	}
+
+	[Fact]
+	public async Task Handle_UnexpectedException_ReturnsUnexpectedErrorResult()
+	{
+		// Arrange
+		var command = new CreateBlogPostCommand("Title", "Content", "Author");
+		_repo.AddAsync(Arg.Any<BlogPost>(), Arg.Any<CancellationToken>())
+			.ThrowsAsync(new TimeoutException("db timeout"));
+
+		// Act
+		var result = await _handler.Handle(command, CancellationToken.None);
+
+		// Assert
+		result.Failure.Should().BeTrue();
+		result.Error.Should().Be("An unexpected error occurred.");
+	}
 }
diff --git a/tests/Web.Tests/Handlers/DeleteBlogPostHandlerTests.cs b/tests/Web.Tests/Handlers/DeleteBlogPostHandlerTests.cs
index 475d6e97..479fbd80 100644
--- a/tests/Web.Tests/Handlers/DeleteBlogPostHandlerTests.cs
+++ b/tests/Web.Tests/Handlers/DeleteBlogPostHandlerTests.cs
@@ -75,4 +75,37 @@ public async Task Handle_ConcurrentDelete_ReturnsConcurrencyErrorCode()
 		result.Failure.Should().BeTrue();
 		result.ErrorCode.Should().Be(ResultErrorCode.Concurrency);
 	}
+
+	[Fact]
+	public async Task Handle_OperationCanceled_Rethrows()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var command = new DeleteBlogPostCommand(id);
+		_repo.DeleteAsync(id, Arg.Any<CancellationToken>())
+			.ThrowsAsync(new OperationCanceledException());
+
+		// Act
+		Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
+
+		// Assert
+		await act.Should().ThrowAsync<OperationCanceledException>();
+	}
+
+	[Fact]
+	public async Task Handle_UnexpectedException_ReturnsUnexpectedErrorResult()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var command = new DeleteBlogPostCommand(id);
+		_repo.DeleteAsync(id, Arg.Any<CancellationToken>())
+			.ThrowsAsync(new TimeoutException("db timeout"));
+
+		// Act
+		var result = await _handler.Handle(command, CancellationToken.None);
+
+		// Assert
+		result.Failure.Should().BeTrue();
+		result.Error.Should().Be("An unexpected error occurred.");
+	}
 }
diff --git a/tests/Web.Tests/Handlers/EditBlogPostHandlerTests.cs b/tests/Web.Tests/Handlers/EditBlogPostHandlerTests.cs
index 380e8941..60cb51ce 100644
--- a/tests/Web.Tests/Handlers/EditBlogPostHandlerTests.cs
+++ b/tests/Web.Tests/Handlers/EditBlogPostHandlerTests.cs
@@ -173,4 +173,74 @@ public async Task HandleGetById_CacheServiceThrows_ReturnsFailResult()
 		result.Failure.Should().BeTrue();
 		result.Error.Should().Contain("redis down");
 	}
+
+	[Fact]
+	public async Task HandleEdit_OperationCanceled_Rethrows()
+	{
+		// Arrange
+		var post = BlogPost.Create("Title", "Content", "Author");
+		var command = new EditBlogPostCommand(post.Id, "New Title", "New Content");
+		_repo.GetByIdAsync(post.Id, Arg.Any<CancellationToken>())
+			.ThrowsAsync(new OperationCanceledException());
+
+		// Act
+		Func<Task> act = () => _handler.Handle(command, CancellationToken.None);
+
+		// Assert
+		await act.Should().ThrowAsync<OperationCanceledException>();
+	}
+
+	[Fact]
+	public async Task HandleEdit_UnexpectedException_ReturnsUnexpectedErrorResult()
+	{
+		// Arrange
+		var post = BlogPost.Create("Title", "Content", "Author");
+		var command = new EditBlogPostCommand(post.Id, "New Title", "New Content");
+		_repo.GetByIdAsync(post.Id, Arg.Any<CancellationToken>())
+			.ThrowsAsync(new TimeoutException("db timeout"));
+
+		// Act
+		var result = await _handler.Handle(command, CancellationToken.None);
+
+		// Assert
+		result.Failure.Should().BeTrue();
+		result.Error.Should().Be("An unexpected error occurred.");
+	}
+
+	[Fact]
+	public async Task HandleGetById_OperationCanceled_Rethrows()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		_cache.GetOrFetchByIdAsync(
+			id,
+			Arg.Any<Func<Task<BlogPostDto?>>>(),
+			Arg.Any<CancellationToken>())
+			.ThrowsAsync(new OperationCanceledException());
+
+		// Act
+		Func<Task> act = () => _handler.Handle(new GetBlogPostByIdQuery(id), CancellationToken.None);
+
+		// Assert
+		await act.Should().ThrowAsync<OperationCanceledException>();
+	}
+
+	[Fact]
+	public async Task HandleGetById_UnexpectedException_ReturnsUnexpectedErrorResult()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		_cache.GetOrFetchByIdAsync(
+			id,
+			Arg.Any<Func<Task<BlogPostDto?>>>(),
+			Arg.Any<CancellationToken>())
+			.ThrowsAsync(new TimeoutException("db timeout"));
+
+		// Act
+		var result = await _handler.Handle(new GetBlogPostByIdQuery(id), CancellationToken.None);
+
+		// Assert
+		result.Failure.Should().BeTrue();
+		result.Error.Should().Be("An unexpected error occurred.");
+	}
 }
diff --git a/tests/Web.Tests/Handlers/GetBlogPostsHandlerTests.cs b/tests/Web.Tests/Handlers/GetBlogPostsHandlerTests.cs
index 80d54767..d4b7fcfa 100644
--- a/tests/Web.Tests/Handlers/GetBlogPostsHandlerTests.cs
+++ b/tests/Web.Tests/Handlers/GetBlogPostsHandlerTests.cs
@@ -127,4 +127,37 @@ public async Task Handle_CacheServiceThrows_ReturnsFailResult()
 		result.Failure.Should().BeTrue();
 		result.Error.Should().Contain("redis down");
 	}
+
+	[Fact]
+	public async Task Handle_OperationCanceled_Rethrows()
+	{
+		// Arrange
+		_cache.GetOrFetchAllAsync(
+			Arg.Any<Func<Task<IReadOnlyList<BlogPostDto>>>>(),
+			Arg.Any<CancellationToken>())
+			.ThrowsAsync(new OperationCanceledException());
+
+		// Act
+		Func<Task> act = () => _handler.Handle(new GetBlogPostsQuery(), CancellationToken.None);
+
+		// Assert
+		await act.Should().ThrowAsync<OperationCanceledException>();
+	}
+
+	[Fact]
+	public async Task Handle_UnexpectedException_ReturnsUnexpectedErrorResult()
+	{
+		// Arrange
+		_cache.GetOrFetchAllAsync(
+			Arg.Any<Func<Task<IReadOnlyList<BlogPostDto>>>>(),
+			Arg.Any<CancellationToken>())
+			.ThrowsAsync(new TimeoutException("db timeout"));
+
+		// Act
+		var result = await _handler.Handle(new GetBlogPostsQuery(), CancellationToken.None);
+
+		// Assert
+		result.Failure.Should().BeTrue();
+		result.Error.Should().Be("An unexpected error occurred.");
+	}
 }
diff --git a/tests/Web.Tests/Infrastructure/Caching/BlogPostCacheServiceTests.cs b/tests/Web.Tests/Infrastructure/Caching/BlogPostCacheServiceTests.cs
new file mode 100644
index 00000000..1cd2bf2b
--- /dev/null
+++ b/tests/Web.Tests/Infrastructure/Caching/BlogPostCacheServiceTests.cs
@@ -0,0 +1,285 @@
+//=======================================================
+//Copyright (c) 2026. All rights reserved.
+//File Name :     BlogPostCacheServiceTests.cs
+//Company :       mpaulosky
+//Author :        Matthew Paulosky
+//Solution Name : MyBlog
+//Project Name :  Web.Tests
+//=======================================================
+
+using System.Text.Json;
+
+using Microsoft.Extensions.Caching.Memory;
+
+namespace Web.Infrastructure.Caching;
+
+public class BlogPostCacheServiceTests : IDisposable
+{
+	private readonly MemoryCache _realLocalCache = new(new MemoryCacheOptions());
+	private readonly IDistributedCache _distributedCache = Substitute.For<IDistributedCache>();
+	private readonly BlogPostCacheService _sut;
+
+	public BlogPostCacheServiceTests()
+	{
+		_sut = new BlogPostCacheService(_realLocalCache, _distributedCache);
+	}
+
+	public void Dispose() => _realLocalCache.Dispose();
+
+	private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web);
+
+	private static List<BlogPostDto> MakeDtos() =>
+	[
+		new(Guid.NewGuid(), "Title1", "Content1", "Author1", DateTime.UtcNow, null, false),
+		new(Guid.NewGuid(), "Title2", "Content2", "Author2", DateTime.UtcNow, null, true),
+	];
+
+	// ── GetOrFetchAllAsync ────────────────────────────────────────────────
+
+	[Fact]
+	public async Task GetOrFetchAllAsync_L1Hit_ReturnsCachedListWithoutDistributedCall()
+	{
+		// Arrange
+		var cachedList = MakeDtos();
+		_realLocalCache.Set(BlogPostCacheKeys.All, cachedList);
+
+		var fetchCalled = false;
+		Task<IReadOnlyList<BlogPostDto>> fetch() { fetchCalled = true; return Task.FromResult<IReadOnlyList<BlogPostDto>>([]); }
+
+		// Act
+		var result = await _sut.GetOrFetchAllAsync(fetch, CancellationToken.None);
+
+		// Assert
+		result.Should().HaveCount(2);
+		fetchCalled.Should().BeFalse();
+		await _distributedCache.DidNotReceive().GetAsync(Arg.Any<string>(), Arg.Any<CancellationToken>());
+	}
+
+	[Fact]
+	public async Task GetOrFetchAllAsync_L2Hit_DeserializesAndPopulatesL1()
+	{
+		// Arrange — no L1 entry, L2 has valid bytes
+		var dtos = MakeDtos();
+		var bytes = JsonSerializer.SerializeToUtf8Bytes(dtos, JsonOpts);
+		_distributedCache.GetAsync(BlogPostCacheKeys.All, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(bytes));
+
+		// Act
+		var result = await _sut.GetOrFetchAllAsync(
+			() => Task.FromResult<IReadOnlyList<BlogPostDto>>([]), CancellationToken.None);
+
+		// Assert
+		result.Should().HaveCount(2);
+		_realLocalCache.TryGetValue(BlogPostCacheKeys.All, out List<BlogPostDto>? l1Val).Should().BeTrue();
+		l1Val.Should().HaveCount(2);
+	}
+
+	[Fact]
+	public async Task GetOrFetchAllAsync_L2JsonCorrupt_RemovesAndFallsThroughToFetch()
+	{
+		// Arrange
+		var corruptBytes = "{ not valid json !!!"u8.ToArray();
+		_distributedCache.GetAsync(BlogPostCacheKeys.All, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(corruptBytes));
+		_distributedCache.RemoveAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+		_distributedCache.SetAsync(
+			Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<DistributedCacheEntryOptions>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+
+		var expected = MakeDtos();
+		var fetchCalled = false;
+		Task<IReadOnlyList<BlogPostDto>> fetch()
+		{
+			fetchCalled = true;
+			return Task.FromResult<IReadOnlyList<BlogPostDto>>(expected);
+		}
+
+		// Act
+		var result = await _sut.GetOrFetchAllAsync(fetch, CancellationToken.None);
+
+		// Assert
+		fetchCalled.Should().BeTrue();
+		result.Should().HaveCount(2);
+		await _distributedCache.Received().RemoveAsync(BlogPostCacheKeys.All, CancellationToken.None);
+	}
+
+	[Fact]
+	public async Task GetOrFetchAllAsync_FullMiss_FetchesAndPopulatesBothTiers()
+	{
+		// Arrange — nothing in L1 or L2
+		_distributedCache.GetAsync(BlogPostCacheKeys.All, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(null));
+		_distributedCache.SetAsync(
+			Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<DistributedCacheEntryOptions>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+
+		var dtos = MakeDtos();
+
+		// Act
+		var result = await _sut.GetOrFetchAllAsync(
+			() => Task.FromResult<IReadOnlyList<BlogPostDto>>(dtos), CancellationToken.None);
+
+		// Assert
+		result.Should().HaveCount(2);
+		_realLocalCache.TryGetValue(BlogPostCacheKeys.All, out List<BlogPostDto>? l1Val).Should().BeTrue();
+		l1Val.Should().HaveCount(2);
+		await _distributedCache.Received().SetAsync(
+			BlogPostCacheKeys.All,
+			Arg.Any<byte[]>(),
+			Arg.Any<DistributedCacheEntryOptions>(),
+			Arg.Any<CancellationToken>());
+	}
+
+	// ── GetOrFetchByIdAsync ───────────────────────────────────────────────
+
+	[Fact]
+	public async Task GetOrFetchByIdAsync_L1Hit_ReturnsCachedDtoWithoutDistributedCall()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var key = BlogPostCacheKeys.ById(id);
+		var dto = new BlogPostDto(id, "T", "C", "A", DateTime.UtcNow, null, false);
+		_realLocalCache.Set(key, dto);
+
+		// Act
+		var result = await _sut.GetOrFetchByIdAsync(id, () => Task.FromResult<BlogPostDto?>(null), CancellationToken.None);
+
+		// Assert
+		result.Should().NotBeNull();
+		result!.Id.Should().Be(id);
+		await _distributedCache.DidNotReceive().GetAsync(Arg.Any<string>(), Arg.Any<CancellationToken>());
+	}
+
+	[Fact]
+	public async Task GetOrFetchByIdAsync_L2Hit_DeserializesAndPopulatesL1()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var key = BlogPostCacheKeys.ById(id);
+		var dto = new BlogPostDto(id, "T", "C", "A", DateTime.UtcNow, null, false);
+		var bytes = JsonSerializer.SerializeToUtf8Bytes(dto, JsonOpts);
+		_distributedCache.GetAsync(key, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(bytes));
+
+		// Act
+		var result = await _sut.GetOrFetchByIdAsync(id, () => Task.FromResult<BlogPostDto?>(null), CancellationToken.None);
+
+		// Assert
+		result.Should().NotBeNull();
+		result!.Id.Should().Be(id);
+		_realLocalCache.TryGetValue(key, out BlogPostDto? l1Val).Should().BeTrue();
+		l1Val!.Id.Should().Be(id);
+	}
+
+	[Fact]
+	public async Task GetOrFetchByIdAsync_L2JsonCorrupt_RemovesAndFallsThroughToFetch()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var key = BlogPostCacheKeys.ById(id);
+		var corruptBytes = "{ not valid json !!!"u8.ToArray();
+		_distributedCache.GetAsync(key, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(corruptBytes));
+		_distributedCache.RemoveAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+		_distributedCache.SetAsync(
+			Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<DistributedCacheEntryOptions>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+
+		var dto = new BlogPostDto(id, "T", "C", "A", DateTime.UtcNow, null, false);
+		var fetchCalled = false;
+		Task<BlogPostDto?> fetch() { fetchCalled = true; return Task.FromResult<BlogPostDto?>(dto); }
+
+		// Act
+		var result = await _sut.GetOrFetchByIdAsync(id, fetch, CancellationToken.None);
+
+		// Assert
+		fetchCalled.Should().BeTrue();
+		result!.Id.Should().Be(id);
+		await _distributedCache.Received().RemoveAsync(key, CancellationToken.None);
+	}
+
+	[Fact]
+	public async Task GetOrFetchByIdAsync_FullMiss_FetchesAndPopulatesBothTiers()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var key = BlogPostCacheKeys.ById(id);
+		_distributedCache.GetAsync(key, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(null));
+		_distributedCache.SetAsync(
+			Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<DistributedCacheEntryOptions>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+
+		var dto = new BlogPostDto(id, "T", "C", "A", DateTime.UtcNow, null, false);
+
+		// Act
+		var result = await _sut.GetOrFetchByIdAsync(id, () => Task.FromResult<BlogPostDto?>(dto), CancellationToken.None);
+
+		// Assert
+		result!.Id.Should().Be(id);
+		_realLocalCache.TryGetValue(key, out BlogPostDto? l1Val).Should().BeTrue();
+		l1Val!.Id.Should().Be(id);
+		await _distributedCache.Received().SetAsync(
+			key, Arg.Any<byte[]>(), Arg.Any<DistributedCacheEntryOptions>(), Arg.Any<CancellationToken>());
+	}
+
+	[Fact]
+	public async Task GetOrFetchByIdAsync_FetchReturnsNull_ReturnsNull()
+	{
+		// Arrange
+		var id = Guid.NewGuid();
+		var key = BlogPostCacheKeys.ById(id);
+		_distributedCache.GetAsync(key, Arg.Any<CancellationToken>())
+			.Returns(Task.FromResult<byte[]?>(null));
+
+		// Act
+		var result = await _sut.GetOrFetchByIdAsync(id, () => Task.FromResult<BlogPostDto?>(null), CancellationToken.None);
+
+		// Assert
+		result.Should().BeNull();
+		await _distributedCache.DidNotReceive().SetAsync(
+			Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<DistributedCacheEntryOptions>(), Arg.Any<CancellationToken>());
+	}
+
+	// ── InvalidateAllAsync ────────────────────────────────────────────────
+
+	[Fact]
+	public async Task InvalidateAllAsync_RemovesBothCacheTiers()
+	{
+		// Arrange — populate L1 so we can verify removal
+		var dtos = MakeDtos();
+		_realLocalCache.Set(BlogPostCacheKeys.All, dtos);
+		_distributedCache.RemoveAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+
+		// Act
+		await _sut.InvalidateAllAsync(CancellationToken.None);
+
+		// Assert
+		_realLocalCache.TryGetValue(BlogPostCacheKeys.All, out List<BlogPostDto>? _).Should().BeFalse();
+		await _distributedCache.Received(1).RemoveAsync(BlogPostCacheKeys.All, CancellationToken.None);
+	}
+
+	// ── InvalidateByIdAsync ───────────────────────────────────────────────
+
+	[Fact]
+	public async Task InvalidateByIdAsync_RemovesByKeyFromBothTiers()
+	{
+		// Arrange — populate L1 so we can verify removal
+		var id = Guid.NewGuid();
+		var key = BlogPostCacheKeys.ById(id);
+		var dto = new BlogPostDto(id, "T", "C", "A", DateTime.UtcNow, null, false);
+		_realLocalCache.Set(key, dto);
+		_distributedCache.RemoveAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
+			.Returns(Task.CompletedTask);
+
+		// Act
+		await _sut.InvalidateByIdAsync(id, CancellationToken.None);
+
+		// Assert
+		_realLocalCache.TryGetValue(key, out BlogPostDto? _).Should().BeFalse();
+		await _distributedCache.Received(1).RemoveAsync(key, CancellationToken.None);
+	}
+}
