< Summary

Information
Class: NostrSure.Domain.Entities.ContactListEvent
Assembly: NostrSure.Domain
File(s): /home/runner/work/NostrSure/NostrSure/NostrSure.Domain/Entities/ContactListEvent.cs
Line coverage
65%
Covered lines: 54
Uncovered lines: 28
Coverable lines: 82
Total lines: 156
Line coverage: 65.8%
Branch coverage
46%
Covered branches: 13
Total branches: 28
Branch coverage: 46.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Contacts()100%11100%
.ctor(...)100%11100%
FromNostrEvent(...)100%22100%
Create(...)100%22100%
ExtractContactsFromTags(...)100%22100%
IsValidContactList()87.5%8893.33%
TagKey()100%11100%
Validate(...)0%4260%
TagsMatch(...)0%7280%

File(s)

/home/runner/work/NostrSure/NostrSure/NostrSure.Domain/Entities/ContactListEvent.cs

#LineLine coverage
 1namespace NostrSure.Domain.Entities;
 2
 3using NostrSure.Domain.ValueObjects;
 4
 5/// <summary>
 6/// Represents a NIP-02 Contact List Event (kind 3).
 7/// Immutable record following SOLID principles with proper encapsulation.
 8/// </summary>
 9public sealed record ContactListEvent(
 10    string Id,
 11    Pubkey Pubkey,
 12    DateTimeOffset CreatedAt,
 13    IReadOnlyList<NostrTag> Tags,
 14    string Content,
 15    string Sig,
 4116    IReadOnlyList<ContactEntry> Contacts
 1517) : NostrEvent(Id, Pubkey, CreatedAt, EventKind.ContactList, Tags, Content, Sig)
 18{
 19    /// <summary>
 20    /// Creates a ContactListEvent from a base NostrEvent and extracts contacts from p tags.
 21    /// </summary>
 22    public static ContactListEvent FromNostrEvent(NostrEvent nostrEvent)
 723    {
 724        if (nostrEvent.Kind != EventKind.ContactList)
 125            throw new ArgumentException("NostrEvent must be of kind ContactList", nameof(nostrEvent));
 26
 627        var contacts = ExtractContactsFromTags(nostrEvent.Tags);
 28
 629        return new ContactListEvent(
 630            nostrEvent.Id,
 631            nostrEvent.Pubkey,
 632            nostrEvent.CreatedAt,
 633            nostrEvent.Tags,
 634            nostrEvent.Content,
 635            nostrEvent.Sig,
 636            contacts
 637        );
 638    }
 39
 40    /// <summary>
 41    /// Creates a new ContactListEvent with the given contacts, automatically generating p tags.
 42    /// </summary>
 43    public static ContactListEvent Create(
 44        string id,
 45        Pubkey pubkey,
 46        DateTimeOffset createdAt,
 47        string content,
 48        string sig,
 49        IReadOnlyList<ContactEntry> contacts,
 50        IReadOnlyList<NostrTag>? additionalTags = null)
 751    {
 752        var allTags = new List<NostrTag>();
 53
 54        // Add non-p tags first
 755        if (additionalTags != null)
 456            allTags.AddRange(additionalTags.Where(t => t.Name != "p"));
 57
 58        // Add p tags from contacts
 2059        allTags.AddRange(contacts.Select(c => c.ToPTag()));
 60
 761        return new ContactListEvent(
 762            id,
 763            pubkey,
 764            createdAt,
 765            allTags,
 766            content,
 767            sig,
 768            contacts
 769        );
 770    }
 71
 72    /// <summary>
 73    /// Extracts contact entries from p tags in the tags collection.
 74    /// </summary>
 75    private static IReadOnlyList<ContactEntry> ExtractContactsFromTags(IReadOnlyList<NostrTag> tags)
 676    {
 677        return tags
 1578            .Where(tag => tag.Name == "p" && tag.Values.Count > 0)
 679            .Select(ContactEntry.FromPTag)
 680            .ToList();
 681    }
 82
 83    /// <summary>
 84    /// Validates the contact list event according to NIP-02 specifications.
 85    /// </summary>
 86    public bool IsValidContactList()
 487    {
 88        // All contacts must be valid
 1189        if (!Contacts.All(c => c.IsValid))
 190            return false;
 91
 92        // Check that p tags match contacts
 993        var pTags = Tags.Where(t => t.Name == "p").ToList();
 394        if (pTags.Count != Contacts.Count)
 195            return false;
 96
 97        // Use a HashSet for efficient lookup
 298        var pTagSet = new HashSet<string>(pTags.Select(TagKey));
 1499        foreach (var contact in Contacts)
 4100        {
 4101            var expectedTag = contact.ToPTag();
 4102            if (!pTagSet.Contains(TagKey(expectedTag)))
 0103                return false;
 4104        }
 105
 106        // Local function to generate a unique key for a tag
 107        static string TagKey(NostrTag tag)
 8108        {
 8109            return $"{tag.Name}:{string.Join(":", tag.Values)}";
 8110        }
 111
 2112        return true;
 4113    }
 114
 115    /// <summary>
 116    /// Override validation to include NIP-02 specific validation.
 117    /// </summary>
 118    public override bool Validate(Interfaces.INostrEventValidator validator, out List<string> errors)
 0119    {
 120        // First run base validation
 0121        var isValid = base.Validate(validator, out errors);
 122
 123        // Then add NIP-02 specific validation
 0124        if (!IsValidContactList())
 0125        {
 0126            errors.Add("Contact list validation failed: contacts and p tags do not match");
 0127            isValid = false;
 0128        }
 129
 130        // Validate individual contacts
 0131        for (int i = 0; i < Contacts.Count; i++)
 0132        {
 0133            if (!Contacts[i].IsValid)
 0134            {
 0135                errors.Add($"Contact {i} is invalid: {Contacts[i].ContactPubkey.Value}");
 0136                isValid = false;
 0137            }
 0138        }
 139
 0140        return isValid;
 0141    }
 142
 143    private static bool TagsMatch(NostrTag tag1, NostrTag tag2)
 0144    {
 0145        if (tag1.Name != tag2.Name || tag1.Values.Count != tag2.Values.Count)
 0146            return false;
 147
 0148        for (int i = 0; i < tag1.Values.Count; i++)
 0149        {
 0150            if (tag1.Values[i] != tag2.Values[i])
 0151                return false;
 0152        }
 153
 0154        return true;
 0155    }
 156}