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