identity.spec.ts raw

   1  import { Identity, UnsignedEvent, SignedEvent, SigningFunction } from './identity';
   2  import { IdentityCreated, IdentityRenamed, IdentitySigned } from '../events';
   3  
   4  describe('Identity Entity', () => {
   5    const TEST_PRIVATE_KEY = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
   6  
   7    describe('create', () => {
   8      it('should create identity with generated keypair when no private key provided', () => {
   9        const identity = Identity.create('Alice');
  10  
  11        expect(identity.nickname).toEqual('Alice');
  12        expect(identity.publicKey).toBeTruthy();
  13        expect(identity.publicKey.length).toBe(64);
  14      });
  15  
  16      it('should create identity with provided private key', () => {
  17        const identity = Identity.create('Bob', TEST_PRIVATE_KEY);
  18  
  19        expect(identity.nickname).toEqual('Bob');
  20        expect(identity.publicKey).toBeTruthy();
  21      });
  22  
  23      it('should raise IdentityCreated event', () => {
  24        const identity = Identity.create('Charlie');
  25        const events = identity.pullDomainEvents();
  26  
  27        expect(events.length).toBe(1);
  28        expect(events[0]).toBeInstanceOf(IdentityCreated);
  29  
  30        const createdEvent = events[0] as IdentityCreated;
  31        expect(createdEvent.identityId).toEqual(identity.id.toString());
  32        expect(createdEvent.publicKey).toEqual(identity.publicKey);
  33        expect(createdEvent.nickname).toEqual('Charlie');
  34      });
  35  
  36      it('should set createdAt timestamp', () => {
  37        const before = new Date();
  38        const identity = Identity.create('Dana');
  39        const after = new Date();
  40  
  41        expect(identity.createdAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
  42        expect(identity.createdAt.getTime()).toBeLessThanOrEqual(after.getTime());
  43      });
  44    });
  45  
  46    describe('fromSnapshot', () => {
  47      it('should reconstruct identity from snapshot', () => {
  48        const original = Identity.create('Eve', TEST_PRIVATE_KEY);
  49        original.pullDomainEvents(); // Clear creation event
  50  
  51        const snapshot = original.toSnapshot();
  52        const restored = Identity.fromSnapshot(snapshot);
  53  
  54        expect(restored.id.toString()).toEqual(original.id.toString());
  55        expect(restored.nickname).toEqual('Eve');
  56        expect(restored.publicKey).toEqual(original.publicKey);
  57      });
  58  
  59      it('should not raise events when loading from snapshot', () => {
  60        const original = Identity.create('Frank');
  61        const snapshot = original.toSnapshot();
  62  
  63        const restored = Identity.fromSnapshot(snapshot);
  64        const events = restored.pullDomainEvents();
  65  
  66        expect(events.length).toBe(0);
  67      });
  68    });
  69  
  70    describe('rename', () => {
  71      it('should update nickname', () => {
  72        const identity = Identity.create('OldName');
  73        identity.pullDomainEvents(); // Clear creation event
  74  
  75        identity.rename('NewName');
  76  
  77        expect(identity.nickname).toEqual('NewName');
  78      });
  79  
  80      it('should raise IdentityRenamed event', () => {
  81        const identity = Identity.create('OldName');
  82        identity.pullDomainEvents(); // Clear creation event
  83  
  84        identity.rename('NewName');
  85        const events = identity.pullDomainEvents();
  86  
  87        expect(events.length).toBe(1);
  88        expect(events[0]).toBeInstanceOf(IdentityRenamed);
  89  
  90        const renamedEvent = events[0] as IdentityRenamed;
  91        expect(renamedEvent.identityId).toEqual(identity.id.toString());
  92        expect(renamedEvent.oldNickname).toEqual('OldName');
  93        expect(renamedEvent.newNickname).toEqual('NewName');
  94      });
  95    });
  96  
  97    describe('sign', () => {
  98      it('should call signing function with event and return signed event', () => {
  99        const identity = Identity.create('Signer', TEST_PRIVATE_KEY);
 100        identity.pullDomainEvents();
 101  
 102        const unsignedEvent: UnsignedEvent = {
 103          kind: 1,
 104          created_at: Math.floor(Date.now() / 1000),
 105          tags: [],
 106          content: 'Hello, Nostr!',
 107        };
 108  
 109        const mockSignFn: SigningFunction = (event, privateKeyBytes) => {
 110          expect(privateKeyBytes).toBeInstanceOf(Uint8Array);
 111          expect(privateKeyBytes.length).toBe(32);
 112  
 113          return {
 114            ...event,
 115            id: 'mock-event-id',
 116            pubkey: identity.publicKey,
 117            sig: 'mock-signature',
 118          } as SignedEvent;
 119        };
 120  
 121        const signedEvent = identity.sign(unsignedEvent, mockSignFn);
 122  
 123        expect(signedEvent.id).toEqual('mock-event-id');
 124        expect(signedEvent.pubkey).toEqual(identity.publicKey);
 125        expect(signedEvent.sig).toEqual('mock-signature');
 126      });
 127  
 128      it('should raise IdentitySigned event', () => {
 129        const identity = Identity.create('Signer', TEST_PRIVATE_KEY);
 130        identity.pullDomainEvents();
 131  
 132        const unsignedEvent: UnsignedEvent = {
 133          kind: 1,
 134          created_at: Math.floor(Date.now() / 1000),
 135          tags: [],
 136          content: 'Test',
 137        };
 138  
 139        const mockSignFn: SigningFunction = (event) => ({
 140          ...event,
 141          id: 'signed-event-id',
 142          pubkey: identity.publicKey,
 143          sig: 'sig',
 144        } as SignedEvent);
 145  
 146        identity.sign(unsignedEvent, mockSignFn);
 147        const events = identity.pullDomainEvents();
 148  
 149        expect(events.length).toBe(1);
 150        expect(events[0]).toBeInstanceOf(IdentitySigned);
 151  
 152        const signedEvt = events[0] as IdentitySigned;
 153        expect(signedEvt.identityId).toEqual(identity.id.toString());
 154        expect(signedEvt.eventKind).toBe(1);
 155        expect(signedEvt.signedEventId).toEqual('signed-event-id');
 156      });
 157    });
 158  
 159    describe('toSnapshot', () => {
 160      it('should create complete snapshot for storage', () => {
 161        const identity = Identity.create('Snapshot Test', TEST_PRIVATE_KEY);
 162        const snapshot = identity.toSnapshot();
 163  
 164        expect(snapshot.id).toEqual(identity.id.toString());
 165        expect(snapshot.nick).toEqual('Snapshot Test');
 166        expect(snapshot.privkey).toBeTruthy();
 167        expect(snapshot.createdAt).toBeTruthy();
 168      });
 169    });
 170  
 171    describe('npub', () => {
 172      it('should return bech32 encoded public key', () => {
 173        const identity = Identity.create('NpubTest');
 174  
 175        expect(identity.npub).toMatch(/^npub1[a-z0-9]+$/);
 176      });
 177    });
 178  
 179    describe('pullDomainEvents', () => {
 180      it('should clear events after pulling', () => {
 181        const identity = Identity.create('Test');
 182  
 183        const firstPull = identity.pullDomainEvents();
 184        const secondPull = identity.pullDomainEvents();
 185  
 186        expect(firstPull.length).toBe(1);
 187        expect(secondPull.length).toBe(0);
 188      });
 189  
 190      it('should accumulate multiple events', () => {
 191        const identity = Identity.create('Multi');
 192        identity.rename('Name1');
 193        identity.rename('Name2');
 194  
 195        const events = identity.pullDomainEvents();
 196  
 197        expect(events.length).toBe(3); // Created + 2 renames
 198      });
 199    });
 200  });
 201