java-topology/defects/btcpayserver-0001/test/btcpayserver-0001-test.cs
russell@unturf.com c2a1fc15cc undf: assign 928-930; btcpayserver CWE-407 scan (3 defects)
btcpayserver-0001: WalletTransactionInfo.Merge Attachments.Any O(A*B) MEDIUM
btcpayserver-0002: AppService gap-fill series.All O(D*S) LOW-MEDIUM
btcpayserver-0003: StringExtensions.IsValidFileName GetInvalidFileNameChars O(F*I) MEDIUM
2026-03-31 09:39:59 -04:00

100 lines
3.7 KiB
C#

// Unit test for btcpayserver-0001: WalletTransactionInfo.Merge O(A*B) attachment dedup
// Demonstrates quadratic vs linear behavior when merging wallet transaction attachments.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace BTCPayServer.Tests.CWE407
{
// Minimal Attachment stand-in
public class Attachment
{
public string Id { get; set; }
public string Type { get; set; }
}
public static class BtcPayServer0001Test
{
// --- ORIGINAL (defective): O(A * B) ---
static List<Attachment> MergeOriginal(List<Attachment> existing, List<Attachment> incoming)
{
var result = new List<Attachment>(existing);
foreach (var va in incoming.Where(va =>
!existing.Any(a => a.Id == va.Id && a.Type == va.Type)))
{
result.Add(va);
}
return result;
}
// --- PATCHED: O(A + B) ---
static List<Attachment> MergePatched(List<Attachment> existing, List<Attachment> incoming)
{
var result = new List<Attachment>(existing);
var existingSet = new HashSet<(string, string)>(
existing.Select(a => (a.Id, a.Type)));
foreach (var va in incoming.Where(va =>
!existingSet.Contains((va.Id, va.Type))))
{
result.Add(va);
}
return result;
}
static List<Attachment> MakeAttachments(int count, string prefix)
{
var list = new List<Attachment>(count);
for (int i = 0; i < count; i++)
list.Add(new Attachment { Id = $"{prefix}-{i}", Type = "invoice" });
return list;
}
public static void Main(string[] args)
{
int N = 2000;
var existing = MakeAttachments(N, "existing");
var incoming = MakeAttachments(N, "incoming"); // all new, worst case
// Warmup
MergeOriginal(existing, incoming);
MergePatched(existing, incoming);
var sw = Stopwatch.StartNew();
for (int i = 0; i < 5; i++)
MergeOriginal(existing, incoming);
sw.Stop();
long originalMs = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 0; i < 5; i++)
MergePatched(existing, incoming);
sw.Stop();
long patchedMs = sw.ElapsedMilliseconds;
double ratio = (double)originalMs / Math.Max(1, patchedMs);
Console.WriteLine($"N = {N}");
Console.WriteLine($"Original (List.Any): {originalMs} ms");
Console.WriteLine($"Patched (HashSet): {patchedMs} ms");
Console.WriteLine($"Speedup ratio: {ratio:F1}x");
// Correctness: both produce same count
var origResult = MergeOriginal(existing, incoming);
var patchResult = MergePatched(existing, incoming);
bool correctness = origResult.Count == patchResult.Count &&
origResult.Count == N + N;
Console.WriteLine($"Correctness: {(correctness ? "PASS" : "FAIL")}");
// Dedup correctness: overlapping
var overlap = MakeAttachments(N, "existing"); // same IDs as existing
var origOverlap = MergeOriginal(existing, overlap);
var patchOverlap = MergePatched(existing, overlap);
bool dedupCorrect = origOverlap.Count == N && patchOverlap.Count == N;
Console.WriteLine($"Dedup correctness: {(dedupCorrect ? "PASS" : "FAIL")}");
if (!correctness || !dedupCorrect)
Environment.Exit(1);
}
}
}