validationtest.*ViolationListAssertion.WithErrors   B
last analyzed

Complexity

Conditions 6

Size

Total Lines 32
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
dl 0
loc 32
rs 8.4186
c 0
b 0
f 0
nop 1
1
// Copyright 2021 Igor Lazarev. All rights reserved.
2
// Use of this source code is governed by a MIT-style
3
// license that can be found in the LICENSE file.
4
5
// Package validationtest contains helper functions for testing purposes.
6
package validationtest
7
8
import (
9
	"fmt"
10
	"strconv"
11
	"testing"
12
13
	"github.com/muonsoft/validation"
14
	"github.com/stretchr/testify/assert"
15
)
16
17
// TestingT is an interface wrapper around [testing.T].
18
type TestingT interface {
19
	Helper()
20
	Error(args ...interface{})
21
	Errorf(format string, args ...interface{})
22
	Fatal(args ...interface{})
23
}
24
25
// ViolationAttributes are used to compare violation against expected values. An empty value is not compared.
26
type ViolationAttributes struct {
27
	Error        error
28
	Message      string
29
	PropertyPath string
30
}
31
32
// Assertion is a structure for testing an error for implementing validator violations.
33
type Assertion struct {
34
	t   TestingT
35
	err error
36
}
37
38
// Assert creates a new [Assertion] for the error.
39
func Assert(t TestingT, err error) *Assertion {
40
	t.Helper()
41
42
	return &Assertion{t: t, err: err}
43
}
44
45
// IsViolation checks that err implements [github.com/muonsoft/validation.Violation] and returns [ViolationAssertion]
46
// for attributes assertions.
47
func (a *Assertion) IsViolation() *ViolationAssertion {
48
	a.t.Helper()
49
50
	violation, ok := validation.UnwrapViolation(a.err)
51
	if !ok {
52
		assert.Fail(a.t, fmt.Sprintf("failed asserting that err is a Violation\nactual %+v", a.err))
53
54
		return nil
55
	}
56
57
	return newViolationAssertion(a.t, violation)
58
}
59
60
// IsViolationList checks that err implements [github.com/muonsoft/validation.IsViolationList] and
61
// returns [ViolationListAssertion] for attributes assertions.
62
func (a *Assertion) IsViolationList() *ViolationListAssertion {
63
	a.t.Helper()
64
65
	violations, ok := validation.UnwrapViolationList(a.err)
66
	if !ok {
67
		assert.Fail(a.t, fmt.Sprintf("failed asserting that err is a ViolationList\nactual %+v", a.err))
68
69
		return nil
70
	}
71
72
	return &ViolationListAssertion{t: a.t, violations: violations}
73
}
74
75
// ViolationListAssertion is a structure for testing violation list attributes.
76
type ViolationListAssertion struct {
77
	t          TestingT
78
	violations *validation.ViolationList
79
}
80
81
// Assert is used for the client-side side assertion of violations by a callback function.
82
func (a *ViolationListAssertion) Assert(
83
	assert func(tb testing.TB, violations []validation.Violation),
84
) *ViolationListAssertion {
85
	if a == nil {
86
		return nil
87
	}
88
	a.t.Helper()
89
90
	if tb, ok := a.t.(testing.TB); ok {
91
		assert(tb, a.violations.AsSlice())
92
	} else {
93
		a.t.Fatal("t must implement testing.TB")
94
	}
95
96
	return a
97
}
98
99
// WithLen checks that the violation list has exact length.
100
func (a *ViolationListAssertion) WithLen(length int) *ViolationListAssertion {
101
	if a == nil {
102
		return nil
103
	}
104
	a.t.Helper()
105
106
	actual := a.violations.Len()
107
	if actual != length {
108
		a.t.Errorf(fmt.Sprintf(
109
			"failed asserting that violation list length is equal to %d, actual is %d\nactual %+v",
110
			length,
111
			actual,
112
			a.violations,
113
		))
114
	}
115
116
	return a
117
}
118
119
// WithOneViolation checks that the violation list contains exactly one violation and returns
120
// a [ViolationAssertion] to test it.
121
func (a *ViolationListAssertion) WithOneViolation() *ViolationAssertion {
122
	if a == nil {
123
		return nil
124
	}
125
	a.t.Helper()
126
127
	if a.violations.Len() != 1 {
128
		assert.Fail(a.t, fmt.Sprintf(
129
			"failed asserting that violation list contains exactly one violation\nactual %+v",
130
			a.violations,
131
		))
132
		return nil
133
	}
134
135
	return newViolationAssertionAt(a.t, a.violations.First(), 0)
136
}
137
138
// HasViolationAt checks that the violation list contains element at specific index and returns
139
// a [ViolationAssertion] to test it.
140
func (a *ViolationListAssertion) HasViolationAt(index int) *ViolationAssertion {
141
	if a == nil {
142
		return nil
143
	}
144
	a.t.Helper()
145
146
	violations := a.violations.AsSlice()
147
	if index >= len(violations) {
148
		assert.Fail(a.t, fmt.Sprintf(
149
			"failed asserting that violation list contains violation at index %d\nactual %+v",
150
			index,
151
			a.violations,
152
		))
153
		return nil
154
	}
155
156
	return newViolationAssertionAt(a.t, violations[index], 0)
157
}
158
159
// WithErrors checks that the violation list contains violations with specific codes in a given order.
160
func (a *ViolationListAssertion) WithErrors(errs ...error) *ViolationListAssertion {
161
	if a == nil {
162
		return nil
163
	}
164
	a.t.Helper()
165
166
	length := a.violations.Len()
167
	if length != len(errs) {
168
		assert.Fail(a.t, fmt.Sprintf(
169
			"failed asserting that violation list length is equal to %d, actual is %d\nactual %+v",
170
			len(errs),
171
			length,
172
			a.violations,
173
		))
174
	}
175
176
	a.violations.ForEach(func(i int, violation validation.Violation) error {
177
		if i >= len(errs) {
178
			return nil
179
		}
180
		if violation.Unwrap() != errs[i] {
181
			assert.Fail(a.t, fmt.Sprintf(
182
				`failed asserting that violation at %d has error "%s", actual is "%s"`,
183
				i,
184
				errs[i],
185
				violation.Unwrap(),
186
			))
187
		}
188
		return nil
189
	})
190
191
	return a
192
}
193
194
// WithAttributes checks that the violation list contains violations with the expected attributes in a given order.
195
// Empty values are not compared.
196
func (a *ViolationListAssertion) WithAttributes(violations ...ViolationAttributes) *ViolationListAssertion {
197
	if a == nil {
198
		return nil
199
	}
200
	a.t.Helper()
201
202
	length := a.violations.Len()
203
	if length != len(violations) {
204
		assert.Fail(a.t, fmt.Sprintf(
205
			"failed asserting that violation list length is equal to %d, actual is %d\nactual %+v",
206
			len(violations),
207
			length,
208
			a.violations,
209
		))
210
	}
211
212
	a.violations.ForEach(func(i int, violation validation.Violation) error {
213
		if i >= len(violations) {
214
			return nil
215
		}
216
		expected := violations[i]
217
218
		if expected.Error != nil && violation.Unwrap() != expected.Error {
219
			assert.Fail(a.t, fmt.Sprintf(
220
				`failed asserting that violation at %d has error "%s", actual is "%s"`,
221
				i,
222
				expected.Error,
223
				violation.Unwrap(),
224
			))
225
		}
226
227
		if expected.Message != "" && violation.Message() != expected.Message {
228
			assert.Fail(a.t, fmt.Sprintf(
229
				`failed asserting that violation at %d has message "%s", actual is "%s"`,
230
				i,
231
				expected.Message,
232
				violation.Message(),
233
			))
234
		}
235
236
		if expected.PropertyPath != "" && violation.PropertyPath().String() != expected.PropertyPath {
237
			assert.Fail(a.t, fmt.Sprintf(
238
				`failed asserting that violation at %d has property path "%s", actual is "%s"`,
239
				i,
240
				expected.PropertyPath,
241
				violation.PropertyPath().String(),
242
			))
243
		}
244
245
		return nil
246
	})
247
248
	return a
249
}
250
251
// ViolationAssertion is a structure for testing violation attributes.
252
type ViolationAssertion struct {
253
	t         TestingT
254
	violation validation.Violation
255
	index     int
256
}
257
258
func newViolationAssertion(t TestingT, violation validation.Violation) *ViolationAssertion {
259
	return &ViolationAssertion{t: t, violation: violation, index: -1}
260
}
261
262
func newViolationAssertionAt(t TestingT, violation validation.Violation, index int) *ViolationAssertion {
263
	return &ViolationAssertion{t: t, violation: violation, index: index}
264
}
265
266
// Assert is used for the client-side assertion of the violation by a callback function.
267
func (a *ViolationAssertion) Assert(
268
	assert func(tb testing.TB, violation validation.Violation),
269
) *ViolationAssertion {
270
	if a == nil {
271
		return nil
272
	}
273
	a.t.Helper()
274
275
	if tb, ok := a.t.(testing.TB); ok {
276
		assert(tb, a.violation)
277
	} else {
278
		a.t.Fatal("t must implement testing.TB")
279
	}
280
281
	return a
282
}
283
284
// WithError checks that violation has expected error.
285
func (a *ViolationAssertion) WithError(err error) *ViolationAssertion {
286
	if a == nil {
287
		return nil
288
	}
289
	a.t.Helper()
290
291
	actual := a.violation.Unwrap()
292
	if actual != err {
293
		assert.Fail(a.t, fmt.Sprintf(
294
			`failed asserting that violation%s has error "%s", actual is "%s"`,
295
			a.atIndex(),
296
			err,
297
			actual,
298
		))
299
	}
300
301
	return a
302
}
303
304
// WithMessage checks that violation has expected message.
305
func (a *ViolationAssertion) WithMessage(message string) *ViolationAssertion {
306
	if a == nil {
307
		return nil
308
	}
309
	a.t.Helper()
310
311
	actual := a.violation.Message()
312
	if actual != message {
313
		assert.Fail(a.t, fmt.Sprintf(
314
			`failed asserting that violation%s has message "%s", actual is "%s"`,
315
			a.atIndex(),
316
			message,
317
			actual,
318
		))
319
	}
320
321
	return a
322
}
323
324
// WithPropertyPath checks that the tested violation has an expected property path.
325
func (a *ViolationAssertion) WithPropertyPath(path string) *ViolationAssertion {
326
	if a == nil {
327
		return nil
328
	}
329
	a.t.Helper()
330
331
	actual := a.violation.PropertyPath().String()
332
	if actual != path {
333
		assert.Fail(a.t, fmt.Sprintf(
334
			`failed asserting that violation%s has property path "%s", actual is "%s"`,
335
			a.atIndex(),
336
			path,
337
			actual,
338
		))
339
	}
340
341
	return a
342
}
343
344
// EqualTo checks that the tested assertion is equal to the expected one.
345
func (a *ViolationAssertion) EqualTo(violation validation.Violation) *ViolationAssertion {
346
	if a == nil {
347
		return nil
348
	}
349
	a.t.Helper()
350
351
	assert.Equal(a.t, violation, a.violation, "failed asserting that violations are equal")
352
353
	return a
354
}
355
356
// EqualToError checks that violation rendered to an error is equal to the expected one.
357
func (a *ViolationAssertion) EqualToError(errString string) *ViolationAssertion {
358
	if a == nil {
359
		return nil
360
	}
361
	a.t.Helper()
362
363
	actual := a.violation.Error()
364
	if actual != errString {
365
		assert.Fail(a.t, fmt.Sprintf(
366
			`failed asserting that violation%s error is equal to "%s", actual is "%s"`,
367
			a.atIndex(),
368
			errString,
369
			actual,
370
		))
371
	}
372
373
	return a
374
}
375
376
func (a *ViolationAssertion) atIndex() string {
377
	if a.index < 0 {
378
		return ""
379
	}
380
	return " #" + strconv.Itoa(a.index)
381
}
382