1- import { getTimeRangeAndStepSize } from './util' ;
1+ import { getTimeRangeAndStepSize , getPrometheusPrefix } from './util' ;
2+ import { isHttpUrl } from './helpers' ;
3+ import { KubernetesType } from './request' ;
24
3- beforeAll ( async ( ) => {
5+ vitest . mock ( './request' , ( ) => ( {
6+ isPrometheusInstalled : vitest . fn ( ) ,
7+ KubernetesType : {
8+ none : 'none' ,
9+ services : 'services' ,
10+ pods : 'pods' ,
11+ } ,
12+ } ) ) ;
13+
14+ vitest . mock ( '@kinvolk/headlamp-plugin/lib' , ( ) => ( {
15+ ConfigStore : vitest . fn ( ) ,
16+ } ) ) ;
17+
18+ import { isPrometheusInstalled } from './request' ;
19+ import { ConfigStore } from '@kinvolk/headlamp-plugin/lib' ;
20+
21+ const mockIsPrometheusInstalled = vitest . mocked ( isPrometheusInstalled ) ;
22+ const MockConfigStore = vitest . mocked ( ConfigStore ) ;
23+
24+ beforeAll ( ( ) => {
425 global . TextEncoder = require ( 'util' ) . TextEncoder ;
526 global . TextDecoder = require ( 'util' ) . TextDecoder ;
627} ) ;
728
29+ function mockClusterConfig ( clusterName : string , config : Record < string , unknown > | null ) {
30+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31+ ( MockConfigStore as any ) . mockImplementation ( ( ) => ( {
32+ get : ( ) => ( config ? { [ clusterName ] : config } : null ) ,
33+ } ) ) ;
34+ }
35+
36+ describe ( 'isHttpUrl' , ( ) => {
37+ test . each ( [
38+ [ 'http://prometheus.example.com' , true ] ,
39+ [ 'https://prometheus.example.com' , true ] ,
40+ [ 'http://prometheus.example.com:9090' , true ] ,
41+ [ 'https://prometheus.example.com/api/v1' , true ] ,
42+ [ '' , false ] ,
43+ [ 'monitoring/prometheus' , false ] ,
44+ [ 'ftp://example.com' , false ] ,
45+ [ 'not a url' , false ] ,
46+ ] ) ( 'isHttpUrl(%s) returns %s' , ( input , expected ) => {
47+ expect ( isHttpUrl ( input ) ) . toBe ( expected ) ;
48+ } ) ;
49+ } ) ;
50+
51+ describe ( 'getPrometheusPrefix' , ( ) => {
52+ beforeEach ( ( ) => {
53+ vitest . clearAllMocks ( ) ;
54+ } ) ;
55+
56+ describe ( 'auto-detect mode' , ( ) => {
57+ test ( 'returns detected endpoint when prometheus is found' , async ( ) => {
58+ mockClusterConfig ( 'test-cluster' , { autoDetect : true } ) ;
59+ mockIsPrometheusInstalled . mockResolvedValue ( {
60+ type : KubernetesType . services ,
61+ namespace : 'monitoring' ,
62+ name : 'prometheus' ,
63+ port : '9090' ,
64+ } ) ;
65+
66+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
67+ expect ( result ) . toBe ( 'monitoring/services/prometheus:9090' ) ;
68+ } ) ;
69+
70+ test ( 'returns endpoint without port when port is empty' , async ( ) => {
71+ mockClusterConfig ( 'test-cluster' , { autoDetect : true } ) ;
72+ mockIsPrometheusInstalled . mockResolvedValue ( {
73+ type : KubernetesType . services ,
74+ namespace : 'monitoring' ,
75+ name : 'prometheus' ,
76+ port : '' ,
77+ } ) ;
78+
79+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
80+ expect ( result ) . toBe ( 'monitoring/services/prometheus' ) ;
81+ } ) ;
82+
83+ test ( 'returns null when no prometheus is found' , async ( ) => {
84+ mockClusterConfig ( 'test-cluster' , { autoDetect : true } ) ;
85+ mockIsPrometheusInstalled . mockResolvedValue ( {
86+ type : KubernetesType . none ,
87+ namespace : '' ,
88+ name : '' ,
89+ port : '' ,
90+ } ) ;
91+
92+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
93+ expect ( result ) . toBeNull ( ) ;
94+ } ) ;
95+
96+ test ( 'takes priority over manual address' , async ( ) => {
97+ mockClusterConfig ( 'test-cluster' , {
98+ autoDetect : true ,
99+ address : 'https://manual-address.com' ,
100+ } ) ;
101+ mockIsPrometheusInstalled . mockResolvedValue ( {
102+ type : KubernetesType . services ,
103+ namespace : 'monitoring' ,
104+ name : 'prometheus' ,
105+ port : '9090' ,
106+ } ) ;
107+
108+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
109+ expect ( result ) . toBe ( 'monitoring/services/prometheus:9090' ) ;
110+ } ) ;
111+ } ) ;
112+
113+ describe ( 'manual address mode' , ( ) => {
114+ test . each ( [
115+ [ 'http://prometheus.example.com:9090' , 'http://prometheus.example.com:9090' ] ,
116+ [ 'https://prometheus.example.com' , 'https://prometheus.example.com' ] ,
117+ [ 'https://prometheus.example.com/' , 'https://prometheus.example.com' ] , // strips trailing slash
118+ [ 'monitoring/prometheus' , 'monitoring/services/prometheus' ] , // k8s service path
119+ [ ' monitoring/prometheus ' , 'monitoring/services/prometheus' ] , // trims whitespace
120+ ] ) ( 'address "%s" returns "%s"' , async ( address , expected ) => {
121+ mockClusterConfig ( 'test-cluster' , { autoDetect : false , address } ) ;
122+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
123+ expect ( result ) . toBe ( expected ) ;
124+ } ) ;
125+
126+ test ( 'returns null for invalid address format' , async ( ) => {
127+ mockClusterConfig ( 'test-cluster' , { autoDetect : false , address : 'invalid-format' } ) ;
128+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
129+ expect ( result ) . toBeNull ( ) ;
130+ } ) ;
131+ } ) ;
132+
133+ describe ( 'no configuration' , ( ) => {
134+ test ( 'returns null when cluster config is null' , async ( ) => {
135+ mockClusterConfig ( 'test-cluster' , null ) ;
136+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
137+ expect ( result ) . toBeNull ( ) ;
138+ } ) ;
139+
140+ test ( 'returns null when neither autoDetect nor address is set' , async ( ) => {
141+ mockClusterConfig ( 'test-cluster' , { } ) ;
142+ const result = await getPrometheusPrefix ( 'test-cluster' ) ;
143+ expect ( result ) . toBeNull ( ) ;
144+ } ) ;
145+ } ) ;
146+ } ) ;
147+
8148describe ( 'getTimeRangeAndStepSize' , ( ) => {
9- // Mock the current timestamp for consistent testing
10149 const mockNow = 1700000000 ;
11- const day = 86400 ; // seconds in a day
150+ const day = 86400 ;
12151
13152 beforeEach ( ( ) => {
14153 vitest . spyOn ( Date , 'now' ) . mockImplementation ( ( ) => mockNow * 1000 ) ;
@@ -29,77 +168,26 @@ describe('getTimeRangeAndStepSize', () => {
29168 {
30169 from : mockNow - ( mockNow % day ) ,
31170 to : mockNow ,
32- step : Math . max (
33- Math . floor ( ( ( mockNow - ( mockNow - ( mockNow % day ) ) ) * 1000 ) / 250 / 1000 ) ,
34- 1
35- ) ,
36- } ,
37- ] ,
38- [
39- 'yesterday' ,
40- 'medium' ,
41- {
42- from : mockNow - ( mockNow % day ) - day ,
43- to : mockNow - ( mockNow % day ) ,
44- step : 345 ,
171+ step : Math . max ( Math . floor ( ( ( mockNow - ( mockNow - ( mockNow % day ) ) ) * 1000 ) / 250 / 1000 ) , 1 ) ,
45172 } ,
46173 ] ,
174+ [ 'yesterday' , 'medium' , { from : mockNow - ( mockNow % day ) - day , to : mockNow - ( mockNow % day ) , step : 345 } ] ,
47175 [ 'week' , 'medium' , { from : mockNow - 7 * day , to : mockNow , step : 2419 } ] ,
48- [
49- 'lastweek' ,
50- 'medium' ,
51- {
52- from : mockNow - 14 * day ,
53- to : mockNow - 7 * day ,
54- step : 2419 ,
55- } ,
56- ] ,
57-
58- // Different resolutions with same interval
59- [ '1h' , 'low' , { from : mockNow - 3600 , to : mockNow , step : 36 } ] , // timeRange / 100
60- [ '1h' , 'medium' , { from : mockNow - 3600 , to : mockNow , step : 14 } ] , // timeRange / 250
61- [ '1h' , 'high' , { from : mockNow - 3600 , to : mockNow , step : 4 } ] , // timeRange / 750
62-
63- // Fixed step sizes with same interval
176+ [ 'lastweek' , 'medium' , { from : mockNow - 14 * day , to : mockNow - 7 * day , step : 2419 } ] ,
177+ // Different resolutions
178+ [ '1h' , 'low' , { from : mockNow - 3600 , to : mockNow , step : 36 } ] ,
179+ [ '1h' , 'medium' , { from : mockNow - 3600 , to : mockNow , step : 14 } ] ,
180+ [ '1h' , 'high' , { from : mockNow - 3600 , to : mockNow , step : 4 } ] ,
181+ // Fixed step sizes
64182 [ '1h' , '30s' , { from : mockNow - 3600 , to : mockNow , step : 30 } ] ,
65183 [ '1h' , '15m' , { from : mockNow - 3600 , to : mockNow , step : 900 } ] ,
66184 [ '1h' , '1h' , { from : mockNow - 3600 , to : mockNow , step : 3600 } ] ,
67-
68185 // Edge cases
69- [ '1m' , 'medium' , { from : mockNow - 60 , to : mockNow , step : 1 } ] , // Minimum step size is 1
70- [ '14d' , 'medium' , { from : mockNow - 14 * day , to : mockNow , step : 4838 } ] , // Large time range
71- [
72- 'invalid' , // Falls back to 10 minutes interval
73- 'medium' ,
74- {
75- from : mockNow - 600 ,
76- to : mockNow ,
77- step : 2 ,
78- } ,
79- ] ,
80- [
81- '1h' ,
82- 'invalid' , // Falls back to medium resolution
83- { from : mockNow - 3600 , to : mockNow , step : 14 } ,
84- ] ,
85- ] ) (
86- 'should return correct timeRange and stepSize for %s interval and %s resolution' ,
87- ( interval , resolution , expected ) => {
88- const result = getTimeRangeAndStepSize ( interval , resolution ) ;
89- expect ( result ) . toEqual ( expected ) ;
90- }
91- ) ;
92-
93- test ( 'should handle different timestamps correctly' , ( ) => {
94- // Test with a specific timestamp
95- const specificTime = 1600000000 ;
96- vitest . spyOn ( Date , 'now' ) . mockImplementation ( ( ) => specificTime * 1000 ) ;
97-
98- const result = getTimeRangeAndStepSize ( '1h' , 'medium' ) ;
99- expect ( result ) . toEqual ( {
100- from : specificTime - 3600 ,
101- to : specificTime ,
102- step : 14 ,
103- } ) ;
186+ [ '1m' , 'medium' , { from : mockNow - 60 , to : mockNow , step : 1 } ] ,
187+ [ '14d' , 'medium' , { from : mockNow - 14 * day , to : mockNow , step : 4838 } ] ,
188+ [ 'invalid' , 'medium' , { from : mockNow - 600 , to : mockNow , step : 2 } ] , // falls back to 10m
189+ [ '1h' , 'invalid' , { from : mockNow - 3600 , to : mockNow , step : 14 } ] , // falls back to medium
190+ ] ) ( 'interval=%s resolution=%s' , ( interval , resolution , expected ) => {
191+ expect ( getTimeRangeAndStepSize ( interval , resolution ) ) . toEqual ( expected ) ;
104192 } ) ;
105193} ) ;
0 commit comments