Skip to content

Commit 9115cda

Browse files
thephezclaude
andcommitted
test: complete tutorial coverage with env var overrides
Add env var overrides to 4 tutorials so they can be tested via the subprocess harness without hardcoded values: - document-update/delete: DOCUMENT_ID - name-register: NAME_LABEL - identity-update-disable-key: DISABLE_KEY_ID (renamed from KEY_ID) Add 5 new write tests and extract state from existing tests to chain the full pipeline (28/28 tutorials now covered). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 905392b commit 9115cda

7 files changed

Lines changed: 156 additions & 17 deletions

File tree

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ WITHDRAWAL_ADDRESS=''
1818
# Used for document-update and document-delete tutorials
1919
DOCUMENT_ID=''
2020

21+
# DISABLE_KEY_ID is the public key ID to permanently disable (from identity key list)
22+
# ⚠️ Disabling a key is irreversible
23+
DISABLE_KEY_ID=''
24+
2125
# NAME_LABEL is an optional username label for name registration (without .dash)
2226
NAME_LABEL=''
2327

1-Identities-and-Names/identity-update-disable-key.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ import { setupDashClient } from '../setupDashClient.mjs';
44
const { sdk, keyManager } = await setupDashClient();
55
const { identity, signer } = await keyManager.getMaster();
66

7-
const KEY_ID = 99; // Replace with one of the identity's existing public key IDs
7+
// Replace with one of the identity's existing public key IDs
8+
const DISABLE_KEY_ID = Number(process.env.DISABLE_KEY_ID ?? 99);
89

9-
console.log(`Disabling key ${KEY_ID} on identity ${keyManager.identityId}...`);
10+
console.log(
11+
`Disabling key ${DISABLE_KEY_ID} on identity ${keyManager.identityId}...`,
12+
);
1013

1114
try {
1215
await sdk.identities.update({
1316
identity,
14-
disablePublicKeys: [KEY_ID], // Disable public key id KEY_ID
17+
disablePublicKeys: [DISABLE_KEY_ID],
1518
signer,
1619
});
1720

1-Identities-and-Names/name-register.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { sdk, keyManager } = await setupDashClient();
55
const { identity, identityKey, signer } = await keyManager.getAuth();
66

77
// ⚠️ Change this to a unique name to register
8-
const NAME_LABEL = 'alice';
8+
const NAME_LABEL = process.env.NAME_LABEL ?? 'alice';
99

1010
try {
1111
// Register a DPNS name for the identity

2-Contracts-and-Documents/document-delete.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const DATA_CONTRACT_ID =
1010
'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4';
1111

1212
// Replace with your existing document ID
13-
const DOCUMENT_ID = 'YOUR_DOCUMENT_ID';
13+
const DOCUMENT_ID = process.env.DOCUMENT_ID ?? 'YOUR_DOCUMENT_ID';
1414

1515
try {
1616
// Delete the document from the platform

2-Contracts-and-Documents/document-update.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const DATA_CONTRACT_ID =
1010
'FW3DHrQiG24VqzPY4ARenMgjEPpBNuEQTZckV8hbVCG4';
1111

1212
// Replace with your existing document ID from the Submit Documents tutorial
13-
const DOCUMENT_ID = 'YOUR_DOCUMENT_ID';
13+
const DOCUMENT_ID = process.env.DOCUMENT_ID ?? 'YOUR_DOCUMENT_ID';
1414

1515
try {
1616
// Fetch the existing document to get current revision

test/assertions.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,11 @@ export function extractId(stdout) {
6868
extractFromOutput(stdout, /id:\s*'([^']+)'/)
6969
);
7070
}
71+
72+
/**
73+
* Extract the key ID from `identity-update-add-key` output.
74+
* Matches "Adding key {N} to identity ...".
75+
*/
76+
export function extractKeyId(stdout) {
77+
return extractFromOutput(stdout, /Adding key (\d+)/);
78+
}

test/read-write.test.mjs

Lines changed: 135 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { describe, it } from 'node:test';
22
import assert from 'node:assert/strict';
33
import { runTutorial } from './run-tutorial.mjs';
4-
import { assertTutorialSuccess, extractId } from './assertions.mjs';
4+
import {
5+
assertTutorialSuccess,
6+
extractId,
7+
extractKeyId,
8+
} from './assertions.mjs';
59

610
// Accumulated state passed forward as env vars to dependent tutorials.
711
const state = {};
@@ -24,7 +28,7 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
2428
/Identity registered!/,
2529
`Expected identity registration output.\nSTDOUT: ${result.stdout}\nSTDERR: ${result.stderr}`,
2630
);
27-
console.log(result.stdout)
31+
console.log(result.stdout);
2832
});
2933

