Version Update Modal Rework + A few bugfixes (#3664)

This commit is contained in:
Joe Milazzo 2025-03-22 15:05:48 -05:00 committed by GitHub
parent 9fb3bdd548
commit 43d0d1277f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 1963 additions and 805 deletions

View file

@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Constants;
using API.Data;
using API.Data.Repositories;
using API.DTOs.Collection;
@ -10,6 +12,8 @@ using API.Helpers.Builders;
using API.Services;
using API.Services.Plus;
using API.SignalR;
using Kavita.Common;
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using Xunit;
@ -53,6 +57,64 @@ public class CollectionTagServiceTests : AbstractDbTest
await _unitOfWork.CommitAsync();
}
#region DeleteTag
[Fact]
public async Task DeleteTag_ShouldDeleteTag_WhenTagExists()
{
// Arrange
await SeedSeries();
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.NotNull(user);
// Act
var result = await _service.DeleteTag(1, user);
// Assert
Assert.True(result);
var deletedTag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.Null(deletedTag);
Assert.Single(user.Collections); // Only one collection should remain
}
[Fact]
public async Task DeleteTag_ShouldReturnTrue_WhenTagDoesNotExist()
{
// Arrange
await SeedSeries();
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.NotNull(user);
// Act - Try to delete a non-existent tag
var result = await _service.DeleteTag(999, user);
// Assert
Assert.True(result); // Should return true because the tag is already "deleted"
Assert.Equal(2, user.Collections.Count); // Both collections should remain
}
[Fact]
public async Task DeleteTag_ShouldNotAffectOtherTags()
{
// Arrange
await SeedSeries();
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.NotNull(user);
// Act
var result = await _service.DeleteTag(1, user);
// Assert
Assert.True(result);
var remainingTag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2);
Assert.NotNull(remainingTag);
Assert.Equal("Tag 2", remainingTag.Title);
Assert.True(remainingTag.Promoted);
}
#endregion
#region UpdateTag
[Fact]
@ -111,6 +173,189 @@ public class CollectionTagServiceTests : AbstractDbTest
Assert.Equal("UpdateTag_ShouldNotChangeTitle_WhenNotKavitaSource", tag.Title);
Assert.False(string.IsNullOrEmpty(tag.Summary));
}
[Fact]
public async Task UpdateTag_ShouldThrowException_WhenTagDoesNotExist()
{
// Arrange
await SeedSeries();
// Act & Assert
var exception = await Assert.ThrowsAsync<KavitaException>(() => _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Non-existent Tag",
Id = 999, // Non-existent ID
Promoted = false
}, 1));
Assert.Equal("collection-doesnt-exist", exception.Message);
}
[Fact]
public async Task UpdateTag_ShouldThrowException_WhenUserDoesNotOwnTag()
{
// Arrange
await SeedSeries();
// Create a second user
var user2 = new AppUserBuilder("user2", "user2", Seed.DefaultThemes.First()).Build();
_unitOfWork.UserRepository.Add(user2);
await _unitOfWork.CommitAsync();
// Act & Assert
var exception = await Assert.ThrowsAsync<KavitaException>(() => _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 1",
Id = 1, // This belongs to user1
Promoted = false
}, 2)); // User with ID 2
Assert.Equal("access-denied", exception.Message);
}
[Fact]
public async Task UpdateTag_ShouldThrowException_WhenTitleIsEmpty()
{
// Arrange
await SeedSeries();
// Act & Assert
var exception = await Assert.ThrowsAsync<KavitaException>(() => _service.UpdateTag(new AppUserCollectionDto()
{
Title = " ", // Empty after trimming
Id = 1,
Promoted = false
}, 1));
Assert.Equal("collection-tag-title-required", exception.Message);
}
[Fact]
public async Task UpdateTag_ShouldThrowException_WhenTitleAlreadyExists()
{
// Arrange
await SeedSeries();
// Act & Assert
var exception = await Assert.ThrowsAsync<KavitaException>(() => _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 2", // Already exists
Id = 1, // Trying to rename Tag 1 to Tag 2
Promoted = false
}, 1));
Assert.Equal("collection-tag-duplicate", exception.Message);
}
[Fact]
public async Task UpdateTag_ShouldUpdateCoverImageSettings()
{
// Arrange
await SeedSeries();
// Act
await _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 1",
Id = 1,
CoverImageLocked = true
}, 1);
// Assert
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.True(tag.CoverImageLocked);
// Now test unlocking the cover image
await _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 1",
Id = 1,
CoverImageLocked = false
}, 1);
tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.False(tag.CoverImageLocked);
Assert.Equal(string.Empty, tag.CoverImage);
}
[Fact]
public async Task UpdateTag_ShouldAllowPromoteForAdminRole()
{
// Arrange
await SeedSeries();
// Setup a user with admin role
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.NotNull(user);
await AddUserWithRole(user.Id, PolicyConstants.AdminRole);
// Act - Try to promote a tag that wasn't previously promoted
await _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 1",
Id = 1,
Promoted = true
}, 1);
// Assert
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.True(tag.Promoted);
}
[Fact]
public async Task UpdateTag_ShouldAllowPromoteForPromoteRole()
{
// Arrange
await SeedSeries();
// Setup a user with promote role
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.NotNull(user);
// Mock to return promote role for the user
await AddUserWithRole(user.Id, PolicyConstants.PromoteRole);
// Act - Try to promote a tag that wasn't previously promoted
await _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 1",
Id = 1,
Promoted = true
}, 1);
// Assert
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.True(tag.Promoted);
}
[Fact]
public async Task UpdateTag_ShouldNotChangePromotion_WhenUserHasNoPermission()
{
// Arrange
await SeedSeries();
// Setup a user with no special roles
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.NotNull(user);
// Act - Try to promote a tag without proper role
await _service.UpdateTag(new AppUserCollectionDto()
{
Title = "Tag 1",
Id = 1,
Promoted = true
}, 1);
// Assert
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.False(tag.Promoted); // Should remain unpromoted
}
#endregion
@ -131,7 +376,7 @@ public class CollectionTagServiceTests : AbstractDbTest
await _service.RemoveTagFromSeries(tag, new[] {1});
var userCollections = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections);
Assert.Equal(2, userCollections!.Collections.Count);
Assert.Equal(1, tag.Items.Count);
Assert.Single(tag.Items);
Assert.Equal(2, tag.Items.First().Id);
}
@ -175,6 +420,111 @@ public class CollectionTagServiceTests : AbstractDbTest
Assert.Null(tag2);
}
[Fact]
public async Task RemoveTagFromSeries_ShouldReturnFalse_WhenTagIsNull()
{
// Act
var result = await _service.RemoveTagFromSeries(null, [1]);
// Assert
Assert.False(result);
}
[Fact]
public async Task RemoveTagFromSeries_ShouldHandleEmptySeriesIdsList()
{
// Arrange
await SeedSeries();
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
var initialItemCount = tag.Items.Count;
// Act
var result = await _service.RemoveTagFromSeries(tag, Array.Empty<int>());
// Assert
Assert.True(result);
tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.Equal(initialItemCount, tag.Items.Count); // No items should be removed
}
[Fact]
public async Task RemoveTagFromSeries_ShouldHandleNonExistentSeriesIds()
{
// Arrange
await SeedSeries();
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
var initialItemCount = tag.Items.Count;
// Act - Try to remove a series that doesn't exist in the tag
var result = await _service.RemoveTagFromSeries(tag, [999]);
// Assert
Assert.True(result);
tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
Assert.Equal(initialItemCount, tag.Items.Count); // No items should be removed
}
[Fact]
public async Task RemoveTagFromSeries_ShouldHandleNullItemsList()
{
// Arrange
await SeedSeries();
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.NotNull(tag);
// Force null items list
tag.Items = null;
_unitOfWork.CollectionTagRepository.Update(tag);
await _unitOfWork.CommitAsync();
// Act
var result = await _service.RemoveTagFromSeries(tag, [1]);
// Assert
Assert.True(result);
// The tag should not be removed since the items list was null, not empty
var tagAfter = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1);
Assert.Null(tagAfter);
}
[Fact]
public async Task RemoveTagFromSeries_ShouldUpdateAgeRating_WhenMultipleSeriesRemain()
{
// Arrange
await SeedSeries();
// Add a third series with a different age rating
var s3 = new SeriesBuilder("Series 3").WithMetadata(new SeriesMetadataBuilder().WithAgeRating(AgeRating.PG).Build()).Build();
_context.Library.First().Series.Add(s3);
await _unitOfWork.CommitAsync();
// Add series 3 to tag 2
var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2);
Assert.NotNull(tag);
tag.Items.Add(s3);
_unitOfWork.CollectionTagRepository.Update(tag);
await _unitOfWork.CommitAsync();
// Act - Remove the series with Mature rating
await _service.RemoveTagFromSeries(tag, new[] {1});
// Assert
tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2);
Assert.NotNull(tag);
Assert.Equal(2, tag.Items.Count);
// The age rating should be updated to the highest remaining rating (PG)
Assert.Equal(AgeRating.PG, tag.AgeRating);
}
#endregion
}

