feat: добавил изменение промта для админа
Backend CI / build-and-test (push) Failing after 11m26s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 14m2s
Frontend CI / build-and-check (push) Failing after 19m55s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m7s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m59s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 15m0s
Backend CI / build-and-test (push) Failing after 11m26s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 14m2s
Frontend CI / build-and-check (push) Failing after 19m55s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m7s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m59s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 15m0s
This commit is contained in:
@@ -121,6 +121,9 @@ public class EndpointAuthorizationTests : IClassFixture<ApiWebApplicationFactory
|
||||
body: """{"lectureId":1,"rating":"Like","text":"Great lecture!"}""");
|
||||
|
||||
// ── Reviews — Admin only ──────────────────────────────────────────────
|
||||
yield return E("reviews/llm-prompt GET [Admin]", "GET", "api/v1/reviews/llm-prompt","Admin", forbidden: ["Student", "Teacher"]);
|
||||
yield return E("reviews/llm-prompt PUT [Admin]", "PUT", "api/v1/reviews/llm-prompt","Admin", forbidden: ["Student", "Teacher"],
|
||||
body: """{"prompt":"Analyze {lectureContext}. Review: {reviewText}"}""");
|
||||
yield return E("reviews/pending GET [Admin]", "GET", "api/v1/reviews/pending","Admin", forbidden: ["Student", "Teacher"]);
|
||||
yield return E("reviews/{id}/reanalyze POST [Admin]","POST", "api/v1/reviews/1/reanalyze","Admin",forbidden: ["Student", "Teacher"]);
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ public class ApiWebApplicationFactory : WebApplicationFactory<Program>
|
||||
ReplaceWithSubstitute<IUserService>(services, CreateUserServiceStub());
|
||||
ReplaceWithSubstitute<ILectureService>(services, CreateLectureServiceStub());
|
||||
ReplaceWithSubstitute<IReviewService>(services, CreateReviewServiceStub());
|
||||
ReplaceWithSubstitute<IReviewPromptService>(services, CreateReviewPromptServiceStub());
|
||||
ReplaceWithSubstitute<ICourseService>(services, CreateCourseServiceStub());
|
||||
ReplaceWithSubstitute<ITagService>(services, CreateTagServiceStub());
|
||||
ReplaceWithSubstitute<ILocationService>(services, CreateLocationServiceStub());
|
||||
@@ -226,6 +227,19 @@ public class ApiWebApplicationFactory : WebApplicationFactory<Program>
|
||||
return stub;
|
||||
}
|
||||
|
||||
private static IReviewPromptService CreateReviewPromptServiceStub()
|
||||
{
|
||||
var stub = Substitute.For<IReviewPromptService>();
|
||||
var promptDto = new ReviewPromptDto(
|
||||
"Analyze {lectureContext}. Review: {reviewText}",
|
||||
DateTime.UtcNow);
|
||||
|
||||
stub.GetAsync().Returns(promptDto);
|
||||
stub.UpdateAsync(Arg.Any<UpdateReviewPromptRequest>()).Returns(callInfo =>
|
||||
new ReviewPromptDto(callInfo.Arg<UpdateReviewPromptRequest>().Prompt, DateTime.UtcNow));
|
||||
return stub;
|
||||
}
|
||||
|
||||
private static ICourseService CreateCourseServiceStub()
|
||||
{
|
||||
var stub = Substitute.For<ICourseService>();
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using UniVerse.Application.DTOs.Reviews;
|
||||
using UniVerse.Application.Interfaces;
|
||||
using UniVerse.Application.Prompts;
|
||||
using UniVerse.Domain.Exceptions;
|
||||
using UniVerse.Infrastructure.Data;
|
||||
using UniVerse.Infrastructure.ExternalServices;
|
||||
using UniVerse.Infrastructure.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace UniVerse.Api.Tests.Reviews;
|
||||
|
||||
public class ReviewPromptServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetAsync_ReturnsDefaultPrompt_WhenSettingDoesNotExist()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
var service = new ReviewPromptService(db);
|
||||
|
||||
var result = await service.GetAsync();
|
||||
|
||||
Assert.Equal(ReviewPromptTemplate.Default, result.Prompt);
|
||||
Assert.Null(result.UpdatedAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_UpsertsSingletonPrompt()
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
var service = new ReviewPromptService(db);
|
||||
|
||||
await service.UpdateAsync(new UpdateReviewPromptRequest("First {lectureContext} {reviewText}"));
|
||||
var result = await service.UpdateAsync(new UpdateReviewPromptRequest("Second {lectureContext} {reviewText}"));
|
||||
|
||||
Assert.Equal("Second {lectureContext} {reviewText}", result.Prompt);
|
||||
Assert.NotNull(result.UpdatedAt);
|
||||
Assert.Equal(1, await db.ReviewPromptSettings.CountAsync());
|
||||
Assert.Equal("Second {lectureContext} {reviewText}", (await service.GetAsync()).Prompt);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("Prompt without placeholders")]
|
||||
[InlineData("Only lecture {lectureContext}")]
|
||||
[InlineData("Only review {reviewText}")]
|
||||
public async Task UpdateAsync_RejectsInvalidPrompt(string prompt)
|
||||
{
|
||||
await using var db = CreateDbContext();
|
||||
var service = new ReviewPromptService(db);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
service.UpdateAsync(new UpdateReviewPromptRequest(prompt)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeReviewAsync_RendersCustomPrompt()
|
||||
{
|
||||
var handler = new CapturingHandler();
|
||||
var http = new HttpClient(handler)
|
||||
{
|
||||
BaseAddress = new Uri("https://llm.test/")
|
||||
};
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Llm:Model"] = "test-model",
|
||||
["Llm:ApiKey"] = "test-key"
|
||||
})
|
||||
.Build();
|
||||
var promptService = Substitute.For<IReviewPromptService>();
|
||||
promptService.GetAsync().Returns(new ReviewPromptDto(
|
||||
"Custom prompt. Context: {lectureContext}. Text: {reviewText}",
|
||||
DateTime.UtcNow));
|
||||
var client = new LlmClient(http, config, promptService, NullLogger<LlmClient>.Instance);
|
||||
|
||||
await client.AnalyzeReviewAsync("Very useful review", "Lecture: Algebra");
|
||||
|
||||
Assert.NotNull(handler.RequestBody);
|
||||
using var requestJson = JsonDocument.Parse(handler.RequestBody!);
|
||||
var content = requestJson.RootElement
|
||||
.GetProperty("messages")[0]
|
||||
.GetProperty("content")
|
||||
.GetString();
|
||||
|
||||
Assert.Contains("Custom prompt", content);
|
||||
Assert.Contains("Lecture: Algebra", content);
|
||||
Assert.Contains("Very useful review", content);
|
||||
Assert.DoesNotContain(ReviewPromptTemplate.LectureContextPlaceholder, content);
|
||||
Assert.DoesNotContain(ReviewPromptTemplate.ReviewTextPlaceholder, content);
|
||||
}
|
||||
|
||||
private static AppDbContext CreateDbContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseInMemoryDatabase($"ReviewPromptServiceTests_{Guid.NewGuid()}")
|
||||
.Options;
|
||||
return new AppDbContext(options);
|
||||
}
|
||||
|
||||
private sealed class CapturingHandler : HttpMessageHandler
|
||||
{
|
||||
public string? RequestBody { get; private set; }
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
RequestBody = request.Content is null
|
||||
? null
|
||||
: await request.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
const string responsePayload = """
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"content": "{\"qualityScore\":0.8,\"sentiment\":\"Positive\",\"tags\":[\"practice\"],\"isInformative\":true}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(responsePayload, Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user