@@ -14,6 +14,10 @@ import (
1414 "github.com/stretchr/testify/assert"
1515 authv1 "k8s.io/api/authorization/v1"
1616 "k8s.io/client-go/rest"
17+ "knative.dev/pkg/apis"
18+ duckv1 "knative.dev/pkg/apis/duck/v1"
19+
20+ kservev1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1"
1721)
1822
1923func TestCanListLlamaStackDistributions (t * testing.T ) {
@@ -428,3 +432,154 @@ func TestHeadlessServicePortLogic(t *testing.T) {
428432 })
429433 }
430434}
435+
436+ // mustParseURL is a test helper that parses a URL string and panics on failure.
437+ func mustParseURL (u string ) * apis.URL {
438+ parsed , err := apis .ParseURL (u )
439+ if err != nil {
440+ panic (err )
441+ }
442+ return parsed
443+ }
444+
445+ func TestExtractEndpointsFromLLMInferenceService (t * testing.T ) {
446+ client := & TokenKubernetesClient {
447+ Logger : slog .Default (),
448+ }
449+
450+ t .Run ("nil LLMInferenceService returns empty endpoints" , func (t * testing.T ) {
451+ endpoints := client .extractEndpointsFromLLMInferenceService (nil )
452+ assert .Empty (t , endpoints )
453+ })
454+
455+ t .Run ("empty status returns empty endpoints" , func (t * testing.T ) {
456+ llmSvc := & kservev1alpha1.LLMInferenceService {}
457+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
458+ assert .Empty (t , endpoints )
459+ })
460+
461+ t .Run ("status.url set with external URL returns external endpoint" , func (t * testing.T ) {
462+ llmSvc := & kservev1alpha1.LLMInferenceService {
463+ Status : kservev1alpha1.LLMInferenceServiceStatus {
464+ URL : mustParseURL ("https://my-model.apps.example.com/v1" ),
465+ },
466+ }
467+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
468+ assert .Len (t , endpoints , 1 )
469+ assert .Equal (t , "external: https://my-model.apps.example.com/v1" , endpoints [0 ])
470+ })
471+
472+ t .Run ("status.url with svc.cluster.local is not added as external" , func (t * testing.T ) {
473+ llmSvc := & kservev1alpha1.LLMInferenceService {
474+ Status : kservev1alpha1.LLMInferenceServiceStatus {
475+ URL : mustParseURL ("https://my-model.namespace.svc.cluster.local:8080/v1" ),
476+ },
477+ }
478+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
479+ // svc.cluster.local URL in Status.URL is ignored; no Addresses to fall back to.
480+ assert .Empty (t , endpoints )
481+ })
482+
483+ t .Run ("only status.addresses with internal URL returns internal endpoint" , func (t * testing.T ) {
484+ // This is the real-world scenario: KServe controller sets addresses but not url or address (singular)
485+ llmSvc := & kservev1alpha1.LLMInferenceService {
486+ Status : kservev1alpha1.LLMInferenceServiceStatus {
487+ AddressStatus : duckv1.AddressStatus {
488+ Addresses : []duckv1.Addressable {
489+ {URL : mustParseURL ("https://openshift-ai-inference.openshift-ingress.svc.cluster.local/ns/my-model" )},
490+ },
491+ },
492+ },
493+ }
494+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
495+ assert .Len (t , endpoints , 1 )
496+ assert .Equal (t , "internal: https://openshift-ai-inference.openshift-ingress.svc.cluster.local/ns/my-model" , endpoints [0 ])
497+ })
498+
499+ t .Run ("only status.addresses with external URL returns external endpoint" , func (t * testing.T ) {
500+ llmSvc := & kservev1alpha1.LLMInferenceService {
501+ Status : kservev1alpha1.LLMInferenceServiceStatus {
502+ AddressStatus : duckv1.AddressStatus {
503+ Addresses : []duckv1.Addressable {
504+ {URL : mustParseURL ("https://my-model.apps.example.com/v1" )},
505+ },
506+ },
507+ },
508+ }
509+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
510+ assert .Len (t , endpoints , 1 )
511+ assert .Equal (t , "external: https://my-model.apps.example.com/v1" , endpoints [0 ])
512+ })
513+
514+ t .Run ("status.addresses with both internal and external URLs" , func (t * testing.T ) {
515+ llmSvc := & kservev1alpha1.LLMInferenceService {
516+ Status : kservev1alpha1.LLMInferenceServiceStatus {
517+ AddressStatus : duckv1.AddressStatus {
518+ Addresses : []duckv1.Addressable {
519+ {URL : mustParseURL ("https://my-model.namespace.svc.cluster.local:8080/v1" )},
520+ {URL : mustParseURL ("https://my-model.apps.example.com/v1" )},
521+ },
522+ },
523+ },
524+ }
525+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
526+ assert .Len (t , endpoints , 2 )
527+ assert .Equal (t , "internal: https://my-model.namespace.svc.cluster.local:8080/v1" , endpoints [0 ])
528+ assert .Equal (t , "external: https://my-model.apps.example.com/v1" , endpoints [1 ])
529+ })
530+
531+ t .Run ("status.url and status.addresses both set does not duplicate" , func (t * testing.T ) {
532+ llmSvc := & kservev1alpha1.LLMInferenceService {
533+ Status : kservev1alpha1.LLMInferenceServiceStatus {
534+ URL : mustParseURL ("https://my-model.apps.example.com/v1" ),
535+ AddressStatus : duckv1.AddressStatus {
536+ Addresses : []duckv1.Addressable {
537+ {URL : mustParseURL ("https://my-model.namespace.svc.cluster.local:8080/v1" )},
538+ {URL : mustParseURL ("https://my-model.apps.example.com/v1" )},
539+ },
540+ },
541+ },
542+ }
543+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
544+ // Internal from Addresses fallback, external from Status.URL, no duplicate external
545+ assert .Len (t , endpoints , 2 )
546+ assert .Equal (t , "internal: https://my-model.namespace.svc.cluster.local:8080/v1" , endpoints [0 ])
547+ assert .Equal (t , "external: https://my-model.apps.example.com/v1" , endpoints [1 ])
548+ })
549+
550+ t .Run ("status.address singular and status.addresses both set does not duplicate" , func (t * testing.T ) {
551+ internalURL := mustParseURL ("https://my-model.namespace.svc.cluster.local:8080/v1" )
552+ llmSvc := & kservev1alpha1.LLMInferenceService {
553+ Status : kservev1alpha1.LLMInferenceServiceStatus {
554+ AddressStatus : duckv1.AddressStatus {
555+ Address : & duckv1.Addressable {URL : internalURL },
556+ Addresses : []duckv1.Addressable {
557+ {URL : mustParseURL ("https://my-model.namespace.svc.cluster.local:8080/v1" )},
558+ {URL : mustParseURL ("https://my-model.apps.example.com/v1" )},
559+ },
560+ },
561+ },
562+ }
563+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
564+ // Should have internal from Address (singular) + external from Addresses, no duplicate internal
565+ assert .Len (t , endpoints , 2 )
566+ assert .Equal (t , "internal: https://my-model.namespace.svc.cluster.local:8080/v1" , endpoints [0 ])
567+ assert .Equal (t , "external: https://my-model.apps.example.com/v1" , endpoints [1 ])
568+ })
569+
570+ t .Run ("status.addresses with nil URL entries are skipped" , func (t * testing.T ) {
571+ llmSvc := & kservev1alpha1.LLMInferenceService {
572+ Status : kservev1alpha1.LLMInferenceServiceStatus {
573+ AddressStatus : duckv1.AddressStatus {
574+ Addresses : []duckv1.Addressable {
575+ {URL : nil },
576+ {URL : mustParseURL ("https://my-model.apps.example.com/v1" )},
577+ },
578+ },
579+ },
580+ }
581+ endpoints := client .extractEndpointsFromLLMInferenceService (llmSvc )
582+ assert .Len (t , endpoints , 1 )
583+ assert .Equal (t , "external: https://my-model.apps.example.com/v1" , endpoints [0 ])
584+ })
585+ }
0 commit comments