diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index fcb5099a..b2ab171a 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -1,5 +1,11 @@ using System.Net; +using System.Net.Http; +using System.Net.Http.Json; using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; +using Yavsc.Models; +using Yavsc.Models.Blog; +using Yavsc.Tests.Shared; namespace Yavsc.Blogs.Tests; @@ -19,6 +25,19 @@ public sealed class BlogApiTests : IClassFixture _fixture = fixture; } + /// Reset the in-memory database to a known empty state. + /// UseInMemoryDatabase shares its store across the + /// lifetime of the instance, + /// so without a per-test reset the test order would leak + /// state between tests. + private void ResetDatabase() + { + using var scope = _fixture.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); + } + /// The fixture's WebApplication is bound to /// https://localhost:<random> via /// . We pick the first @@ -48,6 +67,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlogs_returns_200_with_empty_list_when_no_posts() { + ResetDatabase(); using var http = NewClient(); var response = await http.GetAsync("/api/v1/blog"); @@ -62,4 +82,42 @@ public sealed class BlogApiTests : IClassFixture Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); Assert.Equal(0, doc.RootElement.GetArrayLength()); } + + [Fact] + public async Task PostBlog_creates_a_post_and_Get_returns_it_in_the_list() + { + ResetDatabase(); + using var http = NewClient(); + + // Create a minimal BlogPost. The server assigns Id, so we + // send 0 + an explicit AuthorId; the production + // BlogSpotService.Create() tolerates that. + var draft = new BlogPost + { + Id = 0, + Title = "Premier billet", + AuthorId = "tester", + Article = "Contenu de test.", + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow + }; + + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft); + Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); + + // The POST returns the server-issued post (with a real Id). + var created = await postResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(created); + Assert.NotEqual(0, created!.Id); + Assert.Equal(draft.Title, created.Title); + + // The list should now contain exactly one entry. + var listResponse = await http.GetAsync("/api/v1/blog"); + Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); + + using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync()); + Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); + Assert.Equal(1, doc.RootElement.GetArrayLength()); + Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64()); + } } diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs index 89f74ad2..31afa163 100644 --- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs @@ -92,7 +92,29 @@ namespace Yavsc.Blogs.Controllers return BadRequest(ModelState); } - var post = blogSpotService.Create(User.GetUserId(), blog, Request.Form.Files); + // The BlogSpotService.Create() signature requires an + // IFormFileCollection for file uploads. Reading + // Request.Form.Files when the request is a plain JSON + // body (e.g. from PostIt) throws + // "This request does not have a Content-Type header. + // Forms are available from requests with bodies like + // POSTs and a form Content-Type of either + // application/x-www-form-urlencoded or + // multipart/form-data." + // + // Two valid use cases for this endpoint: + // 1. JSON body only (no files) — PostIt path. + // 2. multipart/form-data with a 'blog' field + 0..N + // files — future browser / server-rendered path. + // + // Branch on HasFormContentType: pass the form files when + // present, pass an empty collection otherwise. The + // FileSystem branch in BlogSpotService.Create then + // short-circuits to "no files to handle". + var files = Request.HasFormContentType + ? Request.Form.Files + : (IFormFileCollection)new FormFileCollection(); + var post = blogSpotService.Create(User.GetUserId(), blog, files); return CreatedAtRoute("GetBlog", new { id = post.Id }, post); }