Add backend test harness with mock SQLite
This commit is contained in:
50
GameList.Tests/AuthTests.cs
Normal file
50
GameList.Tests/AuthTests.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using GameList.Tests.Support;
|
||||||
|
|
||||||
|
namespace GameList.Tests;
|
||||||
|
|
||||||
|
public class AuthTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Register_with_admin_key_sets_admin_flag()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
|
||||||
|
var response = await client.RegisterAsync("adminuser", admin: true);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||||
|
Assert.True(json.GetProperty("IsAdmin").GetBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Register_duplicate_username_returns_conflict()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
|
||||||
|
var first = await client.RegisterAsync("duplicate");
|
||||||
|
first.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var second = await client.RegisterAsync("duplicate");
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Conflict, second.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Login_with_wrong_password_returns_unauthorized()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
|
||||||
|
await client.RegisterAsync("player1");
|
||||||
|
|
||||||
|
var login = await client.LoginAsync("player1", "wrongpass");
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Unauthorized, login.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
GameList.Tests/GameList.Tests.csproj
Normal file
27
GameList.Tests/GameList.Tests.csproj
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\\GameList.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
35
GameList.Tests/ResultsTests.cs
Normal file
35
GameList.Tests/ResultsTests.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using GameList.Tests.Support;
|
||||||
|
|
||||||
|
namespace GameList.Tests;
|
||||||
|
|
||||||
|
public class ResultsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Results_available_after_admin_unlocks()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var admin = factory.CreateClientWithCookies();
|
||||||
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
|
|
||||||
|
var player = factory.CreateClientWithCookies();
|
||||||
|
await player.RegisterAsync("player");
|
||||||
|
var suggestionId = await player.CreateSuggestionAsync("ResultGame");
|
||||||
|
|
||||||
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
await player.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 8 });
|
||||||
|
await player.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
||||||
|
|
||||||
|
await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true });
|
||||||
|
|
||||||
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
|
||||||
|
var results = await player.GetFromJsonAsync<JsonElement>("/api/results");
|
||||||
|
|
||||||
|
Assert.True(results.GetArrayLength() >= 1);
|
||||||
|
var first = results[0];
|
||||||
|
Assert.Equal("ResultGame", first.GetProperty("Name").GetString());
|
||||||
|
Assert.Equal(8, (int)first.GetProperty("Average").GetDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
45
GameList.Tests/StateTests.cs
Normal file
45
GameList.Tests/StateTests.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using GameList.Domain;
|
||||||
|
using GameList.Tests.Support;
|
||||||
|
|
||||||
|
namespace GameList.Tests;
|
||||||
|
|
||||||
|
public class StateTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Cannot_advance_to_results_when_locked()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
await client.RegisterAsync("player");
|
||||||
|
|
||||||
|
var toVote = await client.PostAsync("/api/me/phase/next", JsonContent.Create(new { }));
|
||||||
|
toVote.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var toResults = await client.PostAsync("/api/me/phase/next", JsonContent.Create(new { }));
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, toResults.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Admin_opening_results_moves_players_to_results_phase()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var admin = factory.CreateClientWithCookies();
|
||||||
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
|
|
||||||
|
var player = factory.CreateClientWithCookies();
|
||||||
|
await player.RegisterAsync("user");
|
||||||
|
|
||||||
|
var toggle = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true });
|
||||||
|
toggle.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var state = await player.GetFromJsonAsync<JsonElement>("/api/state");
|
||||||
|
|
||||||
|
Assert.Equal((int)Phase.Results, state.GetProperty("CurrentPhase").GetInt32());
|
||||||
|
Assert.True(state.GetProperty("resultsOpen").GetBoolean());
|
||||||
|
}
|
||||||
|
}
|
||||||
71
GameList.Tests/SuggestionTests.cs
Normal file
71
GameList.Tests/SuggestionTests.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using GameList.Tests.Support;
|
||||||
|
|
||||||
|
namespace GameList.Tests;
|
||||||
|
|
||||||
|
public class SuggestionTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Player_cannot_exceed_five_suggestions()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
await client.RegisterAsync("suggestor");
|
||||||
|
|
||||||
|
for (var i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
var resp = await client.PostAsJsonAsync("/api/suggestions", new
|
||||||
|
{
|
||||||
|
Name = $"Game {i}",
|
||||||
|
Genre = (string?)null,
|
||||||
|
Description = (string?)null,
|
||||||
|
ScreenshotUrl = (string?)null,
|
||||||
|
YoutubeUrl = (string?)null,
|
||||||
|
GameUrl = (string?)null,
|
||||||
|
MinPlayers = (int?)null,
|
||||||
|
MaxPlayers = (int?)null
|
||||||
|
});
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
var sixth = await client.PostAsJsonAsync("/api/suggestions", new
|
||||||
|
{
|
||||||
|
Name = "Overflow",
|
||||||
|
Genre = (string?)null,
|
||||||
|
Description = (string?)null,
|
||||||
|
ScreenshotUrl = (string?)null,
|
||||||
|
YoutubeUrl = (string?)null,
|
||||||
|
GameUrl = (string?)null,
|
||||||
|
MinPlayers = (int?)null,
|
||||||
|
MaxPlayers = (int?)null
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, sixth.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Unreachable_screenshot_url_is_rejected()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
factory.HttpHandler.SetResponder(_ => new HttpResponseMessage(HttpStatusCode.BadRequest));
|
||||||
|
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
await client.RegisterAsync("imgtester");
|
||||||
|
|
||||||
|
var response = await client.PostAsJsonAsync("/api/suggestions", new
|
||||||
|
{
|
||||||
|
Name = "Needs image",
|
||||||
|
Genre = (string?)null,
|
||||||
|
Description = (string?)null,
|
||||||
|
ScreenshotUrl = "http://example.com/image.png",
|
||||||
|
YoutubeUrl = (string?)null,
|
||||||
|
GameUrl = (string?)null,
|
||||||
|
MinPlayers = (int?)null,
|
||||||
|
MaxPlayers = (int?)null
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
GameList.Tests/Support/StubHttpClientFactory.cs
Normal file
18
GameList.Tests/Support/StubHttpClientFactory.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace GameList.Tests.Support;
|
||||||
|
|
||||||
|
internal class StubHttpClientFactory : IHttpClientFactory
|
||||||
|
{
|
||||||
|
private readonly StubHttpMessageHandler _handler;
|
||||||
|
|
||||||
|
public StubHttpClientFactory(StubHttpMessageHandler handler)
|
||||||
|
{
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient CreateClient(string name)
|
||||||
|
{
|
||||||
|
return new HttpClient(_handler, dispose: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
GameList.Tests/Support/StubHttpMessageHandler.cs
Normal file
35
GameList.Tests/Support/StubHttpMessageHandler.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace GameList.Tests.Support;
|
||||||
|
|
||||||
|
internal class StubHttpMessageHandler : HttpMessageHandler
|
||||||
|
{
|
||||||
|
private Func<HttpRequestMessage, HttpResponseMessage> _responder;
|
||||||
|
|
||||||
|
public StubHttpMessageHandler()
|
||||||
|
{
|
||||||
|
_responder = DefaultResponder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetResponder(Func<HttpRequestMessage, HttpResponseMessage> responder)
|
||||||
|
{
|
||||||
|
_responder = responder ?? DefaultResponder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HttpResponseMessage DefaultResponder(HttpRequestMessage _)
|
||||||
|
{
|
||||||
|
var response = new HttpResponseMessage(HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
Content = new ByteArrayContent(Array.Empty<byte>())
|
||||||
|
};
|
||||||
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
|
||||||
|
response.Content.Headers.ContentLength = 0;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(_responder(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
46
GameList.Tests/Support/TestClientExtensions.cs
Normal file
46
GameList.Tests/Support/TestClientExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace GameList.Tests.Support;
|
||||||
|
|
||||||
|
internal static class TestClientExtensions
|
||||||
|
{
|
||||||
|
public static Task<HttpResponseMessage> RegisterAsync(this HttpClient client, string username, bool admin = false)
|
||||||
|
{
|
||||||
|
return client.PostAsJsonAsync("/api/auth/register", new
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = "Pass123!",
|
||||||
|
DisplayName = $"{username}-name",
|
||||||
|
AdminKey = admin ? "admin-key" : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<HttpResponseMessage> LoginAsync(this HttpClient client, string username, string password)
|
||||||
|
{
|
||||||
|
return client.PostAsJsonAsync("/api/auth/login", new
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = password
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> CreateSuggestionAsync(this HttpClient client, string name)
|
||||||
|
{
|
||||||
|
var response = await client.PostAsJsonAsync("/api/suggestions", new
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Genre = "Coop",
|
||||||
|
Description = (string?)null,
|
||||||
|
ScreenshotUrl = (string?)null,
|
||||||
|
YoutubeUrl = (string?)null,
|
||||||
|
GameUrl = (string?)null,
|
||||||
|
MinPlayers = (int?)null,
|
||||||
|
MaxPlayers = (int?)null
|
||||||
|
});
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||||
|
return json.GetProperty("Id").GetInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
90
GameList.Tests/Support/TestWebApplicationFactory.cs
Normal file
90
GameList.Tests/Support/TestWebApplicationFactory.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using GameList.Data;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace GameList.Tests.Support;
|
||||||
|
|
||||||
|
internal class TestWebApplicationFactory : WebApplicationFactory<Program>
|
||||||
|
{
|
||||||
|
private SqliteConnection? _connection;
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
builder.UseEnvironment("Development");
|
||||||
|
builder.ConfigureAppConfiguration((context, config) =>
|
||||||
|
{
|
||||||
|
config.AddInMemoryCollection(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["ADMIN_PASSWORD"] = "admin-key"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
|
||||||
|
if (descriptor != null)
|
||||||
|
{
|
||||||
|
services.Remove(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
_connection = new SqliteConnection("Data Source=:memory:;Cache=Shared");
|
||||||
|
_connection.Open();
|
||||||
|
|
||||||
|
services.AddDbContext<AppDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseSqlite(_connection);
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<StubHttpMessageHandler>();
|
||||||
|
services.AddSingleton<IHttpClientFactory, StubHttpClientFactory>();
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.UseSetting("https_port", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IHost CreateHost(IHostBuilder builder)
|
||||||
|
{
|
||||||
|
var host = base.CreateHost(builder);
|
||||||
|
|
||||||
|
using var scope = host.Services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
db.Database.EnsureCreated();
|
||||||
|
db.Database.Migrate();
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_connection?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StubHttpMessageHandler HttpHandler => Services.GetRequiredService<StubHttpMessageHandler>();
|
||||||
|
|
||||||
|
public Task WithDbContextAsync(Func<AppDbContext, Task> action)
|
||||||
|
{
|
||||||
|
using var scope = Services.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
return action(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient CreateClientWithCookies()
|
||||||
|
{
|
||||||
|
return CreateClient(new WebApplicationFactoryClientOptions
|
||||||
|
{
|
||||||
|
HandleCookies = true,
|
||||||
|
AllowAutoRedirect = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
61
GameList.Tests/VoteTests.cs
Normal file
61
GameList.Tests/VoteTests.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using GameList.Tests.Support;
|
||||||
|
|
||||||
|
namespace GameList.Tests;
|
||||||
|
|
||||||
|
public class VoteTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Finalizing_votes_blocks_further_changes()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var client = factory.CreateClientWithCookies();
|
||||||
|
await client.RegisterAsync("voter");
|
||||||
|
|
||||||
|
var suggestionId = await client.CreateSuggestionAsync("VoteGame");
|
||||||
|
|
||||||
|
await client.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
|
||||||
|
var vote = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 7 });
|
||||||
|
vote.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var finalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
||||||
|
finalize.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var change = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 5 });
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, change.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Linked_votes_apply_to_all_linked_suggestions()
|
||||||
|
{
|
||||||
|
using var factory = new TestWebApplicationFactory();
|
||||||
|
var admin = factory.CreateClientWithCookies();
|
||||||
|
await admin.RegisterAsync("admin", admin: true);
|
||||||
|
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
|
||||||
|
var player = factory.CreateClientWithCookies();
|
||||||
|
await player.RegisterAsync("linker");
|
||||||
|
|
||||||
|
var id1 = await player.CreateSuggestionAsync("Game A");
|
||||||
|
var id2 = await player.CreateSuggestionAsync("Game B");
|
||||||
|
|
||||||
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
||||||
|
|
||||||
|
var linkResponse = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = id1, TargetSuggestionId = id2 });
|
||||||
|
linkResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var vote = await player.PostAsJsonAsync("/api/votes", new { SuggestionId = id1, Score = 9 });
|
||||||
|
vote.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var mine = await player.GetFromJsonAsync<List<VoteRecord>>("/api/votes/mine");
|
||||||
|
|
||||||
|
Assert.Equal(2, mine.Count);
|
||||||
|
Assert.All(mine, v => Assert.Equal(9, v.Score));
|
||||||
|
}
|
||||||
|
|
||||||
|
private record VoteRecord(int SuggestionId, int Score);
|
||||||
|
}
|
||||||
@@ -133,3 +133,5 @@ static void UpdateIndexMetaBase(IWebHostEnvironment env, string basePath)
|
|||||||
// If we can't rewrite, continue; frontend can still be set manually.
|
// If we can't rewrite, continue; frontend can still be set manually.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public partial class Program { }
|
||||||
|
|||||||
Reference in New Issue
Block a user