|
6 | 6 | "testing" |
7 | 7 |
|
8 | 8 | "github.com/stretchr/testify/assert" |
| 9 | + "github.com/stretchr/testify/require" |
9 | 10 |
|
10 | 11 | "github.com/modelcontextprotocol/registry/internal/config" |
11 | 12 | "github.com/modelcontextprotocol/registry/internal/validators" |
@@ -1241,8 +1242,8 @@ func TestValidateArgument_ValidNamedArguments(t *testing.T) { |
1241 | 1242 |
|
1242 | 1243 | func TestValidateArgument_ValidPositionalArguments(t *testing.T) { |
1243 | 1244 | positionalCases := []model.Argument{ |
1244 | | - {Type: model.ArgumentTypePositional, Name: "anything with spaces"}, |
1245 | | - {Type: model.ArgumentTypePositional, Name: "anything<with>brackets"}, |
| 1245 | + {Type: model.ArgumentTypePositional, Name: "anything with spaces", ValueHint: "target_dir"}, |
| 1246 | + {Type: model.ArgumentTypePositional, Name: "anything<with>brackets", ValueHint: "target_dir"}, |
1246 | 1247 | { |
1247 | 1248 | InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "--port 8080"}}, |
1248 | 1249 | Type: model.ArgumentTypePositional, |
@@ -1396,6 +1397,105 @@ func TestValidateArgument_ValidValueFields(t *testing.T) { |
1396 | 1397 | } |
1397 | 1398 | } |
1398 | 1399 |
|
| 1400 | +func TestValidateArgument_InvalidArgumentType(t *testing.T) { |
| 1401 | + invalidTypeCases := []struct { |
| 1402 | + name string |
| 1403 | + arg model.Argument |
| 1404 | + }{ |
| 1405 | + { |
| 1406 | + // Same shape as live entry io.github.Jordan-Horner/symbols@1.0.1 |
| 1407 | + // (there under packageArguments): name and value are set, so only |
| 1408 | + // the type check can fire |
| 1409 | + "empty_type", |
| 1410 | + model.Argument{ |
| 1411 | + InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "mcp"}}, |
| 1412 | + Type: "", |
| 1413 | + Name: "command", |
| 1414 | + }, |
| 1415 | + }, |
| 1416 | + {"literal", model.Argument{Type: "literal"}}, |
| 1417 | + {"flag", model.Argument{Type: "flag", Name: "--verbose"}}, |
| 1418 | + {"environment", model.Argument{Type: "environment", Name: "API_KEY"}}, |
| 1419 | + // Type values are case-sensitive: "Positional" is not "positional" |
| 1420 | + {"uppercase_positional", model.Argument{Type: "Positional"}}, |
| 1421 | + } |
| 1422 | + |
| 1423 | + for _, tc := range invalidTypeCases { |
| 1424 | + t.Run("Invalid_"+tc.name, func(t *testing.T) { |
| 1425 | + server := createValidServerWithArgument(tc.arg) |
| 1426 | + // ValidateServerJSON returns all validation results; using FirstError() to preserve existing test behavior |
| 1427 | + // In future, consider using result.Issues for comprehensive error reporting |
| 1428 | + result := validators.ValidateServerJSON(&server, validators.ValidationSchemaVersionAndSemantic) |
| 1429 | + err := result.FirstError() |
| 1430 | + require.Error(t, err, "Expected error for invalid argument type: %+v", tc.arg) |
| 1431 | + assert.Contains(t, err.Error(), "argument type must be 'positional' or 'named'") |
| 1432 | + }) |
| 1433 | + } |
| 1434 | +} |
| 1435 | + |
| 1436 | +func TestValidateArgument_PositionalValueOrHint(t *testing.T) { |
| 1437 | + positionalCases := []struct { |
| 1438 | + name string |
| 1439 | + arg model.Argument |
| 1440 | + expectError bool |
| 1441 | + }{ |
| 1442 | + { |
| 1443 | + "missing_value_and_value_hint", |
| 1444 | + model.Argument{Type: model.ArgumentTypePositional}, |
| 1445 | + true, |
| 1446 | + }, |
| 1447 | + { |
| 1448 | + "value_only", |
| 1449 | + model.Argument{ |
| 1450 | + InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "input.txt"}}, |
| 1451 | + Type: model.ArgumentTypePositional, |
| 1452 | + }, |
| 1453 | + false, |
| 1454 | + }, |
| 1455 | + { |
| 1456 | + "value_hint_only", |
| 1457 | + model.Argument{Type: model.ArgumentTypePositional, ValueHint: "file_path"}, |
| 1458 | + false, |
| 1459 | + }, |
| 1460 | + { |
| 1461 | + "value_and_value_hint", |
| 1462 | + model.Argument{ |
| 1463 | + InputWithVariables: model.InputWithVariables{Input: model.Input{Value: "input.txt"}}, |
| 1464 | + Type: model.ArgumentTypePositional, |
| 1465 | + ValueHint: "file_path", |
| 1466 | + }, |
| 1467 | + false, |
| 1468 | + }, |
| 1469 | + } |
| 1470 | + |
| 1471 | + for _, tc := range positionalCases { |
| 1472 | + t.Run(tc.name, func(t *testing.T) { |
| 1473 | + server := createValidServerWithArgument(tc.arg) |
| 1474 | + // ValidateServerJSON returns all validation results; using FirstError() to preserve existing test behavior |
| 1475 | + // In future, consider using result.Issues for comprehensive error reporting |
| 1476 | + result := validators.ValidateServerJSON(&server, validators.ValidationSchemaVersionAndSemantic) |
| 1477 | + err := result.FirstError() |
| 1478 | + if tc.expectError { |
| 1479 | + require.Error(t, err, "Expected error for positional argument without value or valueHint: %+v", tc.arg) |
| 1480 | + assert.Contains(t, err.Error(), "positional argument must provide a value or a valueHint") |
| 1481 | + } else { |
| 1482 | + assert.NoError(t, err, "Expected valid positional argument %+v", tc.arg) |
| 1483 | + } |
| 1484 | + }) |
| 1485 | + } |
| 1486 | + |
| 1487 | + // The same check must apply to arguments under packageArguments |
| 1488 | + t.Run("missing_value_and_value_hint_in_package_arguments", func(t *testing.T) { |
| 1489 | + server := createValidServerWithPackageArgument(model.Argument{Type: model.ArgumentTypePositional}) |
| 1490 | + // ValidateServerJSON returns all validation results; using FirstError() to preserve existing test behavior |
| 1491 | + // In future, consider using result.Issues for comprehensive error reporting |
| 1492 | + result := validators.ValidateServerJSON(&server, validators.ValidationSchemaVersionAndSemantic) |
| 1493 | + err := result.FirstError() |
| 1494 | + require.Error(t, err, "Expected error for positional package argument without value or valueHint") |
| 1495 | + assert.Contains(t, err.Error(), "positional argument must provide a value or a valueHint") |
| 1496 | + }) |
| 1497 | +} |
| 1498 | + |
1399 | 1499 | // Helper function to create a valid server with a specific argument for testing |
1400 | 1500 | func TestValidate_TransportValidation(t *testing.T) { |
1401 | 1501 | tests := []struct { |
@@ -1980,6 +2080,15 @@ func createValidServerWithArgument(arg model.Argument) apiv0.ServerJSON { |
1980 | 2080 | } |
1981 | 2081 | } |
1982 | 2082 |
|
| 2083 | +// createValidServerWithPackageArgument is createValidServerWithArgument with the |
| 2084 | +// argument under packageArguments instead of runtimeArguments |
| 2085 | +func createValidServerWithPackageArgument(arg model.Argument) apiv0.ServerJSON { |
| 2086 | + server := createValidServerWithArgument(arg) |
| 2087 | + server.Packages[0].RuntimeArguments = nil |
| 2088 | + server.Packages[0].PackageArguments = []model.Argument{arg} |
| 2089 | + return server |
| 2090 | +} |
| 2091 | + |
1983 | 2092 | func TestValidateTitle(t *testing.T) { |
1984 | 2093 | tests := []struct { |
1985 | 2094 | name string |
|
0 commit comments