| 
 | 1 | +package kibana  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"context"  | 
 | 5 | +	"fmt"  | 
 | 6 | +	"io"  | 
 | 7 | +	"net/http"  | 
 | 8 | +	"strings"  | 
 | 9 | +	"testing"  | 
 | 10 | + | 
 | 11 | +	"github.com/elastic/terraform-provider-elasticstack/generated/alerting"  | 
 | 12 | +	"github.com/elastic/terraform-provider-elasticstack/internal/models"  | 
 | 13 | +	"github.com/elastic/terraform-provider-elasticstack/internal/utils"  | 
 | 14 | +	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"  | 
 | 15 | +	"github.com/stretchr/testify/require"  | 
 | 16 | +	gomock "go.uber.org/mock/gomock"  | 
 | 17 | +)  | 
 | 18 | + | 
 | 19 | +func Test_maintenanceWindowResponseToModel(t *testing.T) {  | 
 | 20 | +	tests := []struct {  | 
 | 21 | +		name                      string  | 
 | 22 | +		spaceId                   string  | 
 | 23 | +		maintenanceWindowResponse *alerting.MaintenanceWindowResponseProperties  | 
 | 24 | +		expectedModel             *models.MaintenanceWindow  | 
 | 25 | +	}{  | 
 | 26 | +		{  | 
 | 27 | +			name:                      "nil response should return a nil model",  | 
 | 28 | +			spaceId:                   "space-id",  | 
 | 29 | +			maintenanceWindowResponse: nil,  | 
 | 30 | +			expectedModel:             nil,  | 
 | 31 | +		},  | 
 | 32 | +		{  | 
 | 33 | +			name:    "nil optional fields should not blow up the transform",  | 
 | 34 | +			spaceId: "space-id",  | 
 | 35 | +			maintenanceWindowResponse: &alerting.MaintenanceWindowResponseProperties{  | 
 | 36 | +				Id:        "some-long-id",  | 
 | 37 | +				CreatedBy: "me",  | 
 | 38 | +				CreatedAt: "today",  | 
 | 39 | +				UpdatedBy: "me",  | 
 | 40 | +				UpdatedAt: "today",  | 
 | 41 | +				Enabled:   true,  | 
 | 42 | +				Status:    "running",  | 
 | 43 | +				Title:     "maintenance-window-id",  | 
 | 44 | +				Schedule: alerting.MaintenanceWindowResponsePropertiesSchedule{  | 
 | 45 | +					Custom: alerting.MaintenanceWindowResponsePropertiesScheduleCustom{  | 
 | 46 | +						Duration:  "12d",  | 
 | 47 | +						Start:     "1999-02-02T05:00:00.200Z",  | 
 | 48 | +						Recurring: nil,  | 
 | 49 | +						Timezone:  nil,  | 
 | 50 | +					},  | 
 | 51 | +				},  | 
 | 52 | +				Scope: nil,  | 
 | 53 | +			},  | 
 | 54 | +			expectedModel: &models.MaintenanceWindow{  | 
 | 55 | +				MaintenanceWindowId: "some-long-id",  | 
 | 56 | +				SpaceId:             "space-id",  | 
 | 57 | +				Title:               "maintenance-window-id",  | 
 | 58 | +				Enabled:             true,  | 
 | 59 | +				CustomSchedule: models.MaintenanceWindowSchedule{  | 
 | 60 | +					Duration: "12d",  | 
 | 61 | +					Start:    "1999-02-02T05:00:00.200Z",  | 
 | 62 | +				},  | 
 | 63 | +			},  | 
 | 64 | +		},  | 
 | 65 | +		{  | 
 | 66 | +			name:    "a full response should be successfully transformed",  | 
 | 67 | +			spaceId: "space-id",  | 
 | 68 | +			maintenanceWindowResponse: &alerting.MaintenanceWindowResponseProperties{  | 
 | 69 | +				Id:        "maintenance-window-id",  | 
 | 70 | +				Title:     "maintenance-window-title",  | 
 | 71 | +				CreatedBy: "me",  | 
 | 72 | +				CreatedAt: "today",  | 
 | 73 | +				UpdatedBy: "me",  | 
 | 74 | +				UpdatedAt: "today",  | 
 | 75 | +				Enabled:   true,  | 
 | 76 | +				Status:    "running",  | 
 | 77 | +				Schedule: alerting.MaintenanceWindowResponsePropertiesSchedule{  | 
 | 78 | +					Custom: alerting.MaintenanceWindowResponsePropertiesScheduleCustom{  | 
 | 79 | +						Duration: "12d",  | 
 | 80 | +						Start:    "1999-02-02T05:00:00.200Z",  | 
 | 81 | +						Timezone: utils.Pointer("Asia/Taipei"),  | 
 | 82 | +						Recurring: &alerting.MaintenanceWindowResponsePropertiesScheduleCustomRecurring{  | 
 | 83 | +							End:         utils.Pointer("2029-05-17T05:05:00.000Z"),  | 
 | 84 | +							Every:       utils.Pointer("20d"),  | 
 | 85 | +							Occurrences: utils.Pointer(float32(30)),  | 
 | 86 | +							OnMonth:     []float32{2},  | 
 | 87 | +							OnMonthDay:  []float32{1},  | 
 | 88 | +							OnWeekDay:   []string{"WE", "TU"},  | 
 | 89 | +						},  | 
 | 90 | +					},  | 
 | 91 | +				},  | 
 | 92 | +				Scope: &alerting.MaintenanceWindowResponsePropertiesScope{  | 
 | 93 | +					Alerting: alerting.MaintenanceWindowResponsePropertiesScopeAlerting{  | 
 | 94 | +						Query: alerting.MaintenanceWindowResponsePropertiesScopeAlertingQuery{  | 
 | 95 | +							Kql: "_id: 'foobar'",  | 
 | 96 | +						},  | 
 | 97 | +					},  | 
 | 98 | +				},  | 
 | 99 | +			},  | 
 | 100 | +			expectedModel: &models.MaintenanceWindow{  | 
 | 101 | +				MaintenanceWindowId: "maintenance-window-id",  | 
 | 102 | +				Title:               "maintenance-window-title",  | 
 | 103 | +				SpaceId:             "space-id",  | 
 | 104 | +				Enabled:             true,  | 
 | 105 | +				CustomSchedule: models.MaintenanceWindowSchedule{  | 
 | 106 | +					Duration: "12d",  | 
 | 107 | +					Start:    "1999-02-02T05:00:00.200Z",  | 
 | 108 | +					Timezone: utils.Pointer("Asia/Taipei"),  | 
 | 109 | +					Recurring: &models.MaintenanceWindowScheduleRecurring{  | 
 | 110 | +						End:         utils.Pointer("2029-05-17T05:05:00.000Z"),  | 
 | 111 | +						Every:       utils.Pointer("20d"),  | 
 | 112 | +						Occurrences: utils.Pointer(float32(30)),  | 
 | 113 | +						OnMonth:     &[]float32{2},  | 
 | 114 | +						OnMonthDay:  &[]float32{1},  | 
 | 115 | +						OnWeekDay:   &[]string{"WE", "TU"},  | 
 | 116 | +					},  | 
 | 117 | +				},  | 
 | 118 | +				Scope: &models.MaintenanceWindowScope{  | 
 | 119 | +					Alerting: &models.MaintenanceWindowAlertingScope{  | 
 | 120 | +						Kql: "_id: 'foobar'",  | 
 | 121 | +					},  | 
 | 122 | +				},  | 
 | 123 | +			},  | 
 | 124 | +		},  | 
 | 125 | +	}  | 
 | 126 | + | 
 | 127 | +	for _, tt := range tests {  | 
 | 128 | +		t.Run(tt.name, func(t *testing.T) {  | 
 | 129 | +			model := maintenanceWindowResponseToModel(tt.spaceId, tt.maintenanceWindowResponse)  | 
 | 130 | + | 
 | 131 | +			require.Equal(t, tt.expectedModel, model)  | 
 | 132 | +		})  | 
 | 133 | +	}  | 
 | 134 | +}  | 
 | 135 | + | 
 | 136 | +func Test_CreateUpdateMaintenanceWindow(t *testing.T) {  | 
 | 137 | +	ctrl := gomock.NewController(t)  | 
 | 138 | + | 
 | 139 | +	getApiClient := func() (ApiClient, *alerting.MockAlertingAPI) {  | 
 | 140 | +		apiClient := NewMockApiClient(ctrl)  | 
 | 141 | +		apiClient.EXPECT().SetAlertingAuthContext(gomock.Any()).DoAndReturn(func(ctx context.Context) context.Context { return ctx })  | 
 | 142 | +		alertingClient := alerting.NewMockAlertingAPI(ctrl)  | 
 | 143 | +		apiClient.EXPECT().GetAlertingClient().DoAndReturn(func() (alerting.AlertingAPI, error) { return alertingClient, nil })  | 
 | 144 | +		return apiClient, alertingClient  | 
 | 145 | +	}  | 
 | 146 | + | 
 | 147 | +	tests := []struct {  | 
 | 148 | +		name              string  | 
 | 149 | +		testFunc          func(ctx context.Context, apiClient ApiClient, maintenanceWindow models.MaintenanceWindow) (*models.MaintenanceWindow, diag.Diagnostics)  | 
 | 150 | +		client            ApiClient  | 
 | 151 | +		maintenanceWindow models.MaintenanceWindow  | 
 | 152 | +		expectedRes       *models.MaintenanceWindow  | 
 | 153 | +		expectedErr       string  | 
 | 154 | +	}{  | 
 | 155 | +		{  | 
 | 156 | +			name:     "CreateMaintenanceWindow should not crash when backend returns 4xx",  | 
 | 157 | +			testFunc: CreateMaintenanceWindow,  | 
 | 158 | +			client: func() ApiClient {  | 
 | 159 | +				apiClient, alertingClient := getApiClient()  | 
 | 160 | +				alertingClient.EXPECT().CreateMaintenanceWindow(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, spaceId string) alerting.ApiCreateMaintenanceWindowRequest {  | 
 | 161 | +					return alerting.ApiCreateMaintenanceWindowRequest{ApiService: alertingClient}  | 
 | 162 | +				})  | 
 | 163 | +				alertingClient.EXPECT().CreateMaintenanceWindowExecute(gomock.Any()).DoAndReturn(func(r alerting.ApiCreateMaintenanceWindowRequest) (*alerting.MaintenanceWindowResponseProperties, *http.Response, error) {  | 
 | 164 | +					return nil, &http.Response{  | 
 | 165 | +						StatusCode: 401,  | 
 | 166 | +						Body:       io.NopCloser(strings.NewReader("some error")),  | 
 | 167 | +					}, nil  | 
 | 168 | +				})  | 
 | 169 | +				return apiClient  | 
 | 170 | +			}(),  | 
 | 171 | +			maintenanceWindow: models.MaintenanceWindow{},  | 
 | 172 | +			expectedRes:       nil,  | 
 | 173 | +			expectedErr:       "some error",  | 
 | 174 | +		},  | 
 | 175 | +		{  | 
 | 176 | +			name:     "UpdateMaintenanceWindow should not crash when backend returns 4xx",  | 
 | 177 | +			testFunc: UpdateMaintenanceWindow,  | 
 | 178 | +			client: func() ApiClient {  | 
 | 179 | +				apiClient, alertingClient := getApiClient()  | 
 | 180 | +				alertingClient.EXPECT().UpdateMaintenanceWindow(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, maintenanceWindowId string, spaceId string) alerting.ApiUpdateMaintenanceWindowRequest {  | 
 | 181 | +					return alerting.ApiUpdateMaintenanceWindowRequest{ApiService: alertingClient}  | 
 | 182 | +				})  | 
 | 183 | +				alertingClient.EXPECT().UpdateMaintenanceWindowExecute(gomock.Any()).DoAndReturn(func(r alerting.ApiUpdateMaintenanceWindowRequest) (*alerting.MaintenanceWindowResponseProperties, *http.Response, error) {  | 
 | 184 | +					return nil, &http.Response{  | 
 | 185 | +						StatusCode: 401,  | 
 | 186 | +						Body:       io.NopCloser(strings.NewReader("some error")),  | 
 | 187 | +					}, nil  | 
 | 188 | +				})  | 
 | 189 | +				return apiClient  | 
 | 190 | +			}(),  | 
 | 191 | +			maintenanceWindow: models.MaintenanceWindow{},  | 
 | 192 | +			expectedRes:       nil,  | 
 | 193 | +			expectedErr:       "some error",  | 
 | 194 | +		},  | 
 | 195 | +		{  | 
 | 196 | +			name:     "CreateMaintenanceWindow should not crash when backend returns an empty response and HTTP 200",  | 
 | 197 | +			testFunc: CreateMaintenanceWindow,  | 
 | 198 | +			client: func() ApiClient {  | 
 | 199 | +				apiClient, alertingClient := getApiClient()  | 
 | 200 | +				alertingClient.EXPECT().CreateMaintenanceWindow(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, spaceId string) alerting.ApiCreateMaintenanceWindowRequest {  | 
 | 201 | +					return alerting.ApiCreateMaintenanceWindowRequest{ApiService: alertingClient}  | 
 | 202 | +				})  | 
 | 203 | +				alertingClient.EXPECT().CreateMaintenanceWindowExecute(gomock.Any()).DoAndReturn(func(r alerting.ApiCreateMaintenanceWindowRequest) (*alerting.MaintenanceWindowResponseProperties, *http.Response, error) {  | 
 | 204 | +					return nil, &http.Response{  | 
 | 205 | +						StatusCode: 200,  | 
 | 206 | +						Body:       io.NopCloser(strings.NewReader("everything seems fine")),  | 
 | 207 | +					}, nil  | 
 | 208 | +				})  | 
 | 209 | +				return apiClient  | 
 | 210 | +			}(),  | 
 | 211 | +			maintenanceWindow: models.MaintenanceWindow{},  | 
 | 212 | +			expectedRes:       nil,  | 
 | 213 | +			expectedErr:       "empty response",  | 
 | 214 | +		},  | 
 | 215 | +		{  | 
 | 216 | +			name:     "UpdateMaintenanceWindow should not crash when backend returns an empty response and HTTP 200",  | 
 | 217 | +			testFunc: UpdateMaintenanceWindow,  | 
 | 218 | +			client: func() ApiClient {  | 
 | 219 | +				apiClient, alertingClient := getApiClient()  | 
 | 220 | +				alertingClient.EXPECT().UpdateMaintenanceWindow(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, maintenanceWindowId string, spaceId string) alerting.ApiUpdateMaintenanceWindowRequest {  | 
 | 221 | +					return alerting.ApiUpdateMaintenanceWindowRequest{ApiService: alertingClient}  | 
 | 222 | +				})  | 
 | 223 | +				alertingClient.EXPECT().UpdateMaintenanceWindowExecute(gomock.Any()).DoAndReturn(func(r alerting.ApiUpdateMaintenanceWindowRequest) (*alerting.MaintenanceWindowResponseProperties, *http.Response, error) {  | 
 | 224 | +					return nil, &http.Response{  | 
 | 225 | +						StatusCode: 200,  | 
 | 226 | +						Body:       io.NopCloser(strings.NewReader("everything seems fine")),  | 
 | 227 | +					}, nil  | 
 | 228 | +				})  | 
 | 229 | +				return apiClient  | 
 | 230 | +			}(),  | 
 | 231 | +			maintenanceWindow: models.MaintenanceWindow{},  | 
 | 232 | +			expectedRes:       nil,  | 
 | 233 | +			expectedErr:       "empty response",  | 
 | 234 | +		},  | 
 | 235 | +	}  | 
 | 236 | + | 
 | 237 | +	for _, tt := range tests {  | 
 | 238 | +		t.Run(tt.name, func(t *testing.T) {  | 
 | 239 | +			maintenanceWindow, diags := tt.testFunc(context.Background(), tt.client, tt.maintenanceWindow)  | 
 | 240 | + | 
 | 241 | +			if tt.expectedRes == nil {  | 
 | 242 | +				require.Nil(t, maintenanceWindow)  | 
 | 243 | +			} else {  | 
 | 244 | +				require.Equal(t, tt.expectedRes, maintenanceWindow)  | 
 | 245 | +			}  | 
 | 246 | + | 
 | 247 | +			if tt.expectedErr != "" {  | 
 | 248 | +				require.NotEmpty(t, diags)  | 
 | 249 | +				if !strings.Contains(diags[0].Detail, tt.expectedErr) {  | 
 | 250 | +					require.Fail(t, fmt.Sprintf("Diags ['%s'] should contain message ['%s']", diags[0].Detail, tt.expectedErr))  | 
 | 251 | +				}  | 
 | 252 | +			}  | 
 | 253 | +		})  | 
 | 254 | +	}  | 
 | 255 | +}  | 
0 commit comments