3034
it('identity-retrieve', { timeout: 120_000 }, async () => {
@@ -85,10 +89,48 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
8589
expectedPatterns: ['Identity updated:'],
8690
errorPatterns: ['Something went wrong'],
8791
});
92+
93+
const keyId = extractKeyId(result.stdout);
94+
assert.ok(keyId, `Failed to extract key ID from stdout:\n${result.stdout}`);
95+
state.addedKeyId = keyId;
96+
});
97+
98+
it('identity-update-disable-key', { timeout: 120_000 }, async (ctx) => {
99+
if (!state.addedKeyId) {
100+
ctx.skip('No KEY_ID (identity-update-add-key must pass first)');
101+
return;
102+
}
103+
const result = await runTutorial(
104+
'1-Identities-and-Names/identity-update-disable-key.mjs',
105+
{
106+
env: { DISABLE_KEY_ID: state.addedKeyId },
107+
timeoutMs: 120_000,
108+
},
109+
);
110+
assertTutorialSuccess(result, {
111+
name: 'identity-update-disable-key',
112+
expectedPatterns: ['Identity updated:'],
113+
errorPatterns: ['Something went wrong'],
114+
});
88115
});
89116

90-
// Skipped: identity-update-disable-key.mjs — KEY_ID = 99 hardcoded
91-
// Skipped: name-register.mjs — NAME_LABEL = 'alice' hardcoded
117+
it('name-register', { timeout: 120_000 }, async () => {
118+
const label = `test-${Date.now()}-${Math.random()
119+
.toString(36)
120+
.slice(2, 6)}`;
121+
const result = await runTutorial(
122+
'1-Identities-and-Names/name-register.mjs',
123+
{
124+
env: { NAME_LABEL: label },
125+
timeoutMs: 120_000,
126+
},
127+
);
128+
assertTutorialSuccess(result, {
129+
name: 'name-register',
130+
expectedPatterns: ['Name registered:'],
131+
errorPatterns: ['Something went wrong', 'already registered'],
132+
});
133+
});
92134

93135
// -----------------------------------------------------------------------
94136
// Phase 2: Contracts
@@ -105,7 +147,10 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
105147
errorPatterns: ['Something went wrong'],
106148
});
107149
const id = extractId(result.stdout);
108-
assert.ok(id, `Failed to extract contract ID from stdout:\n${result.stdout}`);
150+
assert.ok(
151+
id,
152+
`Failed to extract contract ID from stdout:\n${result.stdout}`,
153+
);
109154
state.dataContractId = id;
110155
});
111156

@@ -155,6 +200,13 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
155200
expectedPatterns: ['Contract registered:'],
156201
errorPatterns: ['Something went wrong'],
157202
});
203+
204+
const id = extractId(result.stdout);
205+
assert.ok(
206+
id,
207+
`Failed to extract history contract ID from stdout:\n${result.stdout}`,
208+
);
209+
state.historyContractId = id;
158210
});
159211

