Skip to content

Commit cc61b9b

Browse files
fix(test): make integration tests opt-in
- Gate e2e/functional suites behind RUN_INTEGRATION_TESTS - Fix README code-fence rendering and doc links - Replace console usage in CLI help with stdout/stderr writes
1 parent a52fa07 commit cc61b9b

6 files changed

Lines changed: 73 additions & 55 deletions

File tree

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ Then configure with absolute path:
264264
### As MCP Server
265265

266266
Once configured, you have access to 10 powerful tools
267-
```
268267

269268
### Programmatic Usage
270269

@@ -359,14 +358,17 @@ npm run build
359358
### Run Tests
360359

361360
```bash
362-
# All tests
361+
# Default test run (integration tests are skipped unless explicitly enabled)
363362
npm test
364363

364+
# Integration tests (requires network access + Dynadot credentials)
365+
RUN_INTEGRATION_TESTS=true DYNADOT_API_KEY=your-api-key TEST_DOMAIN=your-domain.com npm test
366+
365367
# E2E tests (validates all 106 API endpoints)
366-
TEST_DOMAIN=your-domain.com npm test -- test/e2e.test.ts
368+
RUN_INTEGRATION_TESTS=true TEST_DOMAIN=your-domain.com npm test -- test/e2e.test.ts
367369

368370
# Functional tests (real CRUD operations)
369-
TEST_DOMAIN=your-domain.com npm test -- test/functional.test.ts
371+
RUN_INTEGRATION_TESTS=true TEST_DOMAIN=your-domain.com npm test -- test/functional.test.ts
370372

371373
# Watch mode
372374
npm run test:watch

docs/environment.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Get your API key from: https://www.dynadot.com/account/domain/setting/api.html?s
2525

2626
**Recommendation**: Set to `true` when testing read-only operations. Some write operations (like `create_contact`) are not supported in sandbox mode.
2727

28-
**⚠️ Sandbox Limitations**: The Dynadot sandbox does not support all API commands. Specifically, `create_contact` fails in sandbox mode. For complete details, see [SANDBOX_LIMITATIONS.md](./SANDBOX_LIMITATIONS.md).
28+
**⚠️ Sandbox Limitations**: The Dynadot sandbox does not support all API commands. Specifically, `create_contact` fails in sandbox mode. For complete details, see [sandbox.md](./sandbox.md).
2929

3030
### TEST_DOMAIN
3131

docs/testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ npm run test:all-tools:write
1616

1717
1. **Set Environment Variables**:
1818

19-
Create a `.env` file in the project root (see [ENVIRONMENT.md](./ENVIRONMENT.md) for details):
19+
Create a `.env` file in the project root (see [environment.md](./environment.md) for details):
2020

2121
```bash
2222
# Required
@@ -292,7 +292,7 @@ TEST_FOLDER_ID=-1
292292
EOF
293293
```
294294

295-
See [ENVIRONMENT.md](./ENVIRONMENT.md) for detailed documentation on all environment variables.
295+
See [environment.md](./environment.md) for detailed documentation on all environment variables.
296296

297297
### Build Errors
298298

src/index.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,36 @@ const __filename = fileURLToPath(import.meta.url);
1111
const __dirname = dirname(__filename);
1212
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
1313

14+
function writeStdoutLine(line: string): void {
15+
process.stdout.write(`${line}\n`);
16+
}
17+
18+
function writeStderrLine(line: string): void {
19+
process.stderr.write(`${line}\n`);
20+
}
21+
1422
function showHelp(): never {
15-
console.log(`domain-mcp v${packageJson.version}`);
16-
console.log('');
17-
console.log('A Domain MCP server for AI-powered Dynadot domain management.');
18-
console.log('');
19-
console.log('This is not a CLI tool - it runs as an MCP server via stdin/stdout.');
20-
console.log('');
21-
console.log('Setup instructions:');
22-
console.log(` ${GITHUB_URL}#quick-installation`);
23-
console.log('');
24-
console.log('Example configuration:');
25-
console.log(JSON.stringify(EXAMPLE_CONFIG, null, 2));
23+
writeStdoutLine(`domain-mcp v${packageJson.version}`);
24+
writeStdoutLine('');
25+
writeStdoutLine('A Domain MCP server for AI-powered Dynadot domain management.');
26+
writeStdoutLine('');
27+
writeStdoutLine('This is not a CLI tool - it runs as an MCP server via stdin/stdout.');
28+
writeStdoutLine('');
29+
writeStdoutLine('Setup instructions:');
30+
writeStdoutLine(` ${GITHUB_URL}#quick-installation`);
31+
writeStdoutLine('');
32+
writeStdoutLine('Example configuration:');
33+
writeStdoutLine(JSON.stringify(EXAMPLE_CONFIG, null, 2));
2634
process.exit(0);
2735
}
2836

