Jeremiah Lowin commited on
Commit
77b11ea
·
1 Parent(s): 35d4f60

Update tests for server input validation

Browse files
tests/server/test_server_interactions.py CHANGED
@@ -554,12 +554,12 @@ class TestToolParameters:
554
  async with Client(mcp) as client:
555
  with pytest.raises(
556
  ToolError,
557
- match="Error calling tool 'my_tool'",
558
  ):
559
  await client.call_tool("my_tool", {"x": "not an int"})
560
 
561
  async def test_tool_int_coercion(self):
562
- """Test string-to-int type coercion."""
563
  mcp = FastMCP()
564
 
565
  @mcp.tool
@@ -567,12 +567,15 @@ class TestToolParameters:
567
  return x + 1
568
 
569
  async with Client(mcp) as client:
570
- # String with integer value should be coerced to int
571
- result = await client.call_tool("add_one", {"x": "42"})
572
- assert result[0].text == "43" # type: ignore[attr-defined]
 
 
 
573
 
574
  async def test_tool_bool_coercion(self):
575
- """Test string-to-bool type coercion."""
576
  mcp = FastMCP()
577
 
578
  @mcp.tool
@@ -580,12 +583,18 @@ class TestToolParameters:
580
  return not flag
581
 
582
  async with Client(mcp) as client:
583
- # String with boolean value should be coerced to bool
584
- result = await client.call_tool("toggle", {"flag": "true"})
585
- assert result[0].text == "false" # type: ignore[attr-defined]
 
 
 
586
 
587
- result = await client.call_tool("toggle", {"flag": "false"})
588
- assert result[0].text == "true" # type: ignore[attr-defined]
 
 
 
589
 
590
  async def test_annotated_field_validation(self):
591
  mcp = FastMCP()
@@ -595,7 +604,10 @@ class TestToolParameters:
595
  pass
596
 
597
  async with Client(mcp) as client:
598
- with pytest.raises(ToolError, match="Error calling tool 'analyze'"):
 
 
 
599
  await client.call_tool("analyze", {"x": 0})
600
 
601
  async def test_default_field_validation(self):
@@ -606,7 +618,10 @@ class TestToolParameters:
606
  pass
607
 
608
  async with Client(mcp) as client:
609
- with pytest.raises(ToolError, match="Error calling tool 'analyze'"):
 
 
 
610
  await client.call_tool("analyze", {"x": 0})
611
 
612
  async def test_default_field_is_still_required_if_no_default_specified(self):
@@ -617,7 +632,9 @@ class TestToolParameters:
617
  pass
618
 
619
  async with Client(mcp) as client:
620
- with pytest.raises(ToolError, match="Error calling tool 'analyze'"):
 
 
621
  await client.call_tool("analyze", {})
622
 
623
  async def test_literal_type_validation_error(self):
@@ -628,7 +645,10 @@ class TestToolParameters:
628
  pass
629
 
630
  async with Client(mcp) as client:
631
- with pytest.raises(ToolError, match="Error calling tool 'analyze'"):
 
 
 
632
  await client.call_tool("analyze", {"x": "c"})
633
 
634
  async def test_literal_type_validation_success(self):
@@ -655,7 +675,10 @@ class TestToolParameters:
655
  return x.value
656
 
657
  async with Client(mcp) as client:
658
- with pytest.raises(ToolError, match="Error calling tool 'analyze'"):
 
 
 
659
  await client.call_tool("analyze", {"x": "some-color"})
660
 
661
  async def test_enum_type_validation_success(self):
@@ -688,7 +711,10 @@ class TestToolParameters:
688
  result = await client.call_tool("analyze", {"x": 1.0})
689
  assert result[0].text == "1.0" # type: ignore[attr-defined]
690
 
691
- with pytest.raises(ToolError, match="Error calling tool 'analyze'"):
 
 
 
692
  await client.call_tool("analyze", {"x": "not a number"})
693
 
694
  async def test_path_type(self):
@@ -714,7 +740,9 @@ class TestToolParameters:
714
  return str(path)
715
 
716
  async with Client(mcp) as client:
717
- with pytest.raises(ToolError, match="Error calling tool 'send_path'"):
 
 
718
  await client.call_tool("send_path", {"path": 1})
719
 
720
  async def test_uuid_type(self):
@@ -815,6 +843,7 @@ class TestToolParameters:
815
  assert result[0].text == "1 day, 0:00:00" # type: ignore[attr-defined]
816
 
817
  async def test_timedelta_type_parse_int(self):
 
818
  mcp = FastMCP()
819
 
820
  @mcp.tool
@@ -822,8 +851,12 @@ class TestToolParameters:
822
  return str(x)
823
 
824
  async with Client(mcp) as client:
825
- result = await client.call_tool("send_timedelta", {"x": 1000})
826
- assert result[0].text == "0:16:40" # type: ignore[attr-defined]
 
 
 
 
827
 
