Skip to content

Commit c8bd5a1

Browse files
committed
Test handling of FieldPathNotFound errors
Signed-off-by: Nic Cope <nicc@rk0n.org>
1 parent 1004f14 commit c8bd5a1

File tree

1 file changed

+156
-14
lines changed

1 file changed

+156
-14
lines changed

fn_test.go

Lines changed: 156 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -384,17 +384,15 @@ func TestRunFunction(t *testing.T) {
384384
},
385385
},
386386
},
387-
"FailedPatchNotSaved": {
388-
reason: "If we fail to patch a desired resource produced by a previous Function in the pipeline we should return a fatal result.",
387+
"OptionalFieldPathNotFound": {
388+
reason: "If we fail to patch a desired resource because an optional field path was not found we should skip the patch.",
389389
args: args{
390390
req: &fnv1beta1.RunFunctionRequest{
391391
Input: resource.MustStructObject(&v1beta1.Resources{
392392
Resources: []v1beta1.ComposedTemplate{
393393
{
394-
// This template base no base, so we try to
395-
// patch the resource named "cool-resource" in
396-
// the desired resources array.
397394
Name: "cool-resource",
395+
Base: &runtime.RawExtension{Raw: []byte(`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}`)},
398396
Patches: []v1beta1.ComposedPatch{
399397
{
400398
// This patch should work.
@@ -405,11 +403,11 @@ func TestRunFunction(t *testing.T) {
405403
},
406404
},
407405
{
408-
// This patch should return an error,
409-
// because the path is not an array.
406+
// This patch should be skipped, because
407+
// the path is not found
410408
Type: v1beta1.PatchTypeFromCompositeFieldPath,
411409
Patch: v1beta1.Patch{
412-
FromFieldPath: ptr.To[string]("spec.widgets[0]"),
410+
FromFieldPath: ptr.To[string]("spec.doesNotExist"),
413411
},
414412
},
415413
},
@@ -421,16 +419,94 @@ func TestRunFunction(t *testing.T) {
421419
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
422420
},
423421
},
422+
},
423+
},
424+
want: want{
425+
rsp: &fnv1beta1.RunFunctionResponse{
426+
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
424427
Desired: &fnv1beta1.State{
425428
Composite: &fnv1beta1.Resource{
426-
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
429+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR"}`),
427430
},
428431
Resources: map[string]*fnv1beta1.Resource{
429432
"cool-resource": {
430-
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":42}}`),
433+
// Watchers becomes "10" because our first patch
434+
// worked. We only skipped the second patch.
435+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":"10"}}`),
431436
},
432437
},
433438
},
439+
Context: &structpb.Struct{Fields: map[string]*structpb.Value{fncontext.KeyEnvironment: structpb.NewStructValue(nil)}},
440+
},
441+
},
442+
},
443+
"RequiredFieldPathNotFound": {
444+
reason: "If we fail to patch a desired resource because a required field path was not found, and the resource doesn't exist, we should not add it to desired state (i.e. create it).",
445+
args: args{
446+
req: &fnv1beta1.RunFunctionRequest{
447+
Input: resource.MustStructObject(&v1beta1.Resources{
448+
Resources: []v1beta1.ComposedTemplate{
449+
{
450+
Name: "new-resource",
451+
Base: &runtime.RawExtension{Raw: []byte(`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}`)},
452+
Patches: []v1beta1.ComposedPatch{
453+
{
454+
// This patch will fail because the path
455+
// is not found.
456+
Type: v1beta1.PatchTypeFromCompositeFieldPath,
457+
Patch: v1beta1.Patch{
458+
FromFieldPath: ptr.To[string]("spec.doesNotExist"),
459+
Policy: &v1beta1.PatchPolicy{
460+
FromFieldPath: ptr.To[v1beta1.FromFieldPathPolicy](v1beta1.FromFieldPathPolicyRequired),
461+
},
462+
},
463+
},
464+
},
465+
},
466+
{
467+
Name: "existing-resource",
468+
Base: &runtime.RawExtension{Raw: []byte(`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}`)},
469+
Patches: []v1beta1.ComposedPatch{
470+
{
471+
// This patch should work.
472+
Type: v1beta1.PatchTypeFromCompositeFieldPath,
473+
Patch: v1beta1.Patch{
474+
FromFieldPath: ptr.To[string]("spec.widgets"),
475+
ToFieldPath: ptr.To[string]("spec.watchers"),
476+
},
477+
},
478+
{
479+
// This patch will fail because the path
480+
// is not found.
481+
Type: v1beta1.PatchTypeFromCompositeFieldPath,
482+
Patch: v1beta1.Patch{
483+
FromFieldPath: ptr.To[string]("spec.doesNotExist"),
484+
Policy: &v1beta1.PatchPolicy{
485+
FromFieldPath: ptr.To[v1beta1.FromFieldPathPolicy](v1beta1.FromFieldPathPolicyRequired),
486+
},
487+
},
488+
},
489+
},
490+
},
491+
},
492+
}),
493+
Observed: &fnv1beta1.State{
494+
Composite: &fnv1beta1.Resource{
495+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
496+
},
497+
Resources: map[string]*fnv1beta1.Resource{
498+
// "existing-resource" exists.
499+
"existing-resource": {},
500+
501+
// Note "new-resource" doesn't appear in the
502+
// observed resources. It doesn't yet exist.
503+
},
504+
},
505+
Desired: &fnv1beta1.State{
506+
Composite: &fnv1beta1.Resource{
507+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
508+
},
509+
},
434510
},
435511
},
436512
want: want{
@@ -441,11 +517,77 @@ func TestRunFunction(t *testing.T) {
441517
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
442518
},
443519
Resources: map[string]*fnv1beta1.Resource{
444-
"cool-resource": {
445-
// spec.watchers would be "10" if we didn't
446-
// discard the patch that worked.
447-
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":42}}`),
520+
// Note that the first patch did work. We only
521+
// skipped the patch from the required field path.
522+
"existing-resource": {
523+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"CD","spec":{"watchers":"10"}}`),
448524
},
525+
526+
// Note "new-resource" doesn't appear here.
527+
},
528+
},
529+
Context: &structpb.Struct{Fields: map[string]*structpb.Value{fncontext.KeyEnvironment: structpb.NewStructValue(nil)}},
530+
Results: []*fnv1beta1.Result{
531+
{
532+
Severity: fnv1beta1.Severity_SEVERITY_WARNING,
533+
Message: `not adding new composed resource "new-resource" to desired state because "FromCompositeFieldPath" patch at index 0 has 'policy.fromFieldPath: Required': spec.doesNotExist: no such field`,
534+
},
535+
{
536+
Severity: fnv1beta1.Severity_SEVERITY_WARNING,
537+
Message: `cannot render composed resource "existing-resource" "FromCompositeFieldPath" patch at index 1: ignoring 'policy.fromFieldPath: Required' because 'to' resource already exists: spec.doesNotExist: no such field`,
538+
},
539+
},
540+
},
541+
},
542+
},
543+
"PatchErrorIsFatal": {
544+
reason: "If we fail to patch a desired resource we should return a fatal result.",
545+
args: args{
546+
req: &fnv1beta1.RunFunctionRequest{
547+
Input: resource.MustStructObject(&v1beta1.Resources{
548+
Resources: []v1beta1.ComposedTemplate{
549+
{
550+
Name: "cool-resource",
551+
Base: &runtime.RawExtension{Raw: []byte(`{"apiVersion":"example.org/v1","kind":"CD","spec":{}}`)},
552+
Patches: []v1beta1.ComposedPatch{
553+
{
554+
// This patch should work.
555+
Type: v1beta1.PatchTypeFromCompositeFieldPath,
556+
Patch: v1beta1.Patch{
557+
FromFieldPath: ptr.To[string]("spec.widgets"),
558+
ToFieldPath: ptr.To[string]("spec.watchers"),
559+
},
560+
},
561+
{
562+
// This patch should return an error,
563+
// because the path is not an array.
564+
Type: v1beta1.PatchTypeFromCompositeFieldPath,
565+
Patch: v1beta1.Patch{
566+
FromFieldPath: ptr.To[string]("spec.widgets[0]"),
567+
},
568+
},
569+
},
570+
},
571+
},
572+
}),
573+
Observed: &fnv1beta1.State{
574+
Composite: &fnv1beta1.Resource{
575+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
576+
},
577+
},
578+
Desired: &fnv1beta1.State{
579+
Composite: &fnv1beta1.Resource{
580+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
581+
},
582+
},
583+
},
584+
},
585+
want: want{
586+
rsp: &fnv1beta1.RunFunctionResponse{
587+
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
588+
Desired: &fnv1beta1.State{
589+
Composite: &fnv1beta1.Resource{
590+
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","spec":{"widgets":"10"}}`),
449591
},
450592
},
451593
Results: []*fnv1beta1.Result{

0 commit comments

Comments
 (0)