2937
function showUsageAndExit(): never {
30-
console.error('domain-mcp is an MCP server, not a CLI tool.');
31-
console.error('');
32-
console.error('Run with --help for usage information.');
33-
console.error('');
34-
console.error('Quick start:');
35-
console.error(` ${GITHUB_URL}#quick-installation`);
38+
writeStderrLine('domain-mcp is an MCP server, not a CLI tool.');
39+
writeStderrLine('');
40+
writeStderrLine('Run with --help for usage information.');
41+
writeStderrLine('');
42+
writeStderrLine('Quick start:');
43+
writeStderrLine(` ${GITHUB_URL}#quick-installation`);
3644
process.exit(1);
3745
}
3846

@@ -42,7 +50,7 @@ if (args.includes('--help') || args.includes('-h')) {
4250
}
4351

4452
if (args.includes('--version') || args.includes('-v')) {
45-
console.log(packageJson.version);
53+
writeStdoutLine(packageJson.version);
4654
process.exit(0);
4755
}
4856

test/e2e.test.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { getClient } from '../src/client.js';
33

44
const TEST_DOMAIN = process.env.TEST_DOMAIN;
55
const client = getClient();
6+
const isIntegrationEnabled = process.env.RUN_INTEGRATION_TESTS === 'true';
7+
const describeIntegration = describe.runIf(isIntegrationEnabled);
68

79
// Helper to extract status from nested response
810
// Dynadot API has multiple response formats:
@@ -38,19 +40,21 @@ function hasValidResponse(result: Record<string, unknown>): boolean {
3840
return false;
3941
}
4042