View file

@ -11,6 +11,7 @@ using API.DTOs.Scrobbling;
using API.Entities;
using API.Entities.Enums;
using API.Entities.Metadata;
using API.Entities.MetadataMatching;
using API.Helpers.Builders;
using API.Services.Plus;
using API.Services.Tasks.Metadata;

View file

@ -167,7 +167,6 @@ public class ScannerServiceTests : AbstractDbTest
Assert.NotNull(postLib.Series.First().Volumes.FirstOrDefault(v => v.Chapters.FirstOrDefault(c => c.IsSpecial) != null));
}
[Fact]
public async Task ScanLibrary_SeriesWithUnbalancedParenthesis()
{

View file

@ -59,6 +59,7 @@ public class SeriesServiceTests : AbstractDbTest
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
Substitute.For<IScrobblingService>(), locService, Substitute.For<IReadingListService>());
}
#region Setup
protected override async Task ResetDb()

View file

@ -0,0 +1,292 @@
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs.KavitaPlus.Metadata;
using API.Entities;
using API.Entities.Enums;
using API.Entities.MetadataMatching;
using API.Services;
using API.Services.Tasks.Scanner;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
namespace API.Tests.Services;
public class SettingsServiceTests
{
private readonly ISettingsService _settingsService;
private readonly IUnitOfWork _mockUnitOfWork;
public SettingsServiceTests()
{
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), new FileSystem());
_mockUnitOfWork = Substitute.For<IUnitOfWork>();
_settingsService = new SettingsService(_mockUnitOfWork, ds,
Substitute.For<ILibraryWatcher>(), Substitute.For<ITaskScheduler>(),
Substitute.For<ILogger<SettingsService>>());
}
#region UpdateMetadataSettings
[Fact]
public async Task UpdateMetadataSettings_ShouldUpdateExistingSettings()
{
// Arrange
var existingSettings = new MetadataSettings
{
Id = 1,
Enabled = false,
EnableSummary = false,
EnableLocalizedName = false,
EnablePublicationStatus = false,
EnableRelationships = false,
EnablePeople = false,
EnableStartDate = false,
EnableGenres = false,
EnableTags = false,
FirstLastPeopleNaming = false,
EnableCoverImage = false,
AgeRatingMappings = new Dictionary<string, AgeRating>(),
Blacklist = [],
Whitelist = [],
Overrides = [],
PersonRoles = [],
FieldMappings = []
};
var settingsRepo = Substitute.For<ISettingsRepository>();
settingsRepo.GetMetadataSettings().Returns(Task.FromResult(existingSettings));
settingsRepo.GetMetadataSettingDto().Returns(Task.FromResult(new MetadataSettingsDto()));
_mockUnitOfWork.SettingsRepository.Returns(settingsRepo);
var updateDto = new MetadataSettingsDto
{
Enabled = true,
EnableSummary = true,
EnableLocalizedName = true,
EnablePublicationStatus = true,
EnableRelationships = true,
EnablePeople = true,
EnableStartDate = true,
EnableGenres = true,
EnableTags = true,
FirstLastPeopleNaming = true,
EnableCoverImage = true,
AgeRatingMappings = new Dictionary<string, AgeRating> { { "Adult", AgeRating.R18Plus } },
Blacklist = ["blacklisted-tag"],
Whitelist = ["whitelisted-tag"],
Overrides = [MetadataSettingField.Summary],
PersonRoles = [PersonRole.Writer],
FieldMappings =
[
new MetadataFieldMappingDto
{
SourceType = MetadataFieldType.Genre,
DestinationType = MetadataFieldType.Tag,
SourceValue = "Action",
DestinationValue = "Fight",
ExcludeFromSource = true
}
]
};
// Act
await _settingsService.UpdateMetadataSettings(updateDto);
// Assert
await _mockUnitOfWork.Received(1).CommitAsync();
// Verify properties were updated
Assert.True(existingSettings.Enabled);
Assert.True(existingSettings.EnableSummary);
Assert.True(existingSettings.EnableLocalizedName);
Assert.True(existingSettings.EnablePublicationStatus);
Assert.True(existingSettings.EnableRelationships);
Assert.True(existingSettings.EnablePeople);
Assert.True(existingSettings.EnableStartDate);
Assert.True(existingSettings.EnableGenres);
Assert.True(existingSettings.EnableTags);
Assert.True(existingSettings.FirstLastPeopleNaming);
Assert.True(existingSettings.EnableCoverImage);
// Verify collections were updated
Assert.Single(existingSettings.AgeRatingMappings);
Assert.Equal(AgeRating.R18Plus, existingSettings.AgeRatingMappings["Adult"]);
Assert.Single(existingSettings.Blacklist);
Assert.Equal("blacklisted-tag", existingSettings.Blacklist[0]);
Assert.Single(existingSettings.Whitelist);
Assert.Equal("whitelisted-tag", existingSettings.Whitelist[0]);
Assert.Single(existingSettings.Overrides);
Assert.Equal(MetadataSettingField.Summary, existingSettings.Overrides[0]);
Assert.Single(existingSettings.PersonRoles);
Assert.Equal(PersonRole.Writer, existingSettings.PersonRoles[0]);
Assert.Single(existingSettings.FieldMappings);
Assert.Equal(MetadataFieldType.Genre, existingSettings.FieldMappings[0].SourceType);
Assert.Equal(MetadataFieldType.Tag, existingSettings.FieldMappings[0].DestinationType);
Assert.Equal("Action", existingSettings.FieldMappings[0].SourceValue);
Assert.Equal("Fight", existingSettings.FieldMappings[0].DestinationValue);
Assert.True(existingSettings.FieldMappings[0].ExcludeFromSource);
}
[Fact]
public async Task UpdateMetadataSettings_WithNullCollections_ShouldUseEmptyCollections()
{
// Arrange
var existingSettings = new MetadataSettings
{
Id = 1,
FieldMappings = [new MetadataFieldMapping {Id = 1, SourceValue = "OldValue"}]
};
var settingsRepo = Substitute.For<ISettingsRepository>();
settingsRepo.GetMetadataSettings().Returns(Task.FromResult(existingSettings));
settingsRepo.GetMetadataSettingDto().Returns(Task.FromResult(new MetadataSettingsDto()));
_mockUnitOfWork.SettingsRepository.Returns(settingsRepo);
var updateDto = new MetadataSettingsDto
{
AgeRatingMappings = null,
Blacklist = null,
Whitelist = null,
Overrides = null,
PersonRoles = null,
FieldMappings = null
};
// Act
await _settingsService.UpdateMetadataSettings(updateDto);
// Assert
await _mockUnitOfWork.Received(1).CommitAsync();
Assert.Empty(existingSettings.AgeRatingMappings);
Assert.Empty(existingSettings.Blacklist);
Assert.Empty(existingSettings.Whitelist);
Assert.Empty(existingSettings.Overrides);
Assert.Empty(existingSettings.PersonRoles);
// Verify existing field mappings were cleared
settingsRepo.Received(1).RemoveRange(Arg.Any<List<MetadataFieldMapping>>());
Assert.Empty(existingSettings.FieldMappings);
}
[Fact]
public async Task UpdateMetadataSettings_WithFieldMappings_ShouldReplaceExistingMappings()
{
// Arrange
var existingSettings = new MetadataSettings
{
Id = 1,
FieldMappings =
[
new MetadataFieldMapping
{
Id = 1,
SourceType = MetadataFieldType.Genre,
DestinationType = MetadataFieldType.Genre,
SourceValue = "OldValue",
DestinationValue = "OldDestination",
ExcludeFromSource = false
}
]
};
var settingsRepo = Substitute.For<ISettingsRepository>();
settingsRepo.GetMetadataSettings().Returns(Task.FromResult(existingSettings));
settingsRepo.GetMetadataSettingDto().Returns(Task.FromResult(new MetadataSettingsDto()));
_mockUnitOfWork.SettingsRepository.Returns(settingsRepo);
var updateDto = new MetadataSettingsDto
{
FieldMappings =
[
new MetadataFieldMappingDto
{
SourceType = MetadataFieldType.Tag,
DestinationType = MetadataFieldType.Genre,
SourceValue = "NewValue",
DestinationValue = "NewDestination",
ExcludeFromSource = true
},
new MetadataFieldMappingDto
{
SourceType = MetadataFieldType.Tag,
DestinationType = MetadataFieldType.Tag,
SourceValue = "AnotherValue",
DestinationValue = "AnotherDestination",
ExcludeFromSource = false
}
]
};
// Act
await _settingsService.UpdateMetadataSettings(updateDto);
// Assert
await _mockUnitOfWork.Received(1).CommitAsync();
// Verify existing field mappings were cleared and new ones added
settingsRepo.Received(1).RemoveRange(Arg.Any<List<MetadataFieldMapping>>());
Assert.Equal(2, existingSettings.FieldMappings.Count);
// Verify first mapping
Assert.Equal(MetadataFieldType.Tag, existingSettings.FieldMappings[0].SourceType);
Assert.Equal(MetadataFieldType.Genre, existingSettings.FieldMappings[0].DestinationType);
Assert.Equal("NewValue", existingSettings.FieldMappings[0].SourceValue);
Assert.Equal("NewDestination", existingSettings.FieldMappings[0].DestinationValue);
Assert.True(existingSettings.FieldMappings[0].ExcludeFromSource);
// Verify second mapping
Assert.Equal(MetadataFieldType.Tag, existingSettings.FieldMappings[1].SourceType);
Assert.Equal(MetadataFieldType.Tag, existingSettings.FieldMappings[1].DestinationType);
Assert.Equal("AnotherValue", existingSettings.FieldMappings[1].SourceValue);
Assert.Equal("AnotherDestination", existingSettings.FieldMappings[1].DestinationValue);
Assert.False(existingSettings.FieldMappings[1].ExcludeFromSource);
}
[Fact]
public async Task UpdateMetadataSettings_WithBlacklistWhitelist_ShouldNormalizeAndDeduplicateEntries()
{
// Arrange
var existingSettings = new MetadataSettings
{
Id = 1,
Blacklist = [],
Whitelist = []
};
// We need to mock the repository and provide a custom implementation for ToNormalized
var settingsRepo = Substitute.For<ISettingsRepository>();
settingsRepo.GetMetadataSettings().Returns(Task.FromResult(existingSettings));
settingsRepo.GetMetadataSettingDto().Returns(Task.FromResult(new MetadataSettingsDto()));
_mockUnitOfWork.SettingsRepository.Returns(settingsRepo);
var updateDto = new MetadataSettingsDto
{
// Include duplicates with different casing and whitespace
Blacklist = ["tag1", "Tag1", " tag2 ", "", " ", "tag3"],
Whitelist = ["allowed1", "Allowed1", " allowed2 ", "", "allowed3"]
};
// Act
await _settingsService.UpdateMetadataSettings(updateDto);
// Assert
await _mockUnitOfWork.Received(1).CommitAsync();
Assert.Equal(3, existingSettings.Blacklist.Count);
Assert.Equal(3, existingSettings.Whitelist.Count);
}
#endregion
}

