mlgym-deploy 3.3.41 → 3.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +255 -2
- package/package.json +5 -1
- package/tests/README.md +7 -2
- package/tests/deploy_dollie_test.sh +199 -0
package/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import crypto from 'crypto';
|
|
|
18
18
|
const execAsync = promisify(exec);
|
|
19
19
|
|
|
20
20
|
// Current version of this MCP server - INCREMENT FOR WORKFLOW FIXES
|
|
21
|
-
const CURRENT_VERSION = '3.3.
|
|
21
|
+
const CURRENT_VERSION = '3.3.42'; // Add deploy_dollie tool for Next.js dev containers with SSH
|
|
22
22
|
const PACKAGE_NAME = 'mlgym-deploy';
|
|
23
23
|
|
|
24
24
|
// Debug logging configuration - ENABLED BY DEFAULT
|
|
@@ -3434,6 +3434,205 @@ async function deployProject(args) {
|
|
|
3434
3434
|
}
|
|
3435
3435
|
}
|
|
3436
3436
|
|
|
3437
|
+
// ============================================================================
|
|
3438
|
+
// DOLLIE DEPLOYMENT - Next.js Dev Containers with SSH
|
|
3439
|
+
// ============================================================================
|
|
3440
|
+
async function deployDollie(args) {
|
|
3441
|
+
const {
|
|
3442
|
+
project_name,
|
|
3443
|
+
project_description,
|
|
3444
|
+
git_repo_url,
|
|
3445
|
+
git_branch = '',
|
|
3446
|
+
git_token = '',
|
|
3447
|
+
app_port = 3000,
|
|
3448
|
+
email,
|
|
3449
|
+
password
|
|
3450
|
+
} = args;
|
|
3451
|
+
|
|
3452
|
+
log.info('MCP >>> Starting Dollie deployment workflow');
|
|
3453
|
+
log.debug('MCP >>> Arguments:', { project_name, git_repo_url, git_branch, app_port });
|
|
3454
|
+
|
|
3455
|
+
try {
|
|
3456
|
+
// Step 1: Authenticate
|
|
3457
|
+
log.info('MCP >>> STEP 1: Authenticating...');
|
|
3458
|
+
const auth = await loadAuth();
|
|
3459
|
+
|
|
3460
|
+
// If not authenticated and credentials provided, authenticate
|
|
3461
|
+
if (!auth.token && email && password) {
|
|
3462
|
+
const loginResult = await apiRequest('POST', '/api/v1/auth/login', {
|
|
3463
|
+
email,
|
|
3464
|
+
password
|
|
3465
|
+
}, false);
|
|
3466
|
+
|
|
3467
|
+
if (!loginResult.success || !loginResult.data.token) {
|
|
3468
|
+
return {
|
|
3469
|
+
content: [{
|
|
3470
|
+
type: 'text',
|
|
3471
|
+
text: JSON.stringify({
|
|
3472
|
+
status: 'error',
|
|
3473
|
+
message: 'Authentication failed',
|
|
3474
|
+
error: loginResult.error || 'Invalid credentials',
|
|
3475
|
+
next_steps: [
|
|
3476
|
+
'Provide valid email and password',
|
|
3477
|
+
'Or ensure you are already logged in'
|
|
3478
|
+
]
|
|
3479
|
+
}, null, 2)
|
|
3480
|
+
}]
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
await saveAuth(email, loginResult.data.token);
|
|
3485
|
+
log.success('MCP >>> STEP 1: Authentication successful');
|
|
3486
|
+
} else if (!auth.token) {
|
|
3487
|
+
return {
|
|
3488
|
+
content: [{
|
|
3489
|
+
type: 'text',
|
|
3490
|
+
text: JSON.stringify({
|
|
3491
|
+
status: 'error',
|
|
3492
|
+
message: 'Authentication required',
|
|
3493
|
+
error: 'No authentication token found and no credentials provided',
|
|
3494
|
+
next_steps: [
|
|
3495
|
+
'Provide email and password in the request',
|
|
3496
|
+
'Or use mlgym_auth_login first'
|
|
3497
|
+
]
|
|
3498
|
+
}, null, 2)
|
|
3499
|
+
}]
|
|
3500
|
+
};
|
|
3501
|
+
} else {
|
|
3502
|
+
log.success('MCP >>> STEP 1: Using existing authentication');
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
// Step 2: Get current user info
|
|
3506
|
+
log.info('MCP >>> STEP 2: Getting user information...');
|
|
3507
|
+
const userResult = await apiRequest('GET', '/api/v1/user', null, true);
|
|
3508
|
+
|
|
3509
|
+
if (!userResult.success) {
|
|
3510
|
+
return {
|
|
3511
|
+
content: [{
|
|
3512
|
+
type: 'text',
|
|
3513
|
+
text: JSON.stringify({
|
|
3514
|
+
status: 'error',
|
|
3515
|
+
message: 'Failed to get user information',
|
|
3516
|
+
error: userResult.error
|
|
3517
|
+
}, null, 2)
|
|
3518
|
+
}]
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
log.success('MCP >>> STEP 2: User information retrieved');
|
|
3523
|
+
|
|
3524
|
+
// Step 3: Generate SSH password
|
|
3525
|
+
log.info('MCP >>> STEP 3: Generating SSH password...');
|
|
3526
|
+
const sshPassword = generateRandomPassword();
|
|
3527
|
+
log.success('MCP >>> STEP 3: SSH password generated');
|
|
3528
|
+
|
|
3529
|
+
// Step 4: Prepare environment variables
|
|
3530
|
+
log.info('MCP >>> STEP 4: Preparing environment variables...');
|
|
3531
|
+
const environmentVariables = {
|
|
3532
|
+
GIT_REPO_URL: git_repo_url,
|
|
3533
|
+
GIT_BRANCH: git_branch,
|
|
3534
|
+
GIT_TOKEN: git_token,
|
|
3535
|
+
SSH_PASSWORD: sshPassword,
|
|
3536
|
+
S5_USERNAME: 'developer',
|
|
3537
|
+
APP_PORT: app_port.toString(),
|
|
3538
|
+
SSH_PORT: '22'
|
|
3539
|
+
};
|
|
3540
|
+
log.success('MCP >>> STEP 4: Environment variables prepared');
|
|
3541
|
+
log.debug('MCP >>> Environment variables:', Object.keys(environmentVariables));
|
|
3542
|
+
|
|
3543
|
+
// Step 5: Create Coolify resource with docker-image
|
|
3544
|
+
log.info('MCP >>> STEP 5: Creating Coolify deployment...');
|
|
3545
|
+
const createResult = await apiRequest('POST', '/api/v1/projects', {
|
|
3546
|
+
name: project_name,
|
|
3547
|
+
description: project_description,
|
|
3548
|
+
repository: '', // Not needed for docker-image deployments
|
|
3549
|
+
branch: '',
|
|
3550
|
+
build_pack: 'docker-image',
|
|
3551
|
+
docker_image: 'code.stratus5.com:5050/dollie/dollie/nextjs-dev:latest',
|
|
3552
|
+
environment_variables: environmentVariables,
|
|
3553
|
+
ports_exposes: app_port.toString()
|
|
3554
|
+
}, true);
|
|
3555
|
+
|
|
3556
|
+
if (!createResult.success) {
|
|
3557
|
+
return {
|
|
3558
|
+
content: [{
|
|
3559
|
+
type: 'text',
|
|
3560
|
+
text: JSON.stringify({
|
|
3561
|
+
status: 'error',
|
|
3562
|
+
message: 'Failed to create Dollie deployment',
|
|
3563
|
+
error: createResult.error,
|
|
3564
|
+
step: 'create_coolify_resource'
|
|
3565
|
+
}, null, 2)
|
|
3566
|
+
}]
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
log.success('MCP >>> STEP 5: Coolify deployment created');
|
|
3571
|
+
|
|
3572
|
+
// Step 6: Extract deployment details
|
|
3573
|
+
const deployment = createResult.data;
|
|
3574
|
+
const appUuid = deployment.coolify_uuid;
|
|
3575
|
+
const appUrl = deployment.url;
|
|
3576
|
+
|
|
3577
|
+
// Parse URL to extract hostname
|
|
3578
|
+
const urlObj = new URL(appUrl);
|
|
3579
|
+
const hostname = urlObj.hostname;
|
|
3580
|
+
|
|
3581
|
+
// Determine SSH port based on node (typically 2201 for all nodes)
|
|
3582
|
+
const sshPort = 2201;
|
|
3583
|
+
|
|
3584
|
+
log.success('MCP >>> Dollie deployment completed successfully');
|
|
3585
|
+
|
|
3586
|
+
return {
|
|
3587
|
+
content: [{
|
|
3588
|
+
type: 'text',
|
|
3589
|
+
text: JSON.stringify({
|
|
3590
|
+
success: true,
|
|
3591
|
+
deployment: {
|
|
3592
|
+
url: appUrl,
|
|
3593
|
+
ssh: {
|
|
3594
|
+
host: hostname,
|
|
3595
|
+
port: sshPort,
|
|
3596
|
+
username: 'developer',
|
|
3597
|
+
password: sshPassword
|
|
3598
|
+
},
|
|
3599
|
+
status: 'deploying',
|
|
3600
|
+
coolify_uuid: appUuid,
|
|
3601
|
+
project_name: project_name,
|
|
3602
|
+
git_repo: git_repo_url,
|
|
3603
|
+
git_branch: git_branch || 'default',
|
|
3604
|
+
app_port: app_port
|
|
3605
|
+
},
|
|
3606
|
+
next_steps: [
|
|
3607
|
+
`Container is deploying at ${appUrl}`,
|
|
3608
|
+
`SSH access: ssh developer@${hostname} -p ${sshPort}`,
|
|
3609
|
+
`Password: ${sshPassword}`,
|
|
3610
|
+
'The container will:',
|
|
3611
|
+
' 1. Clone your git repository',
|
|
3612
|
+
' 2. Install dependencies (npm/yarn/pnpm)',
|
|
3613
|
+
' 3. Start Next.js dev server',
|
|
3614
|
+
' 4. Enable SSH access',
|
|
3615
|
+
'Wait 2-3 minutes for deployment to complete'
|
|
3616
|
+
]
|
|
3617
|
+
}, null, 2)
|
|
3618
|
+
}]
|
|
3619
|
+
};
|
|
3620
|
+
|
|
3621
|
+
} catch (error) {
|
|
3622
|
+
log.error('MCP >>> Dollie deployment failed:', error.message);
|
|
3623
|
+
return {
|
|
3624
|
+
content: [{
|
|
3625
|
+
type: 'text',
|
|
3626
|
+
text: JSON.stringify({
|
|
3627
|
+
status: 'error',
|
|
3628
|
+
message: 'Dollie deployment failed',
|
|
3629
|
+
error: error.message
|
|
3630
|
+
}, null, 2)
|
|
3631
|
+
}]
|
|
3632
|
+
};
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3437
3636
|
// ============================================================================
|
|
3438
3637
|
// SIMPLIFIED STATUS CHECK
|
|
3439
3638
|
// ============================================================================
|
|
@@ -4767,6 +4966,55 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
4767
4966
|
}
|
|
4768
4967
|
}
|
|
4769
4968
|
},
|
|
4969
|
+
{
|
|
4970
|
+
name: 'deploy_dollie',
|
|
4971
|
+
description: 'Deploy a Next.js development container with SSH access for AI-assisted development. Uses the Dollie base image which clones your git repository and runs the dev server. Supports any public git repo or private repos with token authentication.',
|
|
4972
|
+
inputSchema: {
|
|
4973
|
+
type: 'object',
|
|
4974
|
+
properties: {
|
|
4975
|
+
project_name: {
|
|
4976
|
+
type: 'string',
|
|
4977
|
+
description: 'Project name (lowercase alphanumeric with hyphens, e.g., "my-nextjs-app")',
|
|
4978
|
+
pattern: '^[a-z0-9][a-z0-9-]*[a-z0-9]$',
|
|
4979
|
+
minLength: 3
|
|
4980
|
+
},
|
|
4981
|
+
project_description: {
|
|
4982
|
+
type: 'string',
|
|
4983
|
+
description: 'Brief project description (min 10 characters)',
|
|
4984
|
+
minLength: 10
|
|
4985
|
+
},
|
|
4986
|
+
git_repo_url: {
|
|
4987
|
+
type: 'string',
|
|
4988
|
+
description: 'Git repository URL (https://github.com/user/repo.git)',
|
|
4989
|
+
pattern: '^https://.*\\.git$'
|
|
4990
|
+
},
|
|
4991
|
+
git_branch: {
|
|
4992
|
+
type: 'string',
|
|
4993
|
+
description: 'Branch to clone (optional, defaults to repo\'s default branch)'
|
|
4994
|
+
},
|
|
4995
|
+
git_token: {
|
|
4996
|
+
type: 'string',
|
|
4997
|
+
description: 'OAuth token for private repos (optional)'
|
|
4998
|
+
},
|
|
4999
|
+
app_port: {
|
|
5000
|
+
type: 'number',
|
|
5001
|
+
description: 'Next.js port inside container (optional, default: 3000)',
|
|
5002
|
+
default: 3000
|
|
5003
|
+
},
|
|
5004
|
+
email: {
|
|
5005
|
+
type: 'string',
|
|
5006
|
+
description: 'Email for authentication (optional if already authenticated)',
|
|
5007
|
+
pattern: '^[^@]+@[^@]+\\.[^@]+$'
|
|
5008
|
+
},
|
|
5009
|
+
password: {
|
|
5010
|
+
type: 'string',
|
|
5011
|
+
description: 'Password (optional if already authenticated, min 8 characters)',
|
|
5012
|
+
minLength: 8
|
|
5013
|
+
}
|
|
5014
|
+
},
|
|
5015
|
+
required: ['project_name', 'project_description', 'git_repo_url']
|
|
5016
|
+
}
|
|
5017
|
+
},
|
|
4770
5018
|
{
|
|
4771
5019
|
name: 'mlgym_help',
|
|
4772
5020
|
description: 'Display all available MLGym tools with descriptions and usage examples. Use this to discover what operations you can perform.',
|
|
@@ -4902,13 +5150,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4902
5150
|
result = await rollback(args);
|
|
4903
5151
|
break;
|
|
4904
5152
|
|
|
5153
|
+
case 'deploy_dollie':
|
|
5154
|
+
log.info(`Deploying Dollie development container...`);
|
|
5155
|
+
result = await deployDollie(args);
|
|
5156
|
+
break;
|
|
5157
|
+
|
|
4905
5158
|
case 'mlgym_help':
|
|
4906
5159
|
log.info(`Showing help...`);
|
|
4907
5160
|
result = await showHelp(args);
|
|
4908
5161
|
break;
|
|
4909
5162
|
|
|
4910
5163
|
default:
|
|
4911
|
-
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status, mlgym_deploy_logs, mlgym_user_create, mlgym_auth_login, mlgym_set_env_vars, mlgym_set_health_check, mlgym_set_domain, mlgym_set_deployment_commands, mlgym_deploy_manual, mlgym_set_options, mlgym_rollback`);
|
|
5164
|
+
throw new Error(`Unknown tool: ${name}. Available tools: mlgym_deploy, mlgym_status, mlgym_deploy_logs, deploy_dollie, mlgym_user_create, mlgym_auth_login, mlgym_set_env_vars, mlgym_set_health_check, mlgym_set_domain, mlgym_set_deployment_commands, mlgym_deploy_manual, mlgym_set_options, mlgym_rollback`);
|
|
4912
5165
|
}
|
|
4913
5166
|
|
|
4914
5167
|
const duration = Date.now() - startTime;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mlgym-deploy",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.42",
|
|
4
4
|
"description": "MCP server for MLGym - Complete deployment management: deploy, configure, monitor, and rollback applications",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -80,6 +80,10 @@
|
|
|
80
80
|
"name": "mlgym_rollback",
|
|
81
81
|
"description": "Rollback to previous deployment"
|
|
82
82
|
},
|
|
83
|
+
{
|
|
84
|
+
"name": "deploy_dollie",
|
|
85
|
+
"description": "Deploy Next.js development container with SSH access using pre-built Dollie image"
|
|
86
|
+
},
|
|
83
87
|
{
|
|
84
88
|
"name": "mlgym_help",
|
|
85
89
|
"description": "Display all available tools with descriptions and usage examples"
|
package/tests/README.md
CHANGED
|
@@ -13,7 +13,8 @@ tests/
|
|
|
13
13
|
├── mlgym_auth_logout_test.sh # ✅ 8 tests (auth_logout)
|
|
14
14
|
├── mlgym_project_init_test.sh # ✅ 6 tests (project_init)
|
|
15
15
|
├── mlgym_projects_list_test.sh # ✅ 6 tests (projects_list)
|
|
16
|
-
|
|
16
|
+
├── mlgym_projects_get_test.sh # ✅ 7 tests (projects_get)
|
|
17
|
+
└── deploy_dollie_test.sh # ✅ 20 tests (deploy_dollie)
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
## Running Tests
|
|
@@ -32,7 +33,7 @@ cd mcp-server
|
|
|
32
33
|
|
|
33
34
|
## Test Coverage
|
|
34
35
|
|
|
35
|
-
### Completed (
|
|
36
|
+
### Completed (7 functions, 82 total tests)
|
|
36
37
|
- ✅ **mlgym_auth_login** (15 tests)
|
|
37
38
|
- Valid/invalid credentials, SQL injection, XSS, buffer overflow, token storage
|
|
38
39
|
- ✅ **mlgym_user_create** (20 tests)
|
|
@@ -45,6 +46,10 @@ cd mcp-server
|
|
|
45
46
|
- List projects, user isolation, response format
|
|
46
47
|
- ✅ **mlgym_projects_get** (7 tests)
|
|
47
48
|
- Get project details, validation, non-existent projects
|
|
49
|
+
- ✅ **deploy_dollie** (20 tests)
|
|
50
|
+
- Authentication, deployment workflow, public repos, custom ports/branches
|
|
51
|
+
- Required fields, validation, security (SQL injection, XSS, buffer overflow)
|
|
52
|
+
- Response format, SSH password strength
|
|
48
53
|
|
|
49
54
|
### Remaining (37 functions)
|
|
50
55
|
See TODO.md for priority order and testing plan.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Comprehensive test suite for deploy_dollie
|
|
3
|
+
# Tests functional requirements, edge cases, and security
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
PASSED=0
|
|
8
|
+
FAILED=0
|
|
9
|
+
|
|
10
|
+
echo "=== deploy_dollie Comprehensive Test Suite ==="
|
|
11
|
+
echo ""
|
|
12
|
+
|
|
13
|
+
# Helper to run test
|
|
14
|
+
run_test() {
|
|
15
|
+
local test_name="$1"
|
|
16
|
+
local project_name="$2"
|
|
17
|
+
local project_description="$3"
|
|
18
|
+
local git_repo_url="$4"
|
|
19
|
+
local git_branch="$5"
|
|
20
|
+
local app_port="$6"
|
|
21
|
+
local expected_result="$7" # "success" or "error"
|
|
22
|
+
|
|
23
|
+
# Build JSON request
|
|
24
|
+
local branch_field=""
|
|
25
|
+
if [ -n "$git_branch" ]; then
|
|
26
|
+
branch_field=",\"git_branch\":\"$git_branch\""
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
local port_field=""
|
|
30
|
+
if [ -n "$app_port" ]; then
|
|
31
|
+
port_field=",\"app_port\":$app_port"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
local req="{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"deploy_dollie\",\"arguments\":{\"project_name\":\"$project_name\",\"project_description\":\"$project_description\",\"git_repo_url\":\"$git_repo_url\"$branch_field$port_field}},\"id\":99}"
|
|
35
|
+
local resp=$(echo "$req" | node index.js 2>/dev/null | tail -1)
|
|
36
|
+
|
|
37
|
+
# Check for deployment details (success) or error message
|
|
38
|
+
local content=$(echo "$resp" | jq -r '.result.content[0].text' 2>/dev/null)
|
|
39
|
+
local success=$(echo "$content" | jq -r '.success' 2>/dev/null)
|
|
40
|
+
local error_msg=$(echo "$content" | jq -r '.message' 2>/dev/null)
|
|
41
|
+
|
|
42
|
+
if [ "$expected_result" = "success" ]; then
|
|
43
|
+
if [ "$success" = "true" ]; then
|
|
44
|
+
local ssh_host=$(echo "$content" | jq -r '.deployment.ssh.host' 2>/dev/null)
|
|
45
|
+
local ssh_password=$(echo "$content" | jq -r '.deployment.ssh.password' 2>/dev/null)
|
|
46
|
+
echo "✅ $test_name (host: $ssh_host, password: ${ssh_password:0:8}...)"
|
|
47
|
+
PASSED=$((PASSED + 1))
|
|
48
|
+
else
|
|
49
|
+
echo "❌ $test_name (expected deployment success, got: $error_msg)"
|
|
50
|
+
FAILED=$((FAILED + 1))
|
|
51
|
+
fi
|
|
52
|
+
else
|
|
53
|
+
# Expected error
|
|
54
|
+
if echo "$content" | grep -qE "error|missing|required|invalid|must|Authentication"; then
|
|
55
|
+
echo "✅ $test_name"
|
|
56
|
+
PASSED=$((PASSED + 1))
|
|
57
|
+
else
|
|
58
|
+
echo "❌ $test_name (expected error, got success or no error)"
|
|
59
|
+
FAILED=$((FAILED + 1))
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Test 1: Authentication check - should fail without auth
|
|
65
|
+
echo "Test 1: Deployment without authentication"
|
|
66
|
+
TEST_PROJECT="dollie-test-$(date +%s)"
|
|
67
|
+
run_test "Deployment without auth" "$TEST_PROJECT" "Test deployment without authentication" "https://github.com/vercel/next.js.git" "canary" "3000" "error"
|
|
68
|
+
|
|
69
|
+
# Test 2: Authenticate first
|
|
70
|
+
echo "Test 2: Authenticate with test account"
|
|
71
|
+
LOGIN_REQ='{"jsonrpc":"2.0","method":"tools/call","params":{"name":"mlgym_auth_login","arguments":{"email":"test-dollie@ezb.net","password":"TestPassword123!"}},"id":100}'
|
|
72
|
+
LOGIN_RESP=$(echo "$LOGIN_REQ" | node index.js 2>/dev/null | tail -1)
|
|
73
|
+
LOGIN_STATUS=$(echo "$LOGIN_RESP" | jq -r '.result.content[0].text' | jq -r '.status' 2>/dev/null)
|
|
74
|
+
|
|
75
|
+
if [ "$LOGIN_STATUS" = "authenticated" ]; then
|
|
76
|
+
echo "✅ Test 2: Authentication successful"
|
|
77
|
+
PASSED=$((PASSED + 1))
|
|
78
|
+
else
|
|
79
|
+
# Try to create the user first
|
|
80
|
+
echo "Creating test user..."
|
|
81
|
+
CREATE_REQ='{"jsonrpc":"2.0","method":"tools/call","params":{"name":"mlgym_user_create","arguments":{"email":"test-dollie@ezb.net","name":"Dollie Test User","password":"TestPassword123!","accept_terms":true}},"id":101}'
|
|
82
|
+
CREATE_RESP=$(echo "$CREATE_REQ" | node index.js 2>/dev/null | tail -1)
|
|
83
|
+
|
|
84
|
+
# Try login again
|
|
85
|
+
LOGIN_RESP=$(echo "$LOGIN_REQ" | node index.js 2>/dev/null | tail -1)
|
|
86
|
+
LOGIN_STATUS=$(echo "$LOGIN_RESP" | jq -r '.result.content[0].text' | jq -r '.status' 2>/dev/null)
|
|
87
|
+
|
|
88
|
+
if [ "$LOGIN_STATUS" = "authenticated" ]; then
|
|
89
|
+
echo "✅ Test 2: Authentication successful (after user creation)"
|
|
90
|
+
PASSED=$((PASSED + 1))
|
|
91
|
+
else
|
|
92
|
+
echo "⚠️ Test 2: Could not authenticate - skipping authenticated tests"
|
|
93
|
+
FAILED=$((FAILED + 1))
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Test 3: Valid deployment with public repo
|
|
98
|
+
echo "Test 3: Valid deployment with public GitHub repo"
|
|
99
|
+
TEST_PROJECT="dollie-valid-$(date +%s)"
|
|
100
|
+
run_test "Valid public repo deployment" "$TEST_PROJECT" "Test Next.js deployment from public repo" "https://github.com/vercel/next-learn.git" "main" "3000" "success"
|
|
101
|
+
|
|
102
|
+
# Test 4: Missing project_name
|
|
103
|
+
run_test "Missing project_name" "" "Test deployment" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
104
|
+
|
|
105
|
+
# Test 5: Missing project_description
|
|
106
|
+
run_test "Missing description" "test-project" "" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
107
|
+
|
|
108
|
+
# Test 6: Missing git_repo_url
|
|
109
|
+
run_test "Missing git repo URL" "test-project" "Test deployment" "" "" "" "error"
|
|
110
|
+
|
|
111
|
+
# Test 7: Invalid project_name format (uppercase)
|
|
112
|
+
run_test "Invalid project name (uppercase)" "INVALID-NAME" "Test deployment" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
113
|
+
|
|
114
|
+
# Test 8: Invalid project_name format (spaces)
|
|
115
|
+
run_test "Invalid project name (spaces)" "invalid name" "Test deployment" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
116
|
+
|
|
117
|
+
# Test 9: Short project_name (< 3 chars)
|
|
118
|
+
run_test "Short project name" "ab" "Test deployment" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
119
|
+
|
|
120
|
+
# Test 10: Short project_description (< 10 chars)
|
|
121
|
+
run_test "Short description" "test-project" "Short" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
122
|
+
|
|
123
|
+
# Test 11: Invalid git URL format (not https)
|
|
124
|
+
run_test "Invalid git URL (not https)" "test-project" "Test deployment" "git@github.com:vercel/next.js.git" "" "" "error"
|
|
125
|
+
|
|
126
|
+
# Test 12: Invalid git URL format (no .git)
|
|
127
|
+
run_test "Invalid git URL (no .git)" "test-project" "Test deployment" "https://github.com/vercel/next.js" "" "" "error"
|
|
128
|
+
|
|
129
|
+
# Test 13: Custom app_port
|
|
130
|
+
echo "Test 13: Custom app_port (8080)"
|
|
131
|
+
TEST_PROJECT="dollie-port-$(date +%s)"
|
|
132
|
+
run_test "Custom app port 8080" "$TEST_PROJECT" "Test deployment with custom port" "https://github.com/vercel/next-learn.git" "main" "8080" "success"
|
|
133
|
+
|
|
134
|
+
# Test 14: Custom git_branch
|
|
135
|
+
echo "Test 14: Custom git_branch"
|
|
136
|
+
TEST_PROJECT="dollie-branch-$(date +%s)"
|
|
137
|
+
run_test "Custom git branch" "$TEST_PROJECT" "Test deployment with custom branch" "https://github.com/vercel/next-learn.git" "canary" "3000" "success"
|
|
138
|
+
|
|
139
|
+
# Test 15: SQL injection in project_name
|
|
140
|
+
run_test "SQL injection (project_name)" "'; DROP TABLE applications;--" "Test deployment" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
141
|
+
|
|
142
|
+
# Test 16: SQL injection in git_repo_url
|
|
143
|
+
run_test "SQL injection (git_repo_url)" "test-project" "Test deployment" "https://github.com/vercel/next.js.git'; DROP TABLE--" "" "" "error"
|
|
144
|
+
|
|
145
|
+
# Test 17: XSS attempt in project_description
|
|
146
|
+
run_test "XSS attempt (description)" "test-project" "<script>alert('XSS')</script>" "https://github.com/vercel/next.js.git" "" "" "success"
|
|
147
|
+
|
|
148
|
+
# Test 18: Buffer overflow in project_name
|
|
149
|
+
echo "Test 18: Buffer overflow (very long project_name)"
|
|
150
|
+
LONG_NAME=$(python3 -c "print('a' * 1000)")
|
|
151
|
+
run_test "Buffer overflow (project_name)" "$LONG_NAME" "Test deployment" "https://github.com/vercel/next.js.git" "" "" "error"
|
|
152
|
+
|
|
153
|
+
# Test 19: Response format validation
|
|
154
|
+
echo "Test 19: Response format validation"
|
|
155
|
+
TEST_PROJECT="dollie-format-$(date +%s)"
|
|
156
|
+
REQ="{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"deploy_dollie\",\"arguments\":{\"project_name\":\"$TEST_PROJECT\",\"project_description\":\"Test deployment for response format validation\",\"git_repo_url\":\"https://github.com/vercel/next-learn.git\",\"git_branch\":\"main\"}},\"id\":102}"
|
|
157
|
+
RESP=$(echo "$REQ" | node index.js 2>/dev/null | tail -1)
|
|
158
|
+
CONTENT=$(echo "$RESP" | jq -r '.result.content[0].text')
|
|
159
|
+
|
|
160
|
+
HAS_SUCCESS=$(echo "$CONTENT" | jq -e '.success' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
161
|
+
HAS_URL=$(echo "$CONTENT" | jq -e '.deployment.url' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
162
|
+
HAS_SSH=$(echo "$CONTENT" | jq -e '.deployment.ssh' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
163
|
+
HAS_SSH_HOST=$(echo "$CONTENT" | jq -e '.deployment.ssh.host' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
164
|
+
HAS_SSH_PORT=$(echo "$CONTENT" | jq -e '.deployment.ssh.port' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
165
|
+
HAS_SSH_USERNAME=$(echo "$CONTENT" | jq -e '.deployment.ssh.username' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
166
|
+
HAS_SSH_PASSWORD=$(echo "$CONTENT" | jq -e '.deployment.ssh.password' > /dev/null 2>&1 && echo "yes" || echo "no")
|
|
167
|
+
|
|
168
|
+
if [ "$HAS_SUCCESS" = "yes" ] && [ "$HAS_URL" = "yes" ] && [ "$HAS_SSH" = "yes" ] && [ "$HAS_SSH_HOST" = "yes" ] && [ "$HAS_SSH_PORT" = "yes" ] && [ "$HAS_SSH_USERNAME" = "yes" ] && [ "$HAS_SSH_PASSWORD" = "yes" ]; then
|
|
169
|
+
echo "✅ Test 19: Response format valid"
|
|
170
|
+
PASSED=$((PASSED + 1))
|
|
171
|
+
else
|
|
172
|
+
echo "❌ Test 19: Response format invalid (success:$HAS_SUCCESS, url:$HAS_URL, ssh:$HAS_SSH, host:$HAS_SSH_HOST, port:$HAS_SSH_PORT, user:$HAS_SSH_USERNAME, pass:$HAS_SSH_PASSWORD)"
|
|
173
|
+
FAILED=$((FAILED + 1))
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
# Test 20: SSH password strength validation
|
|
177
|
+
echo "Test 20: SSH password strength validation"
|
|
178
|
+
SSH_PASSWORD=$(echo "$CONTENT" | jq -r '.deployment.ssh.password')
|
|
179
|
+
PASSWORD_LENGTH=${#SSH_PASSWORD}
|
|
180
|
+
if [ $PASSWORD_LENGTH -ge 16 ]; then
|
|
181
|
+
echo "✅ Test 20: SSH password is strong (length: $PASSWORD_LENGTH)"
|
|
182
|
+
PASSED=$((PASSED + 1))
|
|
183
|
+
else
|
|
184
|
+
echo "❌ Test 20: SSH password is weak (length: $PASSWORD_LENGTH, expected >= 16)"
|
|
185
|
+
FAILED=$((FAILED + 1))
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
echo ""
|
|
189
|
+
echo "==============================================="
|
|
190
|
+
echo "RESULTS: $PASSED passed, $FAILED failed (Total: $((PASSED + FAILED)))"
|
|
191
|
+
echo "==============================================="
|
|
192
|
+
|
|
193
|
+
if [ $FAILED -eq 0 ]; then
|
|
194
|
+
echo "✅ ALL TESTS PASSED"
|
|
195
|
+
exit 0
|
|
196
|
+
else
|
|
197
|
+
echo "⚠️ SOME TESTS FAILED"
|
|
198
|
+
exit 1
|
|
199
|
+
fi
|