41-
beforeAll(() => {
42-
if (!TEST_DOMAIN) {
43-
throw new Error('TEST_DOMAIN environment variable is required (e.g., example.com)');
44-
}
45-
if (!process.env.DYNADOT_API_KEY) {
46-
throw new Error('DYNADOT_API_KEY environment variable is required');
47-
}
43+
describeIntegration('Integration test prerequisites', () => {
44+
beforeAll(() => {
45+
if (!TEST_DOMAIN) {
46+
throw new Error('TEST_DOMAIN environment variable is required (e.g., example.com)');
47+
}
48+
if (!process.env.DYNADOT_API_KEY) {
49+
throw new Error('DYNADOT_API_KEY environment variable is required');
50+
}
51+
});
4852
});
4953

5054
// =============================================================================
5155
// TOOL 1: dynadot_domain (11 actions)
5256
// =============================================================================
53-
describe('dynadot_domain', () => {
57+
describeIntegration('dynadot_domain', () => {
5458
describe('read operations', () => {
5559
it('list - should list all domains', async () => {
5660
const result = await client.execute('list_domain');
@@ -137,7 +141,7 @@ describe('dynadot_domain', () => {
137141
// =============================================================================
138142
// TOOL 2: dynadot_domain_settings (13 actions)
139143
// =============================================================================
140-
describe('dynadot_domain_settings', () => {
144+
describeIntegration('dynadot_domain_settings', () => {
141145
describe('read operations', () => {
142146
it('get_ns - should get nameservers', async () => {
143147
const result = await client.execute('get_ns', { domain: TEST_DOMAIN });
@@ -245,7 +249,7 @@ describe('dynadot_domain_settings', () => {
245249
// =============================================================================
246250
// TOOL 3: dynadot_dns (5 actions)
247251
// =============================================================================
248-
describe('dynadot_dns', () => {
252+
describeIntegration('dynadot_dns', () => {
249253
describe('read operations', () => {
250254
it('get - should get DNS records', async () => {
251255
const result = await client.execute('get_dns', { domain: TEST_DOMAIN });
@@ -289,7 +293,7 @@ describe('dynadot_dns', () => {
289293
// =============================================================================
290294
// TOOL 4: dynadot_nameserver (6 actions)
291295
// =============================================================================
292-
describe('dynadot_nameserver', () => {
296+
describeIntegration('dynadot_nameserver', () => {
293297
describe('read operations', () => {
294298
it('list - should list registered nameservers', async () => {
295299
const result = await client.execute('server_list');
@@ -334,7 +338,7 @@ describe('dynadot_nameserver', () => {
334338
// =============================================================================
335339
// TOOL 5: dynadot_transfer (8 actions)
336340
// =============================================================================
337-
describe('dynadot_transfer', () => {
341+
describeIntegration('dynadot_transfer', () => {
338342
describe('read operations', () => {
339343
it('status - should get transfer status', async () => {
340344
const result = await client.execute('get_transfer_status', { domain: TEST_DOMAIN });
@@ -392,7 +396,7 @@ describe('dynadot_transfer', () => {
392396
// =============================================================================
393397
// TOOL 6: dynadot_contact (11 actions)
394398
// =============================================================================
395-
describe('dynadot_contact', () => {
399+
describeIntegration('dynadot_contact', () => {
396400
describe('read operations', () => {
397401
it('list - should list all contacts', async () => {
398402
const result = await client.execute('contact_list');
@@ -475,7 +479,7 @@ describe('dynadot_contact', () => {
475479
// =============================================================================
476480
// TOOL 7: dynadot_folder (15 actions)
477481
// =============================================================================
478-
describe('dynadot_folder', () => {
482+
describeIntegration('dynadot_folder', () => {
479483
describe('read operations', () => {
480484
it('list - should list all folders', async () => {
481485
const result = await client.execute('folder_list');
@@ -592,7 +596,7 @@ describe('dynadot_folder', () => {
592596
// =============================================================================
593597
// TOOL 8: dynadot_account (13 actions)
594598
// =============================================================================
595-
describe('dynadot_account', () => {
599+
describeIntegration('dynadot_account', () => {
596600
describe('read operations', () => {
597601
it('info - should get account info', async () => {
598602
const result = await client.execute('account_info');
@@ -682,7 +686,7 @@ describe('dynadot_account', () => {
682686
// =============================================================================
683687
// TOOL 9: dynadot_aftermarket (20 actions)
684688
// =============================================================================
685-
describe('dynadot_aftermarket', () => {
689+
describeIntegration('dynadot_aftermarket', () => {
686690
describe('backorders', () => {
687691
it('backorder_list - should list backorder requests', async () => {
688692
const result = await client.execute('backorder_request_list');
@@ -838,7 +842,7 @@ describe('dynadot_aftermarket', () => {
838842
// =============================================================================
839843
// TOOL 10: dynadot_order (5 actions)
840844
// =============================================================================
841-
describe('dynadot_order', () => {
845+
describeIntegration('dynadot_order', () => {
842846
describe('read operations', () => {
843847
it('list - should list recent orders', async () => {
844848
const today = new Date();

test/functional.test.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,25 @@ import { getClient } from '../src/client.js';
33

44
const TEST_DOMAIN = process.env.TEST_DOMAIN;
55
const client = getClient();
6-
7-
beforeAll(() => {
8-
if (!TEST_DOMAIN) {
9-
throw new Error('TEST_DOMAIN environment variable is required');
10-
}
11-
if (!process.env.DYNADOT_API_KEY) {
12-
throw new Error('DYNADOT_API_KEY environment variable is required');
13-
}
6+
const isIntegrationEnabled = process.env.RUN_INTEGRATION_TESTS === 'true';
7+
const describeIntegration = describe.runIf(isIntegrationEnabled);
8+
9+
describeIntegration('Integration test prerequisites', () => {
10+
beforeAll(() => {
11+
if (!TEST_DOMAIN) {
12+
throw new Error('TEST_DOMAIN environment variable is required');
13+
}
14+
if (!process.env.DYNADOT_API_KEY) {
15+
throw new Error('DYNADOT_API_KEY environment variable is required');
16+
}
17+
});
1418
});
1519

1620
// =============================================================================
1721
// FUNCTIONAL TESTS - Actually verify operations work end-to-end
1822
// =============================================================================
1923

20-
describe('Functional: Folder CRUD', () => {
24+
describeIntegration('Functional: Folder CRUD', () => {
2125
const testFolderName = `test-folder-${Date.now()}`;
2226
let createdFolderId: string | null = null;
2327

@@ -86,7 +90,7 @@ describe('Functional: Folder CRUD', () => {
8690
});
8791
});
8892

89-
describe('Functional: Contact CRUD', () => {
93+
describeIntegration('Functional: Contact CRUD', () => {
9094
const testContactName = `Test Contact ${Date.now()}`;
9195
let createdContactId: string | null = null;
9296

@@ -198,7 +202,7 @@ describe('Functional: Contact CRUD', () => {
198202
});
199203
});
200204

201-
describe('Functional: Domain Note', () => {
205+
describeIntegration('Functional: Domain Note', () => {
202206
const testNote = `Functional test note ${Date.now()}`;
203207

204208
it('1. should set a note on the domain', async () => {
@@ -245,7 +249,7 @@ describe('Functional: Domain Note', () => {
245249
// Note: Dynadot API has a confusing error message for lock_domain unlock:
246250
// When trying to unlock, it returns "this domain has been locked already" (wrong message)
247251
// This appears to be domain protection or an API bug - unlock may not work via API
248-
describe('Functional: Domain Lock/Unlock', () => {
252+
describeIntegration('Functional: Domain Lock/Unlock', () => {
249253
let initialLockState: string | null = null;
250254

251255
it('1. should get current lock state', async () => {
@@ -302,7 +306,7 @@ describe('Functional: Domain Lock/Unlock', () => {
302306
});
303307
});
304308

305-
describe('Functional: Renewal Option', () => {
309+
describeIntegration('Functional: Renewal Option', () => {
306310
let initialRenewOption: string | null = null;
307311

308312
it('1. should get current renewal option', async () => {

0 commit comments

Comments
 (0)