init
This commit is contained in:
124
SmokeTests/Program.cs
Normal file
124
SmokeTests/Program.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System.Text.Json;
|
||||
using Jellyfin.Plugin.RedditComments.Services;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
var failures = 0;
|
||||
|
||||
void Check(bool condition, string name)
|
||||
{
|
||||
Console.WriteLine((condition ? "PASS" : "FAIL") + " " + name);
|
||||
if (!condition)
|
||||
{
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- CommentCache
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), "rc-test-" + Guid.NewGuid().ToString("N"), "cache.db");
|
||||
var cache = new CommentCache(dbPath, NullLogger<CommentCache>.Instance);
|
||||
|
||||
Check(cache.Get("item1", TimeSpan.FromDays(30), TimeSpan.FromHours(24)) is null, "cache: miss on empty db");
|
||||
|
||||
cache.Set(new CachedThread
|
||||
{
|
||||
ItemId = "item1",
|
||||
ThreadId = "abc123",
|
||||
ThreadTitle = "Some Show - Episode 5 discussion",
|
||||
Subreddit = "anime",
|
||||
Permalink = "/r/anime/comments/abc123/x/",
|
||||
ThreadScore = 500,
|
||||
NumComments = 321,
|
||||
CommentsJson = "[{\"Author\":\"u\",\"Body\":\"b\",\"Score\":1,\"CreatedUtc\":1,\"Replies\":[]}]"
|
||||
});
|
||||
|
||||
var hit = cache.Get("item1", TimeSpan.FromDays(30), TimeSpan.FromHours(24));
|
||||
Check(hit is not null && hit.ThreadId == "abc123" && hit.NumComments == 321, "cache: hit after set");
|
||||
Check(hit!.FetchedAt > DateTime.UtcNow.AddMinutes(-1), "cache: fetched_at stamped");
|
||||
|
||||
var stale = cache.Get("item1", TimeSpan.Zero, TimeSpan.FromHours(24));
|
||||
Check(stale is null, "cache: stale entry treated as miss");
|
||||
|
||||
// negative caching
|
||||
cache.Set(new CachedThread { ItemId = "item2" });
|
||||
var notFound = cache.Get("item2", TimeSpan.FromDays(30), TimeSpan.FromHours(24));
|
||||
Check(notFound is not null && notFound.ThreadId == string.Empty, "cache: negative result cached");
|
||||
Check(cache.Get("item2", TimeSpan.FromDays(30), TimeSpan.Zero) is null, "cache: negative result expires on its own ttl");
|
||||
|
||||
cache.Invalidate("item1");
|
||||
Check(cache.Get("item1", TimeSpan.FromDays(30), TimeSpan.FromHours(24)) is null, "cache: invalidate removes entry");
|
||||
|
||||
// reopen against the same file to prove persistence
|
||||
var cache2 = new CommentCache(dbPath, NullLogger<CommentCache>.Instance);
|
||||
Check(cache2.Get("item2", TimeSpan.FromDays(30), TimeSpan.FromHours(24)) is not null, "cache: persists across instances");
|
||||
|
||||
// ---------------------------------------------------------------- Parsing: search
|
||||
const string searchJson = """
|
||||
{"kind":"Listing","data":{"children":[
|
||||
{"kind":"t3","data":{"id":"abc123","title":"Some Show - Episode 5 discussion","subreddit":"anime","permalink":"/r/anime/comments/abc123/x/","score":500,"num_comments":321,"link_flair_text":"Episode Discussion"}},
|
||||
{"kind":"t3","data":{"id":"def456","title":"Unrelated post","subreddit":"anime","permalink":"/r/anime/comments/def456/y/","score":10,"num_comments":2,"link_flair_text":null}}
|
||||
]}}
|
||||
""";
|
||||
using (var doc = JsonDocument.Parse(searchJson))
|
||||
{
|
||||
var candidates = RedditCommentsService.ParseSearchCandidates(doc);
|
||||
Check(candidates.Count == 2, "search: parses all candidates");
|
||||
Check(candidates[0].Id == "abc123" && candidates[0].NumComments == 321, "search: fields mapped");
|
||||
Check(candidates[1].LinkFlairText == string.Empty, "search: null flair becomes empty");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Parsing: comments
|
||||
const string commentsJson = """
|
||||
[
|
||||
{"kind":"Listing","data":{"children":[{"kind":"t3","data":{"id":"abc123","title":"Thread"}}]}},
|
||||
{"kind":"Listing","data":{"children":[
|
||||
{"kind":"t1","data":{"author":"user1","body":"Great episode","score":42,"created_utc":1752000000,"replies":{"kind":"Listing","data":{"children":[
|
||||
{"kind":"t1","data":{"author":"user2","body":"Agreed","score":10,"created_utc":1752000100,"replies":""}},
|
||||
{"kind":"more","data":{"children":["x","y"]}}
|
||||
]}}}},
|
||||
{"kind":"t1","data":{"author":"user3","body":"[deleted]","score":50,"created_utc":1752000200,"replies":""}},
|
||||
{"kind":"t1","data":{"author":"user4","body":"downvoted take","score":-3,"created_utc":1752000300,"replies":""}},
|
||||
{"kind":"more","data":{"children":["z"]}}
|
||||
]}}
|
||||
]
|
||||
""";
|
||||
using (var doc = JsonDocument.Parse(commentsJson))
|
||||
{
|
||||
var comments = RedditCommentsService.ParseCommentTree(doc, minScore: 1);
|
||||
Check(comments.Count == 1, "comments: deleted/downvoted/more filtered out");
|
||||
Check(comments[0].Author == "user1" && comments[0].Score == 42, "comments: fields mapped");
|
||||
Check(comments[0].Replies.Count == 1 && comments[0].Replies[0].Author == "user2", "comments: nested replies parsed, 'more' skipped");
|
||||
|
||||
var everything = RedditCommentsService.ParseCommentTree(doc, minScore: -100);
|
||||
Check(everything.Count == 2, "comments: low minScore keeps downvoted but not deleted");
|
||||
}
|
||||
|
||||
// round-trip through the cache serialization format
|
||||
using (var doc = JsonDocument.Parse(commentsJson))
|
||||
{
|
||||
var comments = RedditCommentsService.ParseCommentTree(doc, minScore: 1);
|
||||
var serialized = JsonSerializer.Serialize(comments);
|
||||
var deserialized = JsonSerializer.Deserialize<List<Jellyfin.Plugin.RedditComments.Models.CommentDto>>(serialized);
|
||||
Check(deserialized is not null && deserialized.Count == 1 && deserialized[0].Replies.Count == 1, "comments: JSON round-trip");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- RateLimiter
|
||||
var limiter = new RateLimiter(maxPerMinuteOverride: 2);
|
||||
await limiter.WaitAsync();
|
||||
await limiter.WaitAsync();
|
||||
|
||||
var third = limiter.WaitAsync();
|
||||
var completed = await Task.WhenAny(third, Task.Delay(TimeSpan.FromMilliseconds(500))) == third;
|
||||
Check(!completed, "ratelimit: 3rd request within the window is blocked");
|
||||
|
||||
var limiter2 = new RateLimiter(maxPerMinuteOverride: 60);
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
await limiter2.WaitAsync();
|
||||
}
|
||||
sw.Stop();
|
||||
Check(sw.ElapsedMilliseconds < 1000, "ratelimit: 60 requests pass instantly under the limit");
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(failures == 0 ? "ALL TESTS PASSED" : failures + " TEST(S) FAILED");
|
||||
return failures == 0 ? 0 : 1;
|
||||
Reference in New Issue
Block a user