Skip to content

Commit 42a71d9

Browse files
committed
Validation and tests.
1 parent 867da54 commit 42a71d9

File tree

7 files changed

+717
-61
lines changed

7 files changed

+717
-61
lines changed

internal/clients/kibana/maintenance_window.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func CreateMaintenanceWindow(ctx context.Context, apiClient ApiClient, maintenan
7676
Custom: alerting.CreateMaintenanceWindowRequestScheduleCustom{
7777
Start: maintenanceWindow.CustomSchedule.Start,
7878
Duration: maintenanceWindow.CustomSchedule.Duration,
79+
Timezone: maintenanceWindow.CustomSchedule.Timezone,
7980
},
8081
}
8182

@@ -199,6 +200,7 @@ func UpdateMaintenanceWindow(ctx context.Context, apiClient ApiClient, maintenan
199200
Custom: alerting.CreateMaintenanceWindowRequestScheduleCustom{
200201
Start: maintenanceWindow.CustomSchedule.Start,
201202
Duration: maintenanceWindow.CustomSchedule.Duration,
203+
Timezone: maintenanceWindow.CustomSchedule.Timezone,
202204
},
203205
}
204206

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
}

internal/kibana/alerting.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"regexp"
87
"strings"
98

109
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
@@ -22,14 +21,6 @@ var frequencyMinSupportedVersion = version.Must(version.NewVersion("8.6.0"))
2221
var alertsFilterMinSupportedVersion = version.Must(version.NewVersion("8.9.0"))
2322
var alertDelayMinSupportedVersion = version.Must(version.NewVersion("8.13.0"))
2423

25-
// Avoid lint error on deprecated SchemaValidateFunc usage.
26-
//
27-
//nolint:staticcheck
28-
func stringIsAlertingDuration() schema.SchemaValidateFunc {
29-
r := regexp.MustCompile(`^[1-9][0-9]*(?:d|h|m|s)$`)
30-
return validation.StringMatch(r, "string is not a valid Alerting duration in seconds (s), minutes (m), hours (h), or days (d)")
31-
}
32-
3324
func ResourceAlertingRule() *schema.Resource {
3425
apikeySchema := map[string]*schema.Schema{
3526
"rule_id": {
@@ -80,7 +71,7 @@ func ResourceAlertingRule() *schema.Resource {
8071
Description: "The check interval, which specifies how frequently the rule conditions are checked. The interval must be specified in seconds, minutes, hours or days.",
8172
Type: schema.TypeString,
8273
Required: true,
83-
ValidateFunc: stringIsAlertingDuration(),
74+
ValidateFunc: utils.StringIsAlertingDuration(),
8475
},
8576
"actions": {
8677
Description: "An action that runs under defined conditions.",
@@ -129,7 +120,7 @@ func ResourceAlertingRule() *schema.Resource {
129120
Description: "Defines how often an alert generates repeated actions. This custom action interval must be specified in seconds, minutes, hours, or days. For example, 10m or 1h. This property is applicable only if `notify_when` is `onThrottleInterval`. NOTE: This is a rule level property; if you update the rule in Kibana, it is automatically changed to use action-specific `throttle` values.",
130121
Type: schema.TypeString,
131122
Optional: true,
132-
ValidateFunc: stringIsAlertingDuration(),
123+
ValidateFunc: utils.StringIsAlertingDuration(),
133124
},
134125
},
135126
},
@@ -207,7 +198,7 @@ func ResourceAlertingRule() *schema.Resource {
207198
Description: "Deprecated in 8.13.0. Defines how often an alert generates repeated actions. This custom action interval must be specified in seconds, minutes, hours, or days. For example, 10m or 1h. This property is applicable only if `notify_when` is `onThrottleInterval`. NOTE: This is a rule level property; if you update the rule in Kibana, it is automatically changed to use action-specific `throttle` values.",
208199
Type: schema.TypeString,
209200
Optional: true,
210-
ValidateFunc: stringIsAlertingDuration(),
201+
ValidateFunc: utils.StringIsAlertingDuration(),
211202
},
212203
"scheduled_task_id": {
213204
Description: "ID of the scheduled task that will execute the alert.",

0 commit comments

Comments
 (0)