View file

@ -65,13 +65,13 @@ public class VersionUpdaterServiceTests : IDisposable
[Fact]
public async Task CheckForUpdate_ShouldReturnNull_WhenGithubApiReturnsNull()
{
// Arrange
_httpTest.RespondWith("null");
// Act
var result = await _service.CheckForUpdate();
// Assert
Assert.Null(result);
}
@ -79,7 +79,7 @@ public class VersionUpdaterServiceTests : IDisposable
//[Fact]
public async Task CheckForUpdate_ShouldReturnUpdateNotification_WhenNewVersionIsAvailable()
{
// Arrange
var githubResponse = new
{
tag_name = "v0.6.0",
@ -91,10 +91,10 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(githubResponse);
// Act
var result = await _service.CheckForUpdate();
// Assert
Assert.NotNull(result);
Assert.Equal("0.6.0", result.UpdateVersion);
Assert.Equal("0.5.0.0", result.CurrentVersion);
@ -121,10 +121,10 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(githubResponse);
// Act
var result = await _service.CheckForUpdate();
// Assert
Assert.NotNull(result);
Assert.True(result.IsReleaseEqual);
Assert.False(result.IsReleaseNewer);
@ -134,7 +134,7 @@ public class VersionUpdaterServiceTests : IDisposable
//[Fact]
public async Task PushUpdate_ShouldSendUpdateEvent_WhenNewerVersionAvailable()
{
// Arrange
var update = new UpdateNotificationDto
{
UpdateVersion = "0.6.0",
@ -145,10 +145,10 @@ public class VersionUpdaterServiceTests : IDisposable
PublishDate = null
};
// Act
await _service.PushUpdate(update);
// Assert
await _eventHub.Received(1).SendMessageAsync(
Arg.Is(MessageFactory.UpdateAvailable),
Arg.Any<SignalRMessage>(),
@ -159,7 +159,7 @@ public class VersionUpdaterServiceTests : IDisposable
[Fact]
public async Task PushUpdate_ShouldNotSendUpdateEvent_WhenVersionIsEqual()
{
// Arrange
var update = new UpdateNotificationDto
{
UpdateVersion = "0.5.0.0",
@ -170,10 +170,10 @@ public class VersionUpdaterServiceTests : IDisposable
PublishDate = null
};
// Act
await _service.PushUpdate(update);
// Assert
await _eventHub.DidNotReceive().SendMessageAsync(
Arg.Any<string>(),
Arg.Any<SignalRMessage>(),
@ -184,7 +184,7 @@ public class VersionUpdaterServiceTests : IDisposable
[Fact]
public async Task GetAllReleases_ShouldReturnReleases_LimitedByCount()
{
// Arrange
var releases = new List<object>
{
new
@ -215,10 +215,10 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(releases);
// Act
var result = await _service.GetAllReleases(2);
// Assert
Assert.Equal(2, result.Count);
Assert.Equal("0.7.0.0", result[0].UpdateVersion);
Assert.Equal("0.6.0", result[1].UpdateVersion);
@ -227,7 +227,7 @@ public class VersionUpdaterServiceTests : IDisposable
[Fact]
public async Task GetAllReleases_ShouldUseCachedData_WhenCacheIsValid()
{
// Arrange
var releases = new List<UpdateNotificationDto>
{
new()
@ -257,10 +257,10 @@ public class VersionUpdaterServiceTests : IDisposable
await File.WriteAllTextAsync(cacheFilePath, System.Text.Json.JsonSerializer.Serialize(releases));
File.SetLastWriteTimeUtc(cacheFilePath, DateTime.UtcNow); // Ensure it's fresh
// Act
var result = await _service.GetAllReleases();
// Assert
Assert.Equal(2, result.Count);
Assert.Empty(_httpTest.CallLog); // No HTTP calls made
}
@ -268,7 +268,7 @@ public class VersionUpdaterServiceTests : IDisposable
[Fact]
public async Task GetAllReleases_ShouldFetchNewData_WhenCacheIsExpired()
{
// Arrange
var releases = new List<UpdateNotificationDto>
{
new()
@ -303,10 +303,10 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(newReleases);
// Act
var result = await _service.GetAllReleases();
// Assert
Assert.Equal(1, result.Count);
Assert.Equal("0.7.0.0", result[0].UpdateVersion);
Assert.NotEmpty(_httpTest.CallLog); // HTTP call was made
@ -314,7 +314,7 @@ public class VersionUpdaterServiceTests : IDisposable
public async Task GetNumberOfReleasesBehind_ShouldReturnCorrectCount()
{
// Arrange
var releases = new List<object>
{
new
@ -345,16 +345,16 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(releases);
// Act
var result = await _service.GetNumberOfReleasesBehind();
// Assert
Assert.Equal(2 + 1, result); // Behind 0.7.0 and 0.6.0 - We have to add 1 because the current release is > 0.7.0
}
public async Task GetNumberOfReleasesBehind_ShouldReturnCorrectCount_WithNightlies()
{
// Arrange
var releases = new List<object>
{
new
@ -377,17 +377,17 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(releases);
// Act
var result = await _service.GetNumberOfReleasesBehind();
// Assert
Assert.Equal(2, result); // We have to add 1 because the current release is > 0.7.0
}
[Fact]
public async Task ParseReleaseBody_ShouldExtractSections()
{
// Arrange
var githubResponse = new
{
tag_name = "v0.6.0",
@ -399,10 +399,10 @@ public class VersionUpdaterServiceTests : IDisposable
_httpTest.RespondWithJson(githubResponse);
// Act
var result = await _service.CheckForUpdate();
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Added.Count);
Assert.Equal(2, result.Fixed.Count);
@ -414,7 +414,7 @@ public class VersionUpdaterServiceTests : IDisposable
[Fact]
public async Task GetAllReleases_ShouldHandleNightlyBuilds()
{
// Arrange
// Set BuildInfo.Version to a nightly build version
typeof(BuildInfo).GetProperty(nameof(BuildInfo.Version))?.SetValue(null, new Version("0.7.1.0"));
@ -444,10 +444,10 @@ public class VersionUpdaterServiceTests : IDisposable
// Mock commit info for develop branch
_httpTest.RespondWithJson(new List<object>());
// Act
var result = await _service.GetAllReleases();
// Assert
Assert.NotNull(result);
Assert.True(result[0].IsOnNightlyInRelease);
}