Files
2025-12-13 21:24:34 +02:00

513 lines
14 KiB
C#

using Cleanuparr.Domain.Enums;
using Cleanuparr.Persistence.Models.Configuration.Notification;
using Shouldly;
using Xunit;
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
public sealed class PushoverConfigTests
{
#region IsValid Tests
[Fact]
public void IsValid_WithValidApiTokenAndUserKey_ReturnsTrue()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal
};
config.IsValid().ShouldBeTrue();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void IsValid_WithEmptyOrNullApiToken_ReturnsFalse(string? apiToken)
{
var config = new PushoverConfig
{
ApiToken = apiToken ?? string.Empty,
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal
};
config.IsValid().ShouldBeFalse();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void IsValid_WithEmptyOrNullUserKey_ReturnsFalse(string? userKey)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = userKey ?? string.Empty,
Priority = PushoverPriority.Normal
};
config.IsValid().ShouldBeFalse();
}
#endregion
#region IsValid Emergency Priority Tests
[Fact]
public void IsValid_WithEmergencyPriority_ValidRetryAndExpire_ReturnsTrue()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = 3600
};
config.IsValid().ShouldBeTrue();
}
[Theory]
[InlineData(null)]
[InlineData(29)]
[InlineData(0)]
[InlineData(-1)]
public void IsValid_WithEmergencyPriority_InvalidRetry_ReturnsFalse(int? retry)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = retry,
Expire = 3600
};
config.IsValid().ShouldBeFalse();
}
[Theory]
[InlineData(null)]
[InlineData(0)]
[InlineData(-1)]
[InlineData(10801)]
public void IsValid_WithEmergencyPriority_InvalidExpire_ReturnsFalse(int? expire)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = expire
};
config.IsValid().ShouldBeFalse();
}
[Theory]
[InlineData(PushoverPriority.Lowest)]
[InlineData(PushoverPriority.Low)]
[InlineData(PushoverPriority.Normal)]
[InlineData(PushoverPriority.High)]
public void IsValid_WithNonEmergencyPriority_DoesNotRequireRetryAndExpire(PushoverPriority priority)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = priority,
Retry = null,
Expire = null
};
config.IsValid().ShouldBeTrue();
}
#endregion
#region IsValid Sound Tests
[Theory]
[InlineData(null)]
[InlineData("pushover")]
[InlineData("bike")]
[InlineData("custom-sound")]
public void IsValid_WithValidOrNullSound_ReturnsTrue(string? sound)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Sound = sound
};
config.IsValid().ShouldBeTrue();
}
[Theory]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
public void IsValid_WithWhitespaceOnlySound_ReturnsFalse(string sound)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Sound = sound
};
config.IsValid().ShouldBeFalse();
}
[Fact]
public void IsValid_WithEmptyStringSound_ReturnsTrue()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Sound = string.Empty
};
// Empty string has Length 0, so Sound.Length > 0 is false - the condition is skipped
config.IsValid().ShouldBeTrue();
}
#endregion
#region Validate Tests
[Fact]
public void Validate_WithValidConfig_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal
};
Should.NotThrow(() => config.Validate());
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Validate_WithEmptyOrNullApiToken_ThrowsValidationException(string? apiToken)
{
var config = new PushoverConfig
{
ApiToken = apiToken ?? string.Empty,
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe("Pushover API token is required");
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Validate_WithEmptyOrNullUserKey_ThrowsValidationException(string? userKey)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = userKey ?? string.Empty,
Priority = PushoverPriority.Normal
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe("Pushover user key is required");
}
#endregion
#region Validate Emergency Priority Tests
[Fact]
public void Validate_WithEmergencyPriority_ValidRetryAndExpire_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = 3600
};
Should.NotThrow(() => config.Validate());
}
[Fact]
public void Validate_WithEmergencyPriority_MinimumValidRetry_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = 1
};
Should.NotThrow(() => config.Validate());
}
[Fact]
public void Validate_WithEmergencyPriority_MaximumValidExpire_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = 10800
};
Should.NotThrow(() => config.Validate());
}
[Theory]
[InlineData(null)]
[InlineData(29)]
[InlineData(0)]
public void Validate_WithEmergencyPriority_RetryTooLowOrNull_ThrowsValidationException(int? retry)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = retry,
Expire = 3600
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe("Retry interval must be at least 30 seconds for emergency priority");
}
[Theory]
[InlineData(null)]
[InlineData(0)]
public void Validate_WithEmergencyPriority_ExpireNullOrZero_ThrowsValidationException(int? expire)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = expire
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe("Expire time is required for emergency priority");
}
[Fact]
public void Validate_WithEmergencyPriority_ExpireTooHigh_ThrowsValidationException()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Emergency,
Retry = 30,
Expire = 10801
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe("Expire time cannot exceed 10800 seconds (3 hours)");
}
#endregion
#region Validate Device Tests
[Fact]
public void Validate_WithValidDeviceNames_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = ["iphone", "android-phone", "my_device"]
};
Should.NotThrow(() => config.Validate());
}
[Fact]
public void Validate_WithEmptyDevicesList_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = []
};
Should.NotThrow(() => config.Validate());
}
[Fact]
public void Validate_WithDeviceNameTooLong_ThrowsValidationException()
{
var longDeviceName = new string('a', 26);
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = [longDeviceName]
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe($"Device name '{longDeviceName}' exceeds 25 character limit");
}
[Fact]
public void Validate_WithDeviceNameAtMaxLength_DoesNotThrow()
{
var maxLengthDeviceName = new string('a', 25);
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = [maxLengthDeviceName]
};
Should.NotThrow(() => config.Validate());
}
[Theory]
[InlineData("device@name")]
[InlineData("device name")]
[InlineData("device.name")]
[InlineData("device!name")]
public void Validate_WithDeviceNameInvalidCharacters_ThrowsValidationException(string deviceName)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = [deviceName]
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe($"Device name '{deviceName}' contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed.");
}
[Theory]
[InlineData("device-name")]
[InlineData("device_name")]
[InlineData("DeviceName123")]
[InlineData("DEVICE")]
[InlineData("a")]
public void Validate_WithDeviceNameValidCharacters_DoesNotThrow(string deviceName)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = [deviceName]
};
Should.NotThrow(() => config.Validate());
}
[Fact]
public void Validate_WithEmptyOrWhitespaceDeviceNames_SkipsThem()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Devices = ["", " ", "valid-device"]
};
Should.NotThrow(() => config.Validate());
}
#endregion
#region Validate Sound Tests
[Theory]
[InlineData(null)]
[InlineData("pushover")]
[InlineData("bike")]
[InlineData("custom-sound")]
public void Validate_WithValidOrNullSound_DoesNotThrow(string? sound)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Sound = sound
};
Should.NotThrow(() => config.Validate());
}
[Theory]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
public void Validate_WithWhitespaceOnlySound_ThrowsValidationException(string sound)
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Sound = sound
};
var exception = Should.Throw<ValidationException>(() => config.Validate());
exception.Message.ShouldBe("Sound name cannot be empty or whitespace when specified");
}
[Fact]
public void Validate_WithEmptyStringSound_DoesNotThrow()
{
var config = new PushoverConfig
{
ApiToken = "test-api-token-1234567890",
UserKey = "test-user-key-1234567890",
Priority = PushoverPriority.Normal,
Sound = string.Empty
};
// Empty string has Length 0, so Sound.Length > 0 is false - the condition is skipped
Should.NotThrow(() => config.Validate());
}
#endregion
}