< Summary

Information
Class: Orchestrator.Commands.Utility.Snapshots.SnapshotsFetchCommand
Assembly: Orchestrator
File(s): /home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Utility/Snapshots/SnapshotsFetchCommand.cs
Line coverage
100%
Covered lines: 163
Uncovered lines: 0
Coverable lines: 163
Total lines: 218
Line coverage: 100%
Branch coverage
100%
Covered branches: 24
Total branches: 24
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ExecuteAsync()100%22100%
FetchSnapshotsAsync()100%11100%
<FetchSnapshotsAsync()100%2020100%
SaveSnapshotAsync()100%11100%
WarnIfNotGitignored(...)100%22100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/Orchestrator/Commands/Utility/Snapshots/SnapshotsFetchCommand.cs

#LineLine coverage
 1using Microsoft.Extensions.Logging;
 2using Spectre.Console;
 3using Spectre.Console.Cli;
 4using Orchestrator.Infrastructure.Factories;
 5
 6namespace Orchestrator.Commands.Utility.Snapshots;
 7
 8/// <summary>
 9/// Command for fetching HTML snapshots from Kicktipp.
 10/// </summary>
 11public class SnapshotsFetchCommand : AsyncCommand<SnapshotsFetchSettings>
 12{
 13    private readonly IAnsiConsole _console;
 14    private readonly IKicktippClientFactory _kicktippClientFactory;
 15    private readonly ILogger<SnapshotsFetchCommand> _logger;
 16
 117    public SnapshotsFetchCommand(
 118        IAnsiConsole console,
 119        IKicktippClientFactory kicktippClientFactory,
 120        ILogger<SnapshotsFetchCommand> logger)
 21    {
 122        _console = console;
 123        _kicktippClientFactory = kicktippClientFactory;
 124        _logger = logger;
 125    }
 26
 27    public override async Task<int> ExecuteAsync(CommandContext context, SnapshotsFetchSettings settings)
 28    {
 29
 30        try
 31        {
 32            // Validate settings
 133            if (string.IsNullOrWhiteSpace(settings.Community))
 34            {
 135                _console.MarkupLine("[red]Error: Community is required[/]");
 136                return 1;
 37            }
 38
 139            _console.MarkupLine("[green]Fetching snapshots...[/]");
 140            _console.MarkupLine($"[blue]Community:[/] [yellow]{settings.Community}[/]");
 141            _console.MarkupLine($"[blue]Output directory:[/] [yellow]{settings.OutputDirectory}[/]");
 42
 43            // Create output directory
 144            var outputPath = Path.GetFullPath(settings.OutputDirectory);
 145            Directory.CreateDirectory(outputPath);
 46
 47            // Warn if not gitignored
 148            WarnIfNotGitignored(_console, outputPath);
 49
 50            // Create snapshot client using factory (factory handles env var loading)
 151            var snapshotClient = _kicktippClientFactory.CreateSnapshotClient();
 52
 153            var savedCount = await FetchSnapshotsAsync(_console, snapshotClient, settings.Community, outputPath);
 54
 155            _console.WriteLine();
 156            _console.MarkupLine($"[green]Done![/] Saved {savedCount} snapshot(s) to [yellow]{outputPath}[/]");
 157            _console.WriteLine();
 158            _console.MarkupLine("[dim]Next step: Run 'snapshots encrypt' to encrypt them for committing[/]");
 59
 160            return 0;
 61        }
 162        catch (Exception ex)
 63        {
 164            _logger.LogError(ex, "Error fetching snapshots");
 165            _console.MarkupLine($"[red]Error:[/] {ex.Message}");
 166            return 1;
 67        }
 168    }
 69
 70    internal static async Task<int> FetchSnapshotsAsync(IAnsiConsole console, ISnapshotClient snapshotClient, string com
 71    {
 172        var savedCount = 0;
 73
 174        await console.Status()
 175            .StartAsync("Fetching snapshots...", async ctx =>
 176            {
 177                // 0. Login page (fetched without community context)
 178                ctx.Status("Fetching login page...");
 179                var loginContent = await snapshotClient.FetchLoginPageAsync();
 180                if (loginContent != null)
 181                {
 182                    await SaveSnapshotAsync(outputPath, "login.html", loginContent);
 183                    savedCount++;
 184                    console.MarkupLine("[green]✓[/] Saved login.html");
 185                }
 186                else
 187                {
 188                    console.MarkupLine("[red]✗[/] Failed to fetch login page");
 189                }
 190
 191                // 1. Tabellen (standings)
 192                ctx.Status("Fetching tabellen...");
 193                var tabellenContent = await snapshotClient.FetchStandingsPageAsync(community);
 194                if (tabellenContent != null)
 195                {
 196                    await SaveSnapshotAsync(outputPath, "tabellen.html", tabellenContent);
 197                    savedCount++;
 198                    console.MarkupLine("[green]✓[/] Saved tabellen.html");
 199                }
 1100                else
 1101                {
 1102                    console.MarkupLine("[red]✗[/] Failed to fetch tabellen");
 1103                }
 1104
 1105                // 2. Tippabgabe (main betting page)
 1106                ctx.Status("Fetching tippabgabe...");
 1107                var tippabgabeContent = await snapshotClient.FetchTippabgabePageAsync(community);
 1108                if (tippabgabeContent != null)
 1109                {
 1110                    await SaveSnapshotAsync(outputPath, "tippabgabe.html", tippabgabeContent);
 1111                    savedCount++;
 1112                    console.MarkupLine("[green]✓[/] Saved tippabgabe.html");
 1113                }
 1114                else
 1115                {
 1116                    console.MarkupLine("[red]✗[/] Failed to fetch tippabgabe");
 1117                }
 1118
 1119                // 3. Tippabgabe bonus (bonus questions)
 1120                ctx.Status("Fetching tippabgabe-bonus...");
 1121                var bonusContent = await snapshotClient.FetchBonusPageAsync(community);
 1122                if (bonusContent != null)
 1123                {
 1124                    await SaveSnapshotAsync(outputPath, "tippabgabe-bonus.html", bonusContent);
 1125                    savedCount++;
 1126                    console.MarkupLine("[green]✓[/] Saved tippabgabe-bonus.html");
 1127                }
 1128                else
 1129                {
 1130                    console.MarkupLine("[red]✗[/] Failed to fetch tippabgabe-bonus");
 1131                }
 1132
 1133                // 4. Spielinfo pages (match details with history)
 1134                ctx.Status("Fetching spielinfo pages...");
 1135                var spielinfoPages = await snapshotClient.FetchAllSpielinfoAsync(community);
 1136
 1137                foreach (var (fileName, content) in spielinfoPages)
 1138                {
 1139                    await SaveSnapshotAsync(outputPath, $"{fileName}.html", content);
 1140                    savedCount++;
 1141                }
 1142
 1143                if (spielinfoPages.Count > 0)
 1144                {
 1145                    console.MarkupLine($"[green]✓[/] Saved {spielinfoPages.Count} spielinfo pages");
 1146                }
 1147                else
 1148                {
 1149                    console.MarkupLine("[yellow]![/] No spielinfo pages found");
 1150                }
 1151
 1152                // 5. Spielinfo pages with home/away history (ansicht=2)
 1153                ctx.Status("Fetching spielinfo home/away pages...");
 1154                var homeAwayPages = await snapshotClient.FetchAllSpielinfoHomeAwayAsync(community);
 1155
 1156                foreach (var (fileName, content) in homeAwayPages)
 1157                {
 1158                    await SaveSnapshotAsync(outputPath, $"{fileName}.html", content);
 1159                    savedCount++;
 1160                }
 1161
 1162                if (homeAwayPages.Count > 0)
 1163                {
 1164                    console.MarkupLine($"[green]✓[/] Saved {homeAwayPages.Count} spielinfo home/away pages");
 1165                }
 1166                else
 1167                {
 1168                    console.MarkupLine("[yellow]![/] No spielinfo home/away pages found");
 1169                }
 1170
 1171                // 6. Spielinfo pages with head-to-head history (ansicht=3)
 1172                ctx.Status("Fetching spielinfo head-to-head pages...");
 1173                var h2hPages = await snapshotClient.FetchAllSpielinfoHeadToHeadAsync(community);
 1174
 1175                foreach (var (fileName, content) in h2hPages)
 1176                {
 1177                    await SaveSnapshotAsync(outputPath, $"{fileName}.html", content);
 1178                    savedCount++;
 1179                }
 1180
 1181                if (h2hPages.Count > 0)
 1182                {
 1183                    console.MarkupLine($"[green]✓[/] Saved {h2hPages.Count} spielinfo head-to-head pages");
 1184                }
 1185                else
 1186                {
 1187                    console.MarkupLine("[yellow]![/] No spielinfo head-to-head pages found");
 1188                }
 1189            });
 190
 1191        return savedCount;
 1192    }
 193
 194    internal static async Task SaveSnapshotAsync(string outputPath, string fileName, string content)
 195    {
 1196        var filePath = Path.Combine(outputPath, fileName);
 1197        await File.WriteAllTextAsync(filePath, content);
 1198    }
 199
 200    private static void WarnIfNotGitignored(IAnsiConsole console, string outputPath)
 201    {
 1202        var relativePath = Path.GetRelativePath(Directory.GetCurrentDirectory(), outputPath);
 1203        var directoryName = Path.GetFileName(outputPath.TrimEnd(Path.DirectorySeparatorChar));
 204
 1205        var commonIgnoredPatterns = new[] { "snapshots", "temp", "tmp", "output", "out", ".local" };
 1206        var looksIgnored = commonIgnoredPatterns.Any(p =>
 1207            directoryName.Contains(p, StringComparison.OrdinalIgnoreCase));
 208
 1209        if (!looksIgnored)
 210        {
 1211            console.MarkupLine(
 1212                $"[yellow]⚠ Warning:[/] Output directory '{relativePath}' may not be gitignored.");
 1213            console.MarkupLine(
 1214                "[yellow]  Make sure to add it to .gitignore before committing![/]");
 1215            console.WriteLine();
 216        }
 1217    }
 218}