1
13
14 package driver
15
16 import (
17 "fmt"
18 "reflect"
19 "regexp"
20 "testing"
21 "time"
22
23 sqlmock "github.com/DATA-DOG/go-sqlmock"
24 migrate "github.com/rubenv/sql-migrate"
25
26 rspb "helm.sh/helm/v3/pkg/release"
27 )
28
29 func TestSQLName(t *testing.T) {
30 sqlDriver, _ := newTestFixtureSQL(t)
31 if sqlDriver.Name() != SQLDriverName {
32 t.Errorf("Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name())
33 }
34 }
35
36 func TestSQLGet(t *testing.T) {
37 vers := int(1)
38 name := "smug-pigeon"
39 namespace := "default"
40 key := testKey(name, vers)
41 rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
42
43 body, _ := encodeRelease(rel)
44
45 sqlDriver, mock := newTestFixtureSQL(t)
46
47 query := fmt.Sprintf(
48 regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"),
49 sqlReleaseTableBodyColumn,
50 sqlReleaseTableName,
51 sqlReleaseTableKeyColumn,
52 sqlReleaseTableNamespaceColumn,
53 )
54
55 mock.
56 ExpectQuery(query).
57 WithArgs(key, namespace).
58 WillReturnRows(
59 mock.NewRows([]string{
60 sqlReleaseTableBodyColumn,
61 }).AddRow(
62 body,
63 ),
64 ).RowsWillBeClosed()
65
66 mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels)
67
68 got, err := sqlDriver.Get(key)
69 if err != nil {
70 t.Fatalf("Failed to get release: %v", err)
71 }
72
73 if !reflect.DeepEqual(rel, got) {
74 t.Errorf("Expected release {%v}, got {%v}", rel, got)
75 }
76
77 if err := mock.ExpectationsWereMet(); err != nil {
78 t.Errorf("sql expectations weren't met: %v", err)
79 }
80 }
81
82 func TestSQLList(t *testing.T) {
83 releases := []*rspb.Release{}
84 releases = append(releases, releaseStub("key-1", 1, "default", rspb.StatusUninstalled))
85 releases = append(releases, releaseStub("key-2", 1, "default", rspb.StatusUninstalled))
86 releases = append(releases, releaseStub("key-3", 1, "default", rspb.StatusDeployed))
87 releases = append(releases, releaseStub("key-4", 1, "default", rspb.StatusDeployed))
88 releases = append(releases, releaseStub("key-5", 1, "default", rspb.StatusSuperseded))
89 releases = append(releases, releaseStub("key-6", 1, "default", rspb.StatusSuperseded))
90
91 sqlDriver, mock := newTestFixtureSQL(t)
92
93 for i := 0; i < 3; i++ {
94 query := fmt.Sprintf(
95 "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2",
96 sqlReleaseTableKeyColumn,
97 sqlReleaseTableNamespaceColumn,
98 sqlReleaseTableBodyColumn,
99 sqlReleaseTableName,
100 sqlReleaseTableOwnerColumn,
101 sqlReleaseTableNamespaceColumn,
102 )
103
104 rows := mock.NewRows([]string{
105 sqlReleaseTableBodyColumn,
106 })
107 for _, r := range releases {
108 body, _ := encodeRelease(r)
109 rows.AddRow(body)
110 }
111 mock.
112 ExpectQuery(regexp.QuoteMeta(query)).
113 WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace).
114 WillReturnRows(rows).RowsWillBeClosed()
115
116 for _, r := range releases {
117 mockGetReleaseCustomLabels(mock, "", r.Namespace, r.Labels)
118 }
119 }
120
121
122 del, err := sqlDriver.List(func(rel *rspb.Release) bool {
123 return rel.Info.Status == rspb.StatusUninstalled
124 })
125
126 if err != nil {
127 t.Errorf("Failed to list deleted: %v", err)
128 }
129 if len(del) != 2 {
130 t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del)
131 }
132
133
134 dpl, err := sqlDriver.List(func(rel *rspb.Release) bool {
135 return rel.Info.Status == rspb.StatusDeployed
136 })
137
138 if err != nil {
139 t.Errorf("Failed to list deployed: %v", err)
140 }
141 if len(dpl) != 2 {
142 t.Errorf("Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl)
143 }
144
145
146 ssd, err := sqlDriver.List(func(rel *rspb.Release) bool {
147 return rel.Info.Status == rspb.StatusSuperseded
148 })
149
150 if err != nil {
151 t.Errorf("Failed to list superseded: %v", err)
152 }
153 if len(ssd) != 2 {
154 t.Errorf("Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd)
155 }
156
157 if err := mock.ExpectationsWereMet(); err != nil {
158 t.Errorf("sql expectations weren't met: %v", err)
159 }
160
161
162 rls := ssd[0]
163 _, ok := rls.Labels["name"]
164 if !ok {
165 t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels)
166 }
167 _, ok = rls.Labels["key1"]
168 if !ok {
169 t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels)
170 }
171 }
172
173 func TestSqlCreate(t *testing.T) {
174 vers := 1
175 name := "smug-pigeon"
176 namespace := "default"
177 key := testKey(name, vers)
178 rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
179
180 sqlDriver, mock := newTestFixtureSQL(t)
181 body, _ := encodeRelease(rel)
182
183 query := fmt.Sprintf(
184 "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)",
185 sqlReleaseTableName,
186 sqlReleaseTableKeyColumn,
187 sqlReleaseTableTypeColumn,
188 sqlReleaseTableBodyColumn,
189 sqlReleaseTableNameColumn,
190 sqlReleaseTableNamespaceColumn,
191 sqlReleaseTableVersionColumn,
192 sqlReleaseTableStatusColumn,
193 sqlReleaseTableOwnerColumn,
194 sqlReleaseTableCreatedAtColumn,
195 )
196
197 mock.ExpectBegin()
198 mock.
199 ExpectExec(regexp.QuoteMeta(query)).
200 WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())).
201 WillReturnResult(sqlmock.NewResult(1, 1))
202
203 labelsQuery := fmt.Sprintf(
204 "INSERT INTO %s (%s,%s,%s,%s) VALUES ($1,$2,$3,$4)",
205 sqlCustomLabelsTableName,
206 sqlCustomLabelsTableReleaseKeyColumn,
207 sqlCustomLabelsTableReleaseNamespaceColumn,
208 sqlCustomLabelsTableKeyColumn,
209 sqlCustomLabelsTableValueColumn,
210 )
211
212 mock.MatchExpectationsInOrder(false)
213 for k, v := range filterSystemLabels(rel.Labels) {
214 mock.
215 ExpectExec(regexp.QuoteMeta(labelsQuery)).
216 WithArgs(key, rel.Namespace, k, v).
217 WillReturnResult(sqlmock.NewResult(1, 1))
218 }
219 mock.ExpectCommit()
220
221 if err := sqlDriver.Create(key, rel); err != nil {
222 t.Fatalf("failed to create release with key %s: %v", key, err)
223 }
224
225 if err := mock.ExpectationsWereMet(); err != nil {
226 t.Errorf("sql expectations weren't met: %v", err)
227 }
228 }
229
230 func TestSqlCreateAlreadyExists(t *testing.T) {
231 vers := 1
232 name := "smug-pigeon"
233 namespace := "default"
234 key := testKey(name, vers)
235 rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
236
237 sqlDriver, mock := newTestFixtureSQL(t)
238 body, _ := encodeRelease(rel)
239
240 insertQuery := fmt.Sprintf(
241 "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)",
242 sqlReleaseTableName,
243 sqlReleaseTableKeyColumn,
244 sqlReleaseTableTypeColumn,
245 sqlReleaseTableBodyColumn,
246 sqlReleaseTableNameColumn,
247 sqlReleaseTableNamespaceColumn,
248 sqlReleaseTableVersionColumn,
249 sqlReleaseTableStatusColumn,
250 sqlReleaseTableOwnerColumn,
251 sqlReleaseTableCreatedAtColumn,
252 )
253
254
255 mock.ExpectBegin()
256 mock.
257 ExpectExec(regexp.QuoteMeta(insertQuery)).
258 WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())).
259 WillReturnError(fmt.Errorf("dialect dependent SQL error"))
260
261 selectQuery := fmt.Sprintf(
262 regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"),
263 sqlReleaseTableKeyColumn,
264 sqlReleaseTableName,
265 sqlReleaseTableKeyColumn,
266 sqlReleaseTableNamespaceColumn,
267 )
268
269
270 mock.
271 ExpectQuery(selectQuery).
272 WithArgs(key, namespace).
273 WillReturnRows(
274 mock.NewRows([]string{
275 sqlReleaseTableKeyColumn,
276 }).AddRow(
277 key,
278 ),
279 ).RowsWillBeClosed()
280 mock.ExpectRollback()
281
282 if err := sqlDriver.Create(key, rel); err == nil {
283 t.Fatalf("failed to create release with key %s: %v", key, err)
284 }
285
286 if err := mock.ExpectationsWereMet(); err != nil {
287 t.Errorf("sql expectations weren't met: %v", err)
288 }
289 }
290
291 func TestSqlUpdate(t *testing.T) {
292 vers := 1
293 name := "smug-pigeon"
294 namespace := "default"
295 key := testKey(name, vers)
296 rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
297
298 sqlDriver, mock := newTestFixtureSQL(t)
299 body, _ := encodeRelease(rel)
300
301 query := fmt.Sprintf(
302 "UPDATE %s SET %s = $1, %s = $2, %s = $3, %s = $4, %s = $5, %s = $6 WHERE %s = $7 AND %s = $8",
303 sqlReleaseTableName,
304 sqlReleaseTableBodyColumn,
305 sqlReleaseTableNameColumn,
306 sqlReleaseTableVersionColumn,
307 sqlReleaseTableStatusColumn,
308 sqlReleaseTableOwnerColumn,
309 sqlReleaseTableModifiedAtColumn,
310 sqlReleaseTableKeyColumn,
311 sqlReleaseTableNamespaceColumn,
312 )
313
314 mock.
315 ExpectExec(regexp.QuoteMeta(query)).
316 WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix()), key, namespace).
317 WillReturnResult(sqlmock.NewResult(0, 1))
318
319 if err := sqlDriver.Update(key, rel); err != nil {
320 t.Fatalf("failed to update release with key %s: %v", key, err)
321 }
322
323 if err := mock.ExpectationsWereMet(); err != nil {
324 t.Errorf("sql expectations weren't met: %v", err)
325 }
326 }
327
328 func TestSqlQuery(t *testing.T) {
329
330 labelSetUnknown := map[string]string{
331 "name": "smug-pigeon",
332 "owner": sqlReleaseDefaultOwner,
333 "status": "unknown",
334 }
335 labelSetDeployed := map[string]string{
336 "name": "smug-pigeon",
337 "owner": sqlReleaseDefaultOwner,
338 "status": "deployed",
339 }
340 labelSetAll := map[string]string{
341 "name": "smug-pigeon",
342 "owner": sqlReleaseDefaultOwner,
343 }
344
345 supersededRelease := releaseStub("smug-pigeon", 1, "default", rspb.StatusSuperseded)
346 supersededReleaseBody, _ := encodeRelease(supersededRelease)
347 deployedRelease := releaseStub("smug-pigeon", 2, "default", rspb.StatusDeployed)
348 deployedReleaseBody, _ := encodeRelease(deployedRelease)
349
350
351 sqlDriver, mock := newTestFixtureSQL(t)
352
353 query := fmt.Sprintf(
354 "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4",
355 sqlReleaseTableKeyColumn,
356 sqlReleaseTableNamespaceColumn,
357 sqlReleaseTableBodyColumn,
358 sqlReleaseTableName,
359 sqlReleaseTableNameColumn,
360 sqlReleaseTableOwnerColumn,
361 sqlReleaseTableStatusColumn,
362 sqlReleaseTableNamespaceColumn,
363 )
364
365 mock.
366 ExpectQuery(regexp.QuoteMeta(query)).
367 WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "unknown", "default").
368 WillReturnRows(
369 mock.NewRows([]string{
370 sqlReleaseTableBodyColumn,
371 }),
372 ).RowsWillBeClosed()
373
374 mock.
375 ExpectQuery(regexp.QuoteMeta(query)).
376 WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default").
377 WillReturnRows(
378 mock.NewRows([]string{
379 sqlReleaseTableBodyColumn,
380 }).AddRow(
381 deployedReleaseBody,
382 ),
383 ).RowsWillBeClosed()
384
385 mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels)
386
387 query = fmt.Sprintf(
388 "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3",
389 sqlReleaseTableKeyColumn,
390 sqlReleaseTableNamespaceColumn,
391 sqlReleaseTableBodyColumn,
392 sqlReleaseTableName,
393 sqlReleaseTableNameColumn,
394 sqlReleaseTableOwnerColumn,
395 sqlReleaseTableNamespaceColumn,
396 )
397
398 mock.
399 ExpectQuery(regexp.QuoteMeta(query)).
400 WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "default").
401 WillReturnRows(
402 mock.NewRows([]string{
403 sqlReleaseTableBodyColumn,
404 }).AddRow(
405 supersededReleaseBody,
406 ).AddRow(
407 deployedReleaseBody,
408 ),
409 ).RowsWillBeClosed()
410
411 mockGetReleaseCustomLabels(mock, "", supersededRelease.Namespace, supersededRelease.Labels)
412 mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels)
413
414 _, err := sqlDriver.Query(labelSetUnknown)
415 if err == nil {
416 t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound)
417 } else if err != ErrReleaseNotFound {
418 t.Fatalf("failed to query for unknown smug-pigeon release: %v", err)
419 }
420
421 results, err := sqlDriver.Query(labelSetDeployed)
422 if err != nil {
423 t.Fatalf("failed to query for deployed smug-pigeon release: %v", err)
424 }
425
426 for _, res := range results {
427 if !reflect.DeepEqual(res, deployedRelease) {
428 t.Errorf("Expected release {%v}, got {%v}", deployedRelease, res)
429 }
430 }
431
432 results, err = sqlDriver.Query(labelSetAll)
433 if err != nil {
434 t.Fatalf("failed to query release history for smug-pigeon: %v", err)
435 }
436
437 if len(results) != 2 {
438 t.Errorf("expected a resultset of size 2, got %d", len(results))
439 }
440
441 for _, res := range results {
442 if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) {
443 t.Errorf("Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res)
444 }
445 }
446
447 if err := mock.ExpectationsWereMet(); err != nil {
448 t.Errorf("sql expectations weren't met: %v", err)
449 }
450 }
451
452 func TestSqlDelete(t *testing.T) {
453 vers := 1
454 name := "smug-pigeon"
455 namespace := "default"
456 key := testKey(name, vers)
457 rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
458
459 body, _ := encodeRelease(rel)
460
461 sqlDriver, mock := newTestFixtureSQL(t)
462
463 selectQuery := fmt.Sprintf(
464 "SELECT %s FROM %s WHERE %s = $1 AND %s = $2",
465 sqlReleaseTableBodyColumn,
466 sqlReleaseTableName,
467 sqlReleaseTableKeyColumn,
468 sqlReleaseTableNamespaceColumn,
469 )
470
471 mock.ExpectBegin()
472 mock.
473 ExpectQuery(regexp.QuoteMeta(selectQuery)).
474 WithArgs(key, namespace).
475 WillReturnRows(
476 mock.NewRows([]string{
477 sqlReleaseTableBodyColumn,
478 }).AddRow(
479 body,
480 ),
481 ).RowsWillBeClosed()
482
483 deleteQuery := fmt.Sprintf(
484 "DELETE FROM %s WHERE %s = $1 AND %s = $2",
485 sqlReleaseTableName,
486 sqlReleaseTableKeyColumn,
487 sqlReleaseTableNamespaceColumn,
488 )
489
490 mock.
491 ExpectExec(regexp.QuoteMeta(deleteQuery)).
492 WithArgs(key, namespace).
493 WillReturnResult(sqlmock.NewResult(0, 1))
494
495 mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels)
496
497 deleteLabelsQuery := fmt.Sprintf(
498 "DELETE FROM %s WHERE %s = $1 AND %s = $2",
499 sqlCustomLabelsTableName,
500 sqlCustomLabelsTableReleaseKeyColumn,
501 sqlCustomLabelsTableReleaseNamespaceColumn,
502 )
503 mock.
504 ExpectExec(regexp.QuoteMeta(deleteLabelsQuery)).
505 WithArgs(key, namespace).
506 WillReturnResult(sqlmock.NewResult(0, 1))
507
508 mock.ExpectCommit()
509
510 deletedRelease, err := sqlDriver.Delete(key)
511 if err := mock.ExpectationsWereMet(); err != nil {
512 t.Errorf("sql expectations weren't met: %v", err)
513 }
514 if err != nil {
515 t.Fatalf("failed to delete release with key %q: %v", key, err)
516 }
517
518 if !reflect.DeepEqual(rel, deletedRelease) {
519 t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease)
520 }
521 }
522
523 func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace string, labels map[string]string) {
524 query := fmt.Sprintf(
525 regexp.QuoteMeta("SELECT %s, %s FROM %s WHERE %s = $1 AND %s = $2"),
526 sqlCustomLabelsTableKeyColumn,
527 sqlCustomLabelsTableValueColumn,
528 sqlCustomLabelsTableName,
529 sqlCustomLabelsTableReleaseKeyColumn,
530 sqlCustomLabelsTableReleaseNamespaceColumn,
531 )
532
533 eq := mock.ExpectQuery(query).
534 WithArgs(key, namespace)
535
536 returnRows := mock.NewRows([]string{
537 sqlCustomLabelsTableKeyColumn,
538 sqlCustomLabelsTableValueColumn,
539 })
540 for k, v := range labels {
541 returnRows.AddRow(k, v)
542 }
543 eq.WillReturnRows(returnRows).RowsWillBeClosed()
544 }
545
546 func TestSqlChechkAppliedMigrations(t *testing.T) {
547 cases := []struct {
548 migrationsToApply []*migrate.Migration
549 appliedMigrationsIDs []string
550 expectedResult bool
551 errorExplanation string
552 }{
553 {
554 migrationsToApply: []*migrate.Migration{{Id: "init1"}, {Id: "init2"}, {Id: "init3"}},
555 appliedMigrationsIDs: []string{"1", "2", "init1", "3", "init2", "4", "5"},
556 expectedResult: false,
557 errorExplanation: "Has found one migration id \"init3\" as applied, that was not applied",
558 },
559 {
560 migrationsToApply: []*migrate.Migration{{Id: "init1"}, {Id: "init2"}, {Id: "init3"}},
561 appliedMigrationsIDs: []string{"1", "2", "init1", "3", "init2", "4", "init3", "5"},
562 expectedResult: true,
563 errorExplanation: "Has not found one or more migration ids, that was applied",
564 },
565 {
566 migrationsToApply: []*migrate.Migration{{Id: "init"}},
567 appliedMigrationsIDs: []string{"1", "2", "3", "inits", "4", "tinit", "5"},
568 expectedResult: false,
569 errorExplanation: "Has found single \"init\", that was not applied",
570 },
571 {
572 migrationsToApply: []*migrate.Migration{{Id: "init"}},
573 appliedMigrationsIDs: []string{"1", "2", "init", "3", "init2", "4", "init3", "5"},
574 expectedResult: true,
575 errorExplanation: "Has not found single migration id \"init\", that was applied",
576 },
577 }
578 for i, c := range cases {
579 sqlDriver, mock := newTestFixtureSQL(t)
580 rows := sqlmock.NewRows([]string{"id", "applied_at"})
581 for _, id := range c.appliedMigrationsIDs {
582 rows.AddRow(id, time.Time{})
583 }
584 mock.
585 ExpectQuery("").
586 WillReturnRows(rows)
587 mock.ExpectCommit()
588 if sqlDriver.checkAlreadyApplied(c.migrationsToApply) != c.expectedResult {
589 t.Errorf("Test case: %v, Expected: %v, Have: %v, Explanation: %v", i, c.expectedResult, !c.expectedResult, c.errorExplanation)
590 }
591 }
592 }
593
View as plain text