160212
it('contract-register-nft', { timeout: 180_000 }, async () => {
@@ -171,7 +223,9 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
171223

172224
it('contract-update-minimal', { timeout: 120_000 }, async (ctx) => {
173225
if (!state.dataContractId) {
174-
ctx.skip('No DATA_CONTRACT_ID (contract-register-minimal must pass first)');
226+
ctx.skip(
227+
'No DATA_CONTRACT_ID (contract-register-minimal must pass first)',
228+
);
175229
return;
176230
}
177231
const result = await runTutorial(
@@ -188,13 +242,36 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
188242
});
189243
});
190244

245+
it('contract-update-history', { timeout: 120_000 }, async (ctx) => {
246+
if (!state.historyContractId) {
247+
ctx.skip(
248+
'No DATA_CONTRACT_ID (contract-register-history must pass first)',
249+
);
250+
return;
251+
}
252+
const result = await runTutorial(
253+
'2-Contracts-and-Documents/contract-update-history.mjs',
254+
{
255+
env: { DATA_CONTRACT_ID: state.historyContractId },
256+
timeoutMs: 120_000,
257+
},
258+
);
259+
assertTutorialSuccess(result, {
260+
name: 'contract-update-history',
261+
expectedPatterns: ['Contract updated:'],
262+
errorPatterns: ['Something went wrong'],
263+
});
264+
});
265+
191266
// -----------------------------------------------------------------------
192267
// Phase 3: Documents (depend on contract from Phase 2)
193268
// -----------------------------------------------------------------------
194269

195270
it('document-submit', { timeout: 120_000 }, async (ctx) => {
196271
if (!state.dataContractId) {
197-
ctx.skip('No DATA_CONTRACT_ID (contract-register-minimal must pass first)');
272+
ctx.skip(
273+
'No DATA_CONTRACT_ID (contract-register-minimal must pass first)',
274+
);
198275
return;
199276
}
200277
const result = await runTutorial(
@@ -211,11 +288,58 @@ describe('Write tutorials (sequential)', { concurrency: 1 }, () => {
211288
});
212289

213290
const docId = extractId(result.stdout);
214-
assert.ok(docId, `Failed to extract document ID from stdout:\n${result.stdout}`);
291+
assert.ok(
292+
docId,
293+
`Failed to extract document ID from stdout:\n${result.stdout}`,
294+
);
215295
state.documentId = docId;
216296
});
217297

218-
// TODO: document-update.mjs and document-delete.mjs need a
219-
// `process.env.DOCUMENT_ID ??` prefix before they can be tested here.
220-
// Once added, use state.documentId (extracted above) via env var.
298+
it('document-update', { timeout: 120_000 }, async (ctx) => {
299+
if (!state.dataContractId || !state.documentId) {
300+
ctx.skip(
301+
'No DATA_CONTRACT_ID or DOCUMENT_ID (earlier tests must pass first)',
302+
);
303+
return;
304+
}
305+
const result = await runTutorial(
306+
'2-Contracts-and-Documents/document-update.mjs',
307+
{
308+
env: {
309+
DATA_CONTRACT_ID: state.dataContractId,
310+
DOCUMENT_ID: state.documentId,
311+
},
312+
timeoutMs: 120_000,
313+
},
314+
);
315+
assertTutorialSuccess(result, {
316+
name: 'document-update',
317+
expectedPatterns: ['Document updated:'],
318+
errorPatterns: ['Something went wrong'],
319+
});
320+
});
321+
322+
it('document-delete', { timeout: 120_000 }, async (ctx) => {
323+
if (!state.dataContractId || !state.documentId) {
324+
ctx.skip(
325+
'No DATA_CONTRACT_ID or DOCUMENT_ID (earlier tests must pass first)',
326+
);
327+
return;
328+
}
329+
const result = await runTutorial(
330+
'2-Contracts-and-Documents/document-delete.mjs',
331+
{
332+
env: {
333+
DATA_CONTRACT_ID: state.dataContractId,
334+
DOCUMENT_ID: state.documentId,
335+
},
336+
timeoutMs: 120_000,
337+
},
338+
);
339+
assertTutorialSuccess(result, {
340+
name: 'document-delete',
341+
expectedPatterns: ['Document deleted successfully'],
342+
errorPatterns: ['Something went wrong'],
343+
});
344+
});
221345
});

0 commit comments

Comments
 (0)