< Summary

Information
Class: FirebaseAdapter.FirebaseContextRepository
Assembly: FirebaseAdapter
File(s): /home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseContextRepository.cs
Line coverage
76%
Covered lines: 103
Uncovered lines: 31
Coverable lines: 134
Total lines: 257
Line coverage: 76.8%
Branch coverage
100%
Covered branches: 28
Total branches: 28
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%44100%
SaveContextDocumentAsync()100%6685.19%
GetLatestContextDocumentAsync()100%2275%
GetContextDocumentAsync()100%2266.67%
GetContextDocumentByTimestampAsync()100%9433.33%
GetContextDocumentNamesAsync()100%4478.57%
GetContextDocumentVersionsAsync()100%4475%
UpdateContextDocumentVersionAsync()100%2278.95%
ConvertToContextDocument(...)100%11100%

File(s)

/home/runner/work/KicktippAi/KicktippAi/src/FirebaseAdapter/FirebaseContextRepository.cs

#LineLine coverage
 1using EHonda.KicktippAi.Core;
 2using FirebaseAdapter.Models;
 3using Google.Cloud.Firestore;
 4using Microsoft.Extensions.Logging;
 5using NodaTime;
 6
 7namespace FirebaseAdapter;
 8
 9/// <summary>
 10/// Firebase Firestore implementation of the context repository.
 11/// </summary>
 12public class FirebaseContextRepository : IContextRepository
 13{
 14    private readonly FirestoreDb _firestoreDb;
 15    private readonly ILogger<FirebaseContextRepository> _logger;
 16    private readonly string _contextDocumentsCollection;
 17    private readonly string _competition;
 18
 119    public FirebaseContextRepository(FirestoreDb firestoreDb, ILogger<FirebaseContextRepository> logger)
 20    {
 121        _firestoreDb = firestoreDb ?? throw new ArgumentNullException(nameof(firestoreDb));
 122        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 23
 124        _contextDocumentsCollection = "context-documents";
 125        _competition = "bundesliga-2025-26";
 26
 127        _logger.LogInformation("Firebase context repository initialized");
 128    }
 29
 30    public async Task<int?> SaveContextDocumentAsync(string documentName, string content, string communityContext, Cance
 31    {
 32        try
 33        {
 34            // Get the latest version to check if content differs
 135            var latestDocument = await GetLatestContextDocumentAsync(documentName, communityContext, cancellationToken);
 36
 37            // If content is the same, don't save a new version
 138            if (latestDocument != null && latestDocument.Content == content)
 39            {
 140                _logger.LogInformation("Context document {DocumentName} content unchanged, skipping save", documentName)
 141                return null;
 42            }
 43
 44            // Determine the next version number
 145            var nextVersion = latestDocument?.Version + 1 ?? 0;
 46
 147            var now = Timestamp.GetCurrentTimestamp();
 148            var documentId = $"{documentName}_{communityContext}_{nextVersion}";
 49
 150            var firestoreDocument = new FirestoreContextDocument
 151            {
 152                Id = documentId,
 153                DocumentName = documentName,
 154                Content = content,
 155                Version = nextVersion,
 156                CreatedAt = now,
 157                Competition = _competition,
 158                CommunityContext = communityContext
 159            };
 60
 161            var docRef = _firestoreDb.Collection(_contextDocumentsCollection).Document(documentId);
 162            await docRef.SetAsync(firestoreDocument, cancellationToken: cancellationToken);
 63
 164            _logger.LogInformation("Saved context document {DocumentName} version {Version} for community {CommunityCont
 165                documentName, nextVersion, communityContext);
 66
 167            return nextVersion;
 68        }
 069        catch (Exception ex)
 70        {
 071            _logger.LogError(ex, "Failed to save context document {DocumentName} for community {CommunityContext}",
 072                documentName, communityContext);
 073            throw;
 74        }
 175    }
 76
 77    public async Task<ContextDocument?> GetLatestContextDocumentAsync(string documentName, string communityContext, Canc
 78    {
 79        try
 80        {
 181            var query = _firestoreDb.Collection(_contextDocumentsCollection)
 182                .WhereEqualTo("documentName", documentName)
 183                .WhereEqualTo("communityContext", communityContext)
 184                .WhereEqualTo("competition", _competition)
 185                .OrderByDescending("version")
 186                .Limit(1);
 87
 188            var snapshot = await query.GetSnapshotAsync(cancellationToken);
 89
 190            if (!snapshot.Documents.Any())
 91            {
 192                return null;
 93            }
 94
 195            var firestoreDoc = snapshot.Documents.First().ConvertTo<FirestoreContextDocument>();
 196            return ConvertToContextDocument(firestoreDoc);
 97        }
 098        catch (Exception ex)
 99        {
 0100            _logger.LogError(ex, "Failed to retrieve latest context document {DocumentName} for community {CommunityCont
 0101                documentName, communityContext);
 0102            throw;
 103        }
 1104    }
 105
 106    public async Task<ContextDocument?> GetContextDocumentAsync(string documentName, int version, string communityContex
 107    {
 108        try
 109        {
 1110            var documentId = $"{documentName}_{communityContext}_{version}";
 1111            var docRef = _firestoreDb.Collection(_contextDocumentsCollection).Document(documentId);
 1112            var snapshot = await docRef.GetSnapshotAsync(cancellationToken);
 113
 1114            if (!snapshot.Exists)
 115            {
 1116                return null;
 117            }
 118
 1119            var firestoreDoc = snapshot.ConvertTo<FirestoreContextDocument>();
 1120            return ConvertToContextDocument(firestoreDoc);
 121        }
 0122        catch (Exception ex)
 123        {
 0124            _logger.LogError(ex, "Failed to retrieve context document {DocumentName} version {Version} for community {Co
 0125                documentName, version, communityContext);
 0126            throw;
 127        }
 1128    }
 129
 130    public async Task<ContextDocument?> GetContextDocumentByTimestampAsync(
 131        string documentName,
 132        DateTimeOffset createdAtOrEarlier,
 133        string communityContext,
 134        CancellationToken cancellationToken = default)
 135    {
 136        try
 137        {
 1138            var versions = await GetContextDocumentVersionsAsync(documentName, communityContext, cancellationToken);
 139
 1140            return versions
 1141                .Where(document => document.CreatedAt <= createdAtOrEarlier)
 1142                .OrderByDescending(document => document.CreatedAt)
 1143                .ThenByDescending(document => document.Version)
 1144                .FirstOrDefault();
 145        }
 0146        catch (Exception ex)
 147        {
 0148            _logger.LogError(
 0149                ex,
 0150                "Failed to retrieve context document {DocumentName} at timestamp {CreatedAt} for community {CommunityCon
 0151                documentName,
 0152                createdAtOrEarlier,
 0153                communityContext);
 0154            throw;
 155        }
 1156    }
 157
 158    public async Task<IReadOnlyList<string>> GetContextDocumentNamesAsync(string communityContext, CancellationToken can
 159    {
 160        try
 161        {
 1162            var query = _firestoreDb.Collection(_contextDocumentsCollection)
 1163                .WhereEqualTo("communityContext", communityContext)
 1164                .WhereEqualTo("competition", _competition)
 1165                .Select("documentName");
 166
 1167            var snapshot = await query.GetSnapshotAsync(cancellationToken);
 168
 1169            var documentNames = snapshot.Documents
 1170                .Select(doc => doc.GetValue<string>("documentName"))
 1171                .Distinct()
 1172                .OrderBy(name => name, StringComparer.Ordinal)
 1173                .ToList()
 1174                .AsReadOnly();
 175
 1176            return documentNames;
 177        }
 0178        catch (Exception ex)
 179        {
 0180            _logger.LogError(ex, "Failed to retrieve context document names for community {CommunityContext}", community
 0181            throw;
 182        }
 1183    }
 184
 185    public async Task<IReadOnlyList<ContextDocument>> GetContextDocumentVersionsAsync(string documentName, string commun
 186    {
 187        try
 188        {
 1189            var query = _firestoreDb.Collection(_contextDocumentsCollection)
 1190                .WhereEqualTo("documentName", documentName)
 1191                .WhereEqualTo("communityContext", communityContext)
 1192                .WhereEqualTo("competition", _competition)
 1193                .OrderBy("version");
 194
 1195            var snapshot = await query.GetSnapshotAsync(cancellationToken);
 196
 1197            var documents = snapshot.Documents
 1198                .Select(doc => doc.ConvertTo<FirestoreContextDocument>())
 1199                .Select(ConvertToContextDocument)
 1200                .ToList()
 1201                .AsReadOnly();
 202
 1203            return documents;
 204        }
 0205        catch (Exception ex)
 206        {
 0207            _logger.LogError(ex, "Failed to retrieve context document versions for {DocumentName} in community {Communit
 0208                documentName, communityContext);
 0209            throw;
 210        }
 1211    }
 212
 213    public async Task<bool> UpdateContextDocumentVersionAsync(string documentName, int version, string newContent, strin
 214    {
 215        try
 216        {
 1217            var documentId = $"{documentName}_{communityContext}_{version}";
 1218            var docRef = _firestoreDb.Collection(_contextDocumentsCollection).Document(documentId);
 219
 220            // Check if document exists
 1221            var snapshot = await docRef.GetSnapshotAsync(cancellationToken);
 1222            if (!snapshot.Exists)
 223            {
 1224                _logger.LogWarning("Cannot update non-existent document {DocumentId}", documentId);
 1225                return false;
 226            }
 227
 228            // Update only the content field
 1229            var updates = new Dictionary<string, object>
 1230            {
 1231                ["content"] = newContent
 1232            };
 233
 1234            await docRef.UpdateAsync(updates, cancellationToken: cancellationToken);
 235
 1236            _logger.LogInformation("Updated content for context document {DocumentName} version {Version} in community {
 1237                documentName, version, communityContext);
 238
 1239            return true;
 240        }
 0241        catch (Exception ex)
 242        {
 0243            _logger.LogError(ex, "Failed to update context document {DocumentName} version {Version} in community {Commu
 0244                documentName, version, communityContext);
 0245            throw;
 246        }
 1247    }
 248
 249    private static ContextDocument ConvertToContextDocument(FirestoreContextDocument firestoreDoc)
 250    {
 1251        return new ContextDocument(
 1252            firestoreDoc.DocumentName,
 1253            firestoreDoc.Content,
 1254            firestoreDoc.Version,
 1255            firestoreDoc.CreatedAt.ToDateTimeOffset());
 256    }
 257}