828
 
829
  class TestToolContextInjection:
 
554
  async with Client(mcp) as client:
555
  with pytest.raises(
556
  ToolError,
557
+ match="Input validation error: 'not an int' is not of type 'integer'",
558
  ):
559
  await client.call_tool("my_tool", {"x": "not an int"})
560
 
561
  async def test_tool_int_coercion(self):
562
+ """Test that invalid int input raises validation error."""
563
  mcp = FastMCP()
564
 
565
  @mcp.tool
 
567
  return x + 1
568
 
569
  async with Client(mcp) as client:
570
+ # String input should raise validation error (no coercion)
571
+ with pytest.raises(
572
+ ToolError,
573
+ match="Input validation error: '42' is not of type 'integer'",
574
+ ):
575
+ await client.call_tool("add_one", {"x": "42"})
576
 
577
  async def test_tool_bool_coercion(self):
578
+ """Test that invalid bool input raises validation error."""
579
  mcp = FastMCP()
580
 
581
  @mcp.tool
 
583
  return not flag
584
 
585
  async with Client(mcp) as client:
586
+ # String input should raise validation error (no coercion)
587
+ with pytest.raises(
588
+ ToolError,
589
+ match="Input validation error: 'true' is not of type 'boolean'",
590
+ ):
591
+ await client.call_tool("toggle", {"flag": "true"})
592
 
593
+ with pytest.raises(
594
+ ToolError,
595
+ match="Input validation error: 'false' is not of type 'boolean'",
596
+ ):
597
+ await client.call_tool("toggle", {"flag": "false"})
598
 
599
  async def test_annotated_field_validation(self):
600
  mcp = FastMCP()
 
604
  pass
605
 
606
  async with Client(mcp) as client:
607
+ with pytest.raises(
608
+ ToolError,
609
+ match="Input validation error: 0 is less than the minimum of 1",
610
+ ):
611
  await client.call_tool("analyze", {"x": 0})
612
 
613
  async def test_default_field_validation(self):
 
618
  pass
619
 
620
  async with Client(mcp) as client:
621
+ with pytest.raises(
622
+ ToolError,
623
+ match="Input validation error: 0 is less than the minimum of 1",
624
+ ):
625
  await client.call_tool("analyze", {"x": 0})
626
 
627
  async def test_default_field_is_still_required_if_no_default_specified(self):
 
632
  pass
633
 
634
  async with Client(mcp) as client:
635
+ with pytest.raises(
636
+ ToolError, match="Input validation error: 'x' is a required property"
637
+ ):
638
  await client.call_tool("analyze", {})
639
 
640
  async def test_literal_type_validation_error(self):
 
645
  pass
646
 
647
  async with Client(mcp) as client:
648
+ with pytest.raises(
649
+ ToolError,
650
+ match=r"Input validation error: 'c' is not one of \['a', 'b'\]",
651
+ ):
652
  await client.call_tool("analyze", {"x": "c"})
653
 
654
  async def test_literal_type_validation_success(self):
 
675
  return x.value
676
 
677
  async with Client(mcp) as client:
678
+ with pytest.raises(
679
+ ToolError,
680
+ match=r"Input validation error: 'some-color' is not one of \['red', 'green', 'blue'\]",
681
+ ):
682
  await client.call_tool("analyze", {"x": "some-color"})
683
 
684
  async def test_enum_type_validation_success(self):
 
711
  result = await client.call_tool("analyze", {"x": 1.0})
712
  assert result[0].text == "1.0" # type: ignore[attr-defined]
713
 
714
+ with pytest.raises(
715
+ ToolError,
716
+ match="Input validation error: 'not a number' is not valid under any of the given schemas",
717
+ ):
718
  await client.call_tool("analyze", {"x": "not a number"})
719
 
720
  async def test_path_type(self):
 
740
  return str(path)
741
 
742
  async with Client(mcp) as client:
743
+ with pytest.raises(
744
+ ToolError, match="Input validation error: 1 is not of type 'string'"
745
+ ):
746
  await client.call_tool("send_path", {"path": 1})
747
 
748
  async def test_uuid_type(self):
 
843
  assert result[0].text == "1 day, 0:00:00" # type: ignore[attr-defined]
844
 
845
  async def test_timedelta_type_parse_int(self):
846
+ """Test that invalid timedelta input raises validation error."""
847
  mcp = FastMCP()
848
 
849
  @mcp.tool
 
851
  return str(x)
852
 
853
  async with Client(mcp) as client:
854
+ # Int input should raise validation error (no conversion)
855
+ with pytest.raises(
856
+ ToolError,
857
+ match="Input validation error: 1000 is not of type 'string'",
858
+ ):
859
+ await client.call_tool("send_timedelta", {"x": 1000})
860
 
861
 
862
  class TestToolContextInjection: