using PnvPanel.Domain.Configs; using PnvPanel.Domain.Exceptions; using PnvPanel.Domain.Inbounds; using Xunit; namespace PnvPanel.Domain.Tests.Configs; public class VpnConfigTests { [Fact] public void Create_SetsActiveStatusAndGeneratesEmailAndToken() { var userId = Guid.NewGuid(); var inboundId = Guid.NewGuid(); var config = VpnConfig.Create(userId, inboundId, VpnProtocol.Vless, "My device", deviceLimit: 3); Assert.Equal(userId, config.UserId); Assert.Equal(inboundId, config.InboundId); Assert.Equal(VpnProtocol.Vless, config.Protocol); Assert.Equal("My device", config.Label); Assert.Equal(3, config.DeviceLimit); Assert.Equal(ConfigStatus.Active, config.Status); Assert.Equal(string.Empty, config.ClientExternalId); Assert.False(string.IsNullOrWhiteSpace(config.ClientEmail)); Assert.StartsWith("pnv_", config.ClientEmail); Assert.False(string.IsNullOrWhiteSpace(config.SubscriptionToken)); Assert.NotEqual(Guid.Empty, config.Id); } [Fact] public void Create_GeneratesUniqueSubscriptionTokensAndClientEmails() { var userId = Guid.NewGuid(); var a = VpnConfig.Create(userId, Guid.NewGuid(), VpnProtocol.Vless, null, 1); var b = VpnConfig.Create(userId, Guid.NewGuid(), VpnProtocol.Vless, null, 1); Assert.NotEqual(a.SubscriptionToken, b.SubscriptionToken); Assert.NotEqual(a.ClientEmail, b.ClientEmail); } [Fact] public void AssignRemoteClient_SetsClientExternalId() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Trojan, null, 1); config.AssignRemoteClient("some-remote-password"); Assert.Equal("some-remote-password", config.ClientExternalId); } [Fact] public void Rotate_WhenActive_ChangesEmailExternalIdAndToken() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.AssignRemoteClient("old-id"); var oldToken = config.SubscriptionToken; var oldEmail = config.ClientEmail; config.Rotate("new-email", "new-id"); Assert.Equal("new-email", config.ClientEmail); Assert.Equal("new-id", config.ClientExternalId); Assert.NotEqual(oldToken, config.SubscriptionToken); Assert.NotEqual(oldEmail, config.ClientEmail); } [Theory] [InlineData(ConfigStatus.Revoked)] [InlineData(ConfigStatus.Disabled)] public void Rotate_WhenNotActive_Throws(ConfigStatus status) { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); MoveToStatus(config, status); Assert.Throws(() => config.Rotate("e", "i")); } [Fact] public void Revoke_WhenActive_SetsRevokedStatus() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.Revoke(); Assert.Equal(ConfigStatus.Revoked, config.Status); } [Fact] public void Revoke_WhenAlreadyRevoked_Throws() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.Revoke(); Assert.Throws(() => config.Revoke()); } [Fact] public void Disable_WhenActive_SetsDisabled() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.Disable(); Assert.Equal(ConfigStatus.Disabled, config.Status); } [Fact] public void Disable_WhenRevoked_DoesNotChangeStatus() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.Revoke(); config.Disable(); Assert.Equal(ConfigStatus.Revoked, config.Status); } [Fact] public void Enable_WhenDisabled_ReturnsToActive() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.Disable(); config.Enable(); Assert.Equal(ConfigStatus.Active, config.Status); } [Fact] public void Enable_WhenRevoked_DoesNotResurrect() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.Revoke(); config.Enable(); Assert.Equal(ConfigStatus.Revoked, config.Status); } [Fact] public void UpdateTraffic_SetsBytesAndLastSyncAt() { var config = VpnConfig.Create(Guid.NewGuid(), Guid.NewGuid(), VpnProtocol.Vless, null, 1); config.UpdateTraffic(100, 200); Assert.Equal(100, config.UsedUpBytes); Assert.Equal(200, config.UsedDownBytes); Assert.NotNull(config.LastSyncAt); } [Fact] public void GenerateClientEmail_IsDeterministicPrefixWithRandomSuffix() { var userId = Guid.NewGuid(); var email1 = VpnConfig.GenerateClientEmail(userId); var email2 = VpnConfig.GenerateClientEmail(userId); var expectedPrefix = $"pnv_{userId:N}"[..12]; Assert.StartsWith(expectedPrefix, email1); Assert.NotEqual(email1, email2); } private static void MoveToStatus(VpnConfig config, ConfigStatus status) { switch (status) { case ConfigStatus.Revoked: config.Revoke(); break; case ConfigStatus.Disabled: config.Disable(); break; } } }