@@ -331,263 +331,6 @@ func (s *NodesSuite) TestNodesStatsSummaryDenied() {
331331 })
332332}
333333
334- func (s * NodesSuite ) TestNodesTop () {
335- s .mockServer .Handle (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
336- w .Header ().Set ("Content-Type" , "application/json" )
337- // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-)
338- if req .URL .Path == "/api" {
339- _ , _ = w .Write ([]byte (`{"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}` ))
340- return
341- }
342- // Request Performed by DiscoveryClient to Kube API (Get API Groups)
343- if req .URL .Path == "/apis" {
344- _ , _ = w .Write ([]byte (`{"kind":"APIGroupList","apiVersion":"v1","groups":[{"name":"metrics.k8s.io","versions":[{"groupVersion":"metrics.k8s.io/v1beta1","version":"v1beta1"}],"preferredVersion":{"groupVersion":"metrics.k8s.io/v1beta1","version":"v1beta1"}}]}` ))
345- return
346- }
347- // Request Performed by DiscoveryClient to Kube API (Get API Resources)
348- if req .URL .Path == "/apis/metrics.k8s.io/v1beta1" {
349- _ , _ = w .Write ([]byte (`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"nodes","singularName":"","namespaced":false,"kind":"NodeMetrics","verbs":["get","list"]}]}` ))
350- return
351- }
352- // List Nodes
353- if req .URL .Path == "/api/v1/nodes" {
354- _ , _ = w .Write ([]byte (`{
355- "apiVersion": "v1",
356- "kind": "NodeList",
357- "items": [
358- {
359- "metadata": {
360- "name": "node-1",
361- "labels": {
362- "node-role.kubernetes.io/worker": ""
363- }
364- },
365- "status": {
366- "allocatable": {
367- "cpu": "4",
368- "memory": "16Gi"
369- },
370- "nodeInfo": {
371- "swap": {
372- "capacity": 0
373- }
374- }
375- }
376- },
377- {
378- "metadata": {
379- "name": "node-2",
380- "labels": {
381- "node-role.kubernetes.io/worker": ""
382- }
383- },
384- "status": {
385- "allocatable": {
386- "cpu": "4",
387- "memory": "16Gi"
388- },
389- "nodeInfo": {
390- "swap": {
391- "capacity": 0
392- }
393- }
394- }
395- }
396- ]
397- }` ))
398- return
399- }
400- // Get NodeMetrics
401- if req .URL .Path == "/apis/metrics.k8s.io/v1beta1/nodes" {
402- _ , _ = w .Write ([]byte (`{
403- "apiVersion": "metrics.k8s.io/v1beta1",
404- "kind": "NodeMetricsList",
405- "items": [
406- {
407- "metadata": {
408- "name": "node-1"
409- },
410- "timestamp": "2025-10-29T09:00:00Z",
411- "window": "30s",
412- "usage": {
413- "cpu": "500m",
414- "memory": "2Gi"
415- }
416- },
417- {
418- "metadata": {
419- "name": "node-2"
420- },
421- "timestamp": "2025-10-29T09:00:00Z",
422- "window": "30s",
423- "usage": {
424- "cpu": "1000m",
425- "memory": "4Gi"
426- }
427- }
428- ]
429- }` ))
430- return
431- }
432- // Get specific NodeMetrics
433- if req .URL .Path == "/apis/metrics.k8s.io/v1beta1/nodes/node-1" {
434- _ , _ = w .Write ([]byte (`{
435- "apiVersion": "metrics.k8s.io/v1beta1",
436- "kind": "NodeMetrics",
437- "metadata": {
438- "name": "node-1"
439- },
440- "timestamp": "2025-10-29T09:00:00Z",
441- "window": "30s",
442- "usage": {
443- "cpu": "500m",
444- "memory": "2Gi"
445- }
446- }` ))
447- return
448- }
449- w .WriteHeader (http .StatusNotFound )
450- }))
451- s .InitMcpClient ()
452-
453- s .Run ("nodes_top() - all nodes" , func () {
454- toolResult , err := s .CallTool ("nodes_top" , map [string ]interface {}{})
455- s .Require ().NotNil (toolResult , "toolResult should not be nil" )
456- s .Run ("no error" , func () {
457- s .Falsef (toolResult .IsError , "call tool should succeed" )
458- s .Nilf (err , "call tool should not return error object" )
459- })
460- s .Run ("returns metrics for all nodes" , func () {
461- content := toolResult .Content [0 ].(mcp.TextContent ).Text
462- s .Contains (content , "node-1" , "expected metrics to contain node-1" )
463- s .Contains (content , "node-2" , "expected metrics to contain node-2" )
464- s .Contains (content , "CPU(cores)" , "expected header with CPU column" )
465- s .Contains (content , "MEMORY(bytes)" , "expected header with MEMORY column" )
466- })
467- })
468-
469- s .Run ("nodes_top(name=node-1) - specific node" , func () {
470- toolResult , err := s .CallTool ("nodes_top" , map [string ]interface {}{
471- "name" : "node-1" ,
472- })
473- s .Require ().NotNil (toolResult , "toolResult should not be nil" )
474- s .Run ("no error" , func () {
475- s .Falsef (toolResult .IsError , "call tool should succeed" )
476- s .Nilf (err , "call tool should not return error object" )
477- })
478- s .Run ("returns metrics for specific node" , func () {
479- content := toolResult .Content [0 ].(mcp.TextContent ).Text
480- s .Contains (content , "node-1" , "expected metrics to contain node-1" )
481- s .Contains (content , "500m" , "expected CPU usage of 500m" )
482- s .Contains (content , "2048Mi" , "expected memory usage of 2048Mi" )
483- })
484- })
485-
486- s .Run ("nodes_top(label_selector=node-role.kubernetes.io/worker=)" , func () {
487- toolResult , err := s .CallTool ("nodes_top" , map [string ]interface {}{
488- "label_selector" : "node-role.kubernetes.io/worker=" ,
489- })
490- s .Require ().NotNil (toolResult , "toolResult should not be nil" )
491- s .Run ("no error" , func () {
492- s .Falsef (toolResult .IsError , "call tool should succeed" )
493- s .Nilf (err , "call tool should not return error object" )
494- })
495- s .Run ("returns metrics for filtered nodes" , func () {
496- content := toolResult .Content [0 ].(mcp.TextContent ).Text
497- s .Contains (content , "node-1" , "expected metrics to contain node-1" )
498- s .Contains (content , "node-2" , "expected metrics to contain node-2" )
499- })
500- })
501- }
502-
503- func (s * NodesSuite ) TestNodesTopMetricsUnavailable () {
504- s .mockServer .Handle (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
505- // List Nodes
506- if req .URL .Path == "/api/v1/nodes" {
507- w .Header ().Set ("Content-Type" , "application/json" )
508- w .WriteHeader (http .StatusOK )
509- _ , _ = w .Write ([]byte (`{
510- "apiVersion": "v1",
511- "kind": "NodeList",
512- "items": [
513- {
514- "metadata": {
515- "name": "node-1"
516- },
517- "status": {
518- "allocatable": {
519- "cpu": "4",
520- "memory": "16Gi"
521- }
522- }
523- }
524- ]
525- }` ))
526- return
527- }
528- // Metrics server not available
529- if req .URL .Path == "/apis/metrics.k8s.io/v1beta1/nodes" {
530- w .WriteHeader (http .StatusNotFound )
531- return
532- }
533- w .WriteHeader (http .StatusNotFound )
534- }))
535- s .InitMcpClient ()
536-
537- s .Run ("nodes_top() - metrics unavailable" , func () {
538- toolResult , err := s .CallTool ("nodes_top" , map [string ]interface {}{})
539- s .Require ().NotNil (toolResult , "toolResult should not be nil" )
540- s .Run ("has error" , func () {
541- s .Truef (toolResult .IsError , "call tool should fail when metrics unavailable" )
542- s .Nilf (err , "call tool should not return error object" )
543- })
544- s .Run ("describes metrics unavailable" , func () {
545- content := toolResult .Content [0 ].(mcp.TextContent ).Text
546- s .Contains (content , "failed to get nodes top" , "expected error message about failing to get nodes top" )
547- })
548- })
549- }
550-
551- func (s * NodesSuite ) TestNodesTopDenied () {
552- s .Require ().NoError (toml .Unmarshal ([]byte (`
553- denied_resources = [ { group = "metrics.k8s.io", version = "v1beta1" } ]
554- ` ), s .Cfg ), "Expected to parse denied resources config" )
555- s .mockServer .Handle (http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
556- w .Header ().Set ("Content-Type" , "application/json" )
557- // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-)
558- if req .URL .Path == "/api" {
559- _ , _ = w .Write ([]byte (`{"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}` ))
560- return
561- }
562- // Request Performed by DiscoveryClient to Kube API (Get API Groups)
563- if req .URL .Path == "/apis" {
564- _ , _ = w .Write ([]byte (`{"kind":"APIGroupList","apiVersion":"v1","groups":[{"name":"metrics.k8s.io","versions":[{"groupVersion":"metrics.k8s.io/v1beta1","version":"v1beta1"}],"preferredVersion":{"groupVersion":"metrics.k8s.io/v1beta1","version":"v1beta1"}}]}` ))
565- return
566- }
567- // Request Performed by DiscoveryClient to Kube API (Get API Resources)
568- if req .URL .Path == "/apis/metrics.k8s.io/v1beta1" {
569- _ , _ = w .Write ([]byte (`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"nodes","singularName":"","namespaced":false,"kind":"NodeMetrics","verbs":["get","list"]}]}` ))
570- return
571- }
572- w .WriteHeader (http .StatusNotFound )
573- }))
574- s .InitMcpClient ()
575-
576- s .Run ("nodes_top (denied)" , func () {
577- toolResult , err := s .CallTool ("nodes_top" , map [string ]interface {}{})
578- s .Require ().NotNil (toolResult , "toolResult should not be nil" )
579- s .Run ("has error" , func () {
580- s .Truef (toolResult .IsError , "call tool should fail" )
581- s .Nilf (err , "call tool should not return error object" )
582- })
583- s .Run ("describes denial" , func () {
584- expectedMessage := "failed to get nodes top: resource not allowed: metrics.k8s.io/v1beta1, Kind=NodeMetrics"
585- s .Equalf (expectedMessage , toolResult .Content [0 ].(mcp.TextContent ).Text ,
586- "expected descriptive error '%s', got %v" , expectedMessage , toolResult .Content [0 ].(mcp.TextContent ).Text )
587- })
588- })
589- }
590-
591334func TestNodes (t * testing.T ) {
592335 suite .Run (t , new (NodesSuite ))
593336}
0 commit comments