forked from sim0n00ps/OF-DL
152 lines
5.4 KiB
C#
152 lines
5.4 KiB
C#
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using OF_DL.Models;
|
|
using OF_DL.Models.Config;
|
|
using OF_DL.Models.OfdlApi;
|
|
using OF_DL.Services;
|
|
|
|
namespace OF_DL.Tests.Services;
|
|
|
|
public class ApiServiceTests
|
|
{
|
|
[Fact]
|
|
public void GetDynamicHeaders_ReturnsSignedHeaders()
|
|
{
|
|
FakeAuthService authService = new()
|
|
{
|
|
CurrentAuth = new Auth
|
|
{
|
|
UserId = "123", UserAgent = "unit-test-agent", XBc = "xbc-token", Cookie = "auth_cookie=abc;"
|
|
}
|
|
};
|
|
ApiService service = CreateService(authService);
|
|
DynamicRules rules = new()
|
|
{
|
|
AppToken = "app-token",
|
|
StaticParam = "static",
|
|
Prefix = "prefix",
|
|
Suffix = "suffix",
|
|
ChecksumConstant = 7,
|
|
ChecksumIndexes = [0, 5, 10, 15]
|
|
};
|
|
|
|
using DynamicRulesCacheScope _ = new(rules);
|
|
|
|
Dictionary<string, string> headers = service.GetDynamicHeaders("/api2/v2/users", "?limit=1");
|
|
|
|
Assert.Equal("application/json, text/plain", headers["accept"]);
|
|
Assert.Equal("app-token", headers["app-token"]);
|
|
Assert.Equal("auth_cookie=abc;", headers["cookie"]);
|
|
Assert.Equal("unit-test-agent", headers["user-agent"]);
|
|
Assert.Equal("xbc-token", headers["x-bc"]);
|
|
Assert.Equal("123", headers["user-id"]);
|
|
Assert.True(long.TryParse(headers["time"], out long timestamp));
|
|
|
|
string expectedSign = BuildSign(rules, timestamp, "/api2/v2/users?limit=1", "123");
|
|
Assert.Equal(expectedSign, headers["sign"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetDynamicHeaders_ThrowsWhenRulesInvalid()
|
|
{
|
|
FakeAuthService authService = new()
|
|
{
|
|
CurrentAuth = new Auth
|
|
{
|
|
UserId = "123", UserAgent = "unit-test-agent", XBc = "xbc-token", Cookie = "auth_cookie=abc;"
|
|
}
|
|
};
|
|
ApiService service = CreateService(authService);
|
|
DynamicRules rules = new()
|
|
{
|
|
AppToken = null,
|
|
StaticParam = "static",
|
|
Prefix = null,
|
|
Suffix = "suffix",
|
|
ChecksumConstant = null,
|
|
ChecksumIndexes = []
|
|
};
|
|
|
|
using DynamicRulesCacheScope _ = new(rules);
|
|
|
|
Exception ex = Assert.Throws<Exception>(() => service.GetDynamicHeaders("/api2/v2/users", "?limit=1"));
|
|
Assert.Contains("Invalid dynamic rules", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetDynamicHeaders_ThrowsWhenAuthMissingFields()
|
|
{
|
|
FakeAuthService authService = new()
|
|
{
|
|
CurrentAuth = new Auth
|
|
{
|
|
UserId = "123", UserAgent = "unit-test-agent", XBc = "xbc-token", Cookie = null
|
|
}
|
|
};
|
|
ApiService service = CreateService(authService);
|
|
DynamicRules rules = new()
|
|
{
|
|
AppToken = "app-token",
|
|
StaticParam = "static",
|
|
Prefix = "prefix",
|
|
Suffix = "suffix",
|
|
ChecksumConstant = 1,
|
|
ChecksumIndexes = [0]
|
|
};
|
|
|
|
using DynamicRulesCacheScope _ = new(rules);
|
|
|
|
Exception ex = Assert.Throws<Exception>(() => service.GetDynamicHeaders("/api2/v2/users", "?limit=1"));
|
|
Assert.Contains("Auth service is missing required fields", ex.Message);
|
|
}
|
|
|
|
private static ApiService CreateService(FakeAuthService authService) =>
|
|
new(authService, new FakeConfigService(new Config()), new FakeDbService());
|
|
|
|
private static string BuildSign(DynamicRules rules, long timestamp, string pathWithQuery, string userId)
|
|
{
|
|
string input = $"{rules.StaticParam}\n{timestamp}\n{pathWithQuery}\n{userId}";
|
|
byte[] hashBytes = SHA1.HashData(Encoding.UTF8.GetBytes(input));
|
|
string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
|
|
|
Assert.NotNull(rules.ChecksumConstant);
|
|
|
|
int checksum = rules.ChecksumIndexes.Aggregate(0, (current, index) => current + hashString[index]) +
|
|
rules.ChecksumConstant.Value;
|
|
string checksumHex = checksum.ToString("X").ToLowerInvariant();
|
|
|
|
return $"{rules.Prefix}:{hashString}:{checksumHex}:{rules.Suffix}";
|
|
}
|
|
|
|
private sealed class DynamicRulesCacheScope : IDisposable
|
|
{
|
|
private static readonly FieldInfo s_rulesField =
|
|
typeof(ApiService).GetField("s_cachedDynamicRules", BindingFlags.NonPublic | BindingFlags.Static) ??
|
|
throw new InvalidOperationException("Unable to access cached rules field.");
|
|
|
|
private static readonly FieldInfo s_expirationField =
|
|
typeof(ApiService).GetField("s_cachedDynamicRulesExpiration",
|
|
BindingFlags.NonPublic | BindingFlags.Static) ??
|
|
throw new InvalidOperationException("Unable to access cached rules expiration field.");
|
|
|
|
private readonly object? _priorRules;
|
|
private readonly DateTime? _priorExpiration;
|
|
|
|
public DynamicRulesCacheScope(DynamicRules rules)
|
|
{
|
|
_priorRules = s_rulesField.GetValue(null);
|
|
_priorExpiration = (DateTime?)s_expirationField.GetValue(null);
|
|
|
|
s_rulesField.SetValue(null, rules);
|
|
s_expirationField.SetValue(null, DateTime.UtcNow.AddHours(1));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
s_rulesField.SetValue(null, _priorRules);
|
|
s_expirationField.SetValue(null, _priorExpiration);
|
|
}
|
|
}
|
|
}
|