Skip to content

Conversation

@punkouter26
Copy link
Owner

Fixed several issues in the codebase:

  • Corrected LyricsService to correctly deserialize JSON by adding [JsonPropertyName("content")] to SongEntry and enabling case-insensitive property matching.
  • Changed LyricsService and other backend services to Singleton lifetime to enable caching and optimize resource usage.
  • Removed incorrect file path manipulation in LyricsService.GetLyricsAsync which caused issues with song titles containing dots.
  • Updated integration test GetAvailableSongs_ShouldReturnList to use the correct API endpoint /api/lyrics/titles.
  • Updated global.json to match the installed .NET SDK version.

PR created automatically by Jules for task 8833489810780996995 started by @punkouter26

Copilot AI review requested due to automatic review settings January 31, 2026 15:26
@google-labs-jules
Copy link

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Comment on lines +128 to +130
builder.Services.AddSingleton<IDiagnosticValidator, AzureOpenAIDiagnosticValidator>();
builder.Services.AddSingleton<IDiagnosticValidator, AzureSpeechDiagnosticValidator>();
builder.Services.AddSingleton<IDiagnosticValidator, InternetConnectivityDiagnosticValidator>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple implementations of IDiagnosticValidator are registered as singletons:

builder.Services.AddSingleton<IDiagnosticValidator, AzureOpenAIDiagnosticValidator>();
builder.Services.AddSingleton<IDiagnosticValidator, AzureSpeechDiagnosticValidator>();
builder.Services.AddSingleton<IDiagnosticValidator, InternetConnectivityDiagnosticValidator>();

Ensure that all consumers of IDiagnosticValidator are designed to resolve IEnumerable<IDiagnosticValidator>. If any consumer expects a single instance, this will cause runtime errors or unexpected behavior. Review all usages of IDiagnosticValidator to confirm correct resolution.

Recommended solution:

  • If multiple implementations are intended, ensure all consumers use IEnumerable<IDiagnosticValidator>.
  • If only one implementation should be used, register only the required one.

Comment on lines 13 to 17
/// <summary>
/// Gets lyrics for a specific song, limited to 200 words.
/// </summary>
Task<string?> GetLyricsAsync(string songFileName);
Task<string?> GetLyricsAsync(string songTitle);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ambiguity in Error Handling

The interface methods GetLyricsAsync and GetRandomLyricsAsync return nullable types, but do not specify how errors (e.g., song not found, IO exceptions) should be handled or propagated. This can lead to inconsistent error handling across implementations. Consider defining a consistent error handling strategy, such as returning a result type that encapsulates both data and error information, or documenting expected exception behavior.

Comment on lines 13 to 17
/// <summary>
/// Gets lyrics for a specific song, limited to 200 words.
/// </summary>
Task<string?> GetLyricsAsync(string songFileName);
Task<string?> GetLyricsAsync(string songTitle);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear Enforcement of 200-Word Limit

The summary for GetLyricsAsync states that lyrics are limited to 200 words, but the interface does not specify how this limit is enforced or whether it is the responsibility of the implementation. This ambiguity may result in inconsistent behavior. It is recommended to clarify in the interface contract whether the limit is enforced by the service or expected from the data source, and to document the expected behavior.

Comment on lines +47 to 50
_lyricsCache = JsonSerializer.Deserialize<LyricsCollection>(jsonContent, _jsonOptions);

if (_lyricsCache is null)
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deserialization of the lyrics collection does not handle exceptions that may occur if the JSON is malformed or unreadable. This could result in an unhandled exception and application crash.

Recommendation:
Wrap the deserialization in a try-catch block and log the exception, returning a controlled error or null value as appropriate. For example:

try
{
    _lyricsCache = JsonSerializer.Deserialize<LyricsCollection>(jsonContent, _jsonOptions);
}
catch (JsonException ex)
{
    _logger.LogError(ex, "Failed to deserialize lyrics collection JSON");
    throw new InvalidOperationException("Failed to deserialize lyrics collection", ex);
}

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes lyrics loading by aligning JSON deserialization with the lyrics-collection.json schema, updates the lyrics API integration test to hit the correct titles endpoint, and changes several DI registrations to Singleton to support caching.

Changes:

  • Fix lyrics JSON deserialization (map contentLyrics, enable case-insensitive property matching).
  • Adjust lyrics lookup to use songTitle directly (avoid stripping dotted titles).
  • Update integration test endpoint and change multiple service lifetimes to Singleton; update global.json SDK pin.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Po.VicTranslate.IntegrationTests/LyricsEndpointTests.cs Updates the integration test to call /api/lyrics/titles.
src/PoVicTranslate.Web/Services/LyricsService.cs Uses case-insensitive JSON options and changes lookup to compare by songTitle.
src/PoVicTranslate.Web/Services/ILyricsService.cs Renames the API parameter from songFileName to songTitle.
src/PoVicTranslate.Web/Program.cs Switches several service registrations from Scoped to Singleton.
src/PoVicTranslate.Web/Models/LyricsCollection.cs Maps JSON content field onto SongEntry.Lyrics.
global.json Pins the repo SDK version to 10.0.100.

Comment on lines +3 to 5
"version": "10.0.100",
"rollForward": "latestPatch",
"allowPrerelease": false
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global.json now pins the SDK to 10.0.100 with rollForward: latestPatch. latestPatch will not roll forward across SDK feature bands (e.g., 10.0.100 -> 10.0.102), so this can break builds on machines/CI that only have 10.0.102 installed (your GitHub Actions workflow installs 10.0.x). Consider either pinning to the feature band used in CI (e.g., 10.0.102) or changing rollForward to latestFeature/latestMinor, or updating CI to install 10.0.100 explicitly.

Copilot uses AI. Check for mistakes.
// Register validation services
builder.Services.AddSingleton<ISpeechConfigValidator, SpeechConfigValidator>();
builder.Services.AddScoped<IAudioSynthesisService, AudioSynthesisService>();
builder.Services.AddSingleton<IAudioSynthesisService, AudioSynthesisService>();
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching AudioSynthesisService to Singleton introduces shared mutable state across requests (_accessToken/_tokenExpiry in AudioSynthesisService). As written, token refresh is not synchronized, so concurrent calls can race and trigger multiple token fetches (or transiently observe stale values). Either keep this service scoped, or add synchronization around token refresh (e.g., a lock/SemaphoreSlim) or move token caching into IMemoryCache with proper concurrency control.

Suggested change
builder.Services.AddSingleton<IAudioSynthesisService, AudioSynthesisService>();
builder.Services.AddScoped<IAudioSynthesisService, AudioSynthesisService>();

Copilot uses AI. Check for mistakes.
Comment on lines 46 to 48
var jsonContent = await File.ReadAllTextAsync(_lyricsFilePath);
_lyricsCache = JsonSerializer.Deserialize<LyricsCollection>(jsonContent);
_lyricsCache = JsonSerializer.Deserialize<LyricsCollection>(jsonContent, _jsonOptions);

Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR fixes JSON deserialization and changes how lyrics are looked up by songTitle, but the integration tests still don’t cover the successful path of /api/lyrics/{songTitle} returning non-empty lyrics (and the 200-word truncation behavior). Adding a test that fetches /api/lyrics/titles, picks a title, then calls /api/lyrics/{title} and asserts a non-empty Lyrics field (and optionally word-count <= 200) would prevent regressions.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants