package launcher

import (
	"context"
	"errors"
	"fmt"
	nethttp "net/http"
	"net/http/httptest"
	"os"
	"runtime"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/kit/platform"
	errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors"
	"github.com/influxdata/influxdb/v2/mock"
	"github.com/influxdata/influxdb/v2/notification"
	"github.com/influxdata/influxdb/v2/notification/check"
	"github.com/influxdata/influxdb/v2/notification/endpoint"
	"github.com/influxdata/influxdb/v2/notification/rule"
	"github.com/influxdata/influxdb/v2/pkger"
	"github.com/influxdata/influxdb/v2/task/taskmodel"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

var ctx = context.Background()

func TestLauncher_Pkger(t *testing.T) {
	l := RunAndSetupNewLauncherOrFail(ctx, t, func(o *InfluxdOpts) {
		o.LogLevel = zap.ErrorLevel
	})
	defer l.ShutdownOrFail(t, ctx)

	require.NoError(t, l.BucketService(t).DeleteBucket(ctx, l.Bucket.ID))

	svc := l.PkgerService(t)

	resourceCheck := newResourceChecker(l)

	deleteStackFn := func(t *testing.T, stackID platform.ID) {
		t.Helper()
		err := svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{
			OrgID:   l.Org.ID,
			UserID:  l.User.ID,
			StackID: stackID,
		})
		require.NoError(t, err, "failed to delete stack and its associated resources")
	}

	newStackFn := func(t *testing.T, stack pkger.StackCreate) (pkger.Stack, func()) {
		t.Helper()

		if stack.OrgID == 0 {
			stack.OrgID = l.Org.ID
		}

		newStack, err := svc.InitStack(ctx, l.User.ID, stack)
		require.NoError(t, err)

		assert.Equal(t, l.Org.ID, newStack.OrgID)
		ev := newStack.LatestEvent()
		assert.Equal(t, stack.Name, ev.Name)
		assert.Equal(t, stack.Description, ev.Description)
		assert.NotNil(t, ev.Resources, "failed to match stack resources")
		expectedURLs := stack.TemplateURLs
		if expectedURLs == nil {
			expectedURLs = []string{}
		}
		assert.Equal(t, expectedURLs, ev.TemplateURLs, "failed to match stack URLs")
		assert.NotZero(t, newStack.CreatedAt)
		assert.NotZero(t, ev.UpdatedAt)

		return newStack, func() {
			// deletes are idempotent, so any error encountered here is not a not found error
			// but rather an error to concern ourselves with.
			deleteStackFn(t, newStack.ID)
		}
	}

	newTemplate := func(objects ...pkger.Object) *pkger.Template {
		return &pkger.Template{Objects: objects}
	}

	newBucketObject := func(pkgName, name, desc string) pkger.Object {
		obj := pkger.BucketToObject("", influxdb.Bucket{
			Name:        name,
			Description: desc,
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newCheckBase := func(t *testing.T, name string, every time.Duration) check.Base {
		t.Helper()

		d, err := notification.FromTimeDuration(every)
		require.NoError(t, err)

		return check.Base{
			Name:  name,
			Every: &d,
			Query: influxdb.DashboardQuery{
				Text: `from(bucket: "rucket_1") |> range(start: -1d)`,
			},
			StatusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }",
		}
	}

	newCheckDeadmanObject := func(t *testing.T, pkgName, name string, every time.Duration) pkger.Object {
		t.Helper()

		obj := pkger.CheckToObject("", &check.Deadman{
			Base:  newCheckBase(t, name, every),
			Level: notification.Critical,
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newCheckThresholdObject := func(t *testing.T, pkgName, name string, every time.Duration) pkger.Object {
		t.Helper()

		obj := pkger.CheckToObject("", &check.Threshold{
			Base: newCheckBase(t, name, every),
			Thresholds: []check.ThresholdConfig{
				check.Lesser{
					ThresholdConfigBase: check.ThresholdConfigBase{
						Level: notification.Critical,
					},
					Value: 0.5,
				},
			},
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newDashObject := func(pkgName, name, desc string) pkger.Object {
		obj := pkger.DashboardToObject("", influxdb.Dashboard{
			Name:        name,
			Description: desc,
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newEndpointBase := func(name, desc string) endpoint.Base {
		return endpoint.Base{
			Name:        name,
			Description: desc,
			Status:      influxdb.Inactive,
		}
	}

	newEndpointHTTP := func(pkgName, name, description string) pkger.Object {
		obj := pkger.NotificationEndpointToObject("", &endpoint.HTTP{
			Base:       newEndpointBase(name, description),
			AuthMethod: "none",
			URL:        "http://example.com",
			Method:     "GET",
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newEndpointPagerDuty := func(pkgName, name, description string) pkger.Object {
		obj := pkger.NotificationEndpointToObject("", &endpoint.PagerDuty{
			Base:      newEndpointBase(name, description),
			ClientURL: "http://example.com",
			RoutingKey: influxdb.SecretField{
				Key:   "routing-key",
				Value: strPtr("threeve"),
			},
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newEndpointSlack := func(pkgName, name, description string) pkger.Object {
		obj := pkger.NotificationEndpointToObject("", &endpoint.Slack{
			Base: newEndpointBase(name, description),
			URL:  "http://influxslack.com",
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newLabelObject := func(pkgName, name, desc, color string) pkger.Object {
		obj := pkger.LabelToObject("", influxdb.Label{
			Name: name,
			Properties: map[string]string{
				"color":       color,
				"description": desc,
			},
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newRuleObject := func(t *testing.T, pkgName, name, endpointPkgName, desc string) pkger.Object {
		t.Helper()

		every, err := notification.FromTimeDuration(time.Hour)
		require.NoError(t, err)

		obj := pkger.NotificationRuleToObject("", endpointPkgName, &rule.HTTP{
			Base: rule.Base{
				Name:        name,
				Description: desc,
				Every:       &every,
				StatusRules: []notification.StatusRule{{CurrentLevel: notification.Critical}},
			},
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newTaskObject := func(pkgName, name, description string) pkger.Object {
		obj := pkger.TaskToObject("", taskmodel.Task{
			Name:        name,
			Description: description,
			Flux:        "buckets()",
			Every:       "1h",
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newTelegrafObject := func(pkgName, name, description string) pkger.Object {
		obj := pkger.TelegrafToObject("", influxdb.TelegrafConfig{
			Name:        name,
			Description: description,
			Config:      telegrafCfg,
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	newVariableObject := func(pkgName, name, description string, selected ...string) pkger.Object {
		obj := pkger.VariableToObject("", influxdb.Variable{
			Name:        name,
			Description: description,
			Selected:    selected,
			Arguments: &influxdb.VariableArguments{
				Type:   "constant",
				Values: influxdb.VariableConstantValues{"a", "b"},
			},
		})
		obj.SetMetadataName(pkgName)
		return obj
	}

	validateAllResourcesRemoved := func(t *testing.T, summary pkger.Summary) {
		t.Helper()

		for _, b := range summary.Buckets {
			_, err := resourceCheck.getBucket(t, bySafeID(b.ID))
			assertErrorCode(t, errors2.ENotFound, err)
		}

		for _, c := range summary.Checks {
			_, err := resourceCheck.getCheck(t, byID(c.Check.GetID()))
			assert.Error(t, err)
		}

		for _, d := range summary.Dashboards {
			_, err := resourceCheck.getDashboard(t, bySafeID(d.ID))
			assertErrorCode(t, errors2.ENotFound, err)
		}

		for _, l := range summary.Labels {
			_, err := resourceCheck.getLabel(t, bySafeID(l.ID))
			assertErrorCode(t, errors2.ENotFound, err)
		}

		for _, e := range summary.NotificationEndpoints {
			_, err := resourceCheck.getEndpoint(t, byID(e.NotificationEndpoint.GetID()))
			assert.Error(t, err)
		}

		for _, r := range summary.NotificationRules {
			_, err := resourceCheck.getRule(t, bySafeID(r.ID))
			assert.Error(t, err)
		}

		for _, ta := range summary.Tasks {
			_, err := resourceCheck.getTask(t, bySafeID(ta.ID))
			assert.Error(t, err)
		}

		for _, te := range summary.TelegrafConfigs {
			_, err := resourceCheck.getTelegrafConfig(t, byID(te.TelegrafConfig.ID))
			assert.Error(t, err)
		}

		for _, v := range summary.Variables {
			_, err := resourceCheck.getVariable(t, bySafeID(v.ID))
			assertErrorCode(t, errors2.ENotFound, err)
		}
	}

	t.Run("managing pkg lifecycle with stacks", func(t *testing.T) {
		t.Run("list stacks", func(t *testing.T) {
			stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{})
			require.NoError(t, err)
			require.Empty(t, stacks)

			newStack1, cleanup1 := newStackFn(t, pkger.StackCreate{
				Name: "first stack",
			})
			defer cleanup1()

			newStack2, cleanup2 := newStackFn(t, pkger.StackCreate{
				Name: "second stack",
			})
			defer cleanup2()

			containsStack := func(t *testing.T, haystack []pkger.Stack, needle pkger.Stack) {
				t.Helper()
				for _, hay := range haystack {
					if hay.ID == needle.ID {
						return
					}
				}
				require.FailNowf(t, "did not find expected stack", "got: %+v", needle)
			}

			t.Run("returns all stacks when no filter args provided", func(t *testing.T) {
				stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{})
				require.NoError(t, err)

				containsStack(t, stacks, newStack1)
				containsStack(t, stacks, newStack2)
			})

			t.Run("filters stacks by ID filter", func(t *testing.T) {
				stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{
					StackIDs: []platform.ID{newStack1.ID},
				})
				require.NoError(t, err)
				require.Len(t, stacks, 1)
				containsStack(t, stacks, newStack1)
			})

			t.Run("filter stacks by names", func(t *testing.T) {
				stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{
					Names: []string{newStack2.LatestEvent().Name},
				})
				require.NoError(t, err)
				require.Len(t, stacks, 1)
				containsStack(t, stacks, newStack2)
			})
		})

		t.Run("creating a stack", func(t *testing.T) {
			_, cleanup := newStackFn(t, pkger.StackCreate{
				OrgID:        l.Org.ID,
				Name:         "first stack",
				Description:  "desc",
				TemplateURLs: []string{"http://example.com"},
			})
			cleanup()
		})

		t.Run("uninstall a stack", func(t *testing.T) {
			t.Run("should remove all resources associated with it", func(t *testing.T) {
				newStack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				newEndpointPkgName := "non-existent-endpoint"
				allResourcesPkg := newTemplate(
					newBucketObject("non-existent-bucket", "", ""),
					newCheckDeadmanObject(t, "non-existent-check", "", time.Minute),
					newDashObject("non-existent-dash", "", ""),
					newEndpointHTTP(newEndpointPkgName, "", ""),
					newLabelObject("non-existent-label", "", "", ""),
					newRuleObject(t, "non-existent-rule", "", newEndpointPkgName, ""),
					newTaskObject("non-existent-task", "", ""),
					newTelegrafObject("non-existent-tele", "", ""),
					newVariableObject("non-existent-var", "", ""),
				)

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(allResourcesPkg),
					pkger.ApplyWithStackID(newStack.ID),
				)
				require.NoError(t, err)

				sum := impact.Summary

				require.Len(t, sum.Buckets, 1)
				assert.NotZero(t, sum.Buckets[0].ID)
				require.Len(t, sum.Checks, 1)
				assert.NotZero(t, sum.Checks[0].Check.GetID())
				require.Len(t, sum.Dashboards, 1)
				assert.NotZero(t, sum.Dashboards[0].ID)
				require.Len(t, sum.Labels, 1)
				assert.NotZero(t, sum.Labels[0].ID)
				require.Len(t, sum.NotificationEndpoints, 1)
				assert.NotZero(t, sum.NotificationEndpoints[0].NotificationEndpoint.GetID())
				require.Len(t, sum.NotificationRules, 1)
				assert.NotZero(t, sum.NotificationRules[0].ID)
				require.Len(t, sum.Tasks, 1)
				assert.NotZero(t, sum.Tasks[0].ID)
				require.Len(t, sum.TelegrafConfigs, 1)
				assert.NotZero(t, sum.TelegrafConfigs[0].TelegrafConfig.ID)
				require.Len(t, sum.Variables, 1)
				assert.NotZero(t, sum.Variables[0].ID)

				_, err = svc.UninstallStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{
					OrgID:   l.Org.ID,
					UserID:  l.User.ID,
					StackID: newStack.ID,
				})
				require.NoError(t, err)

				matchingStack, err := svc.ReadStack(ctx, newStack.ID)
				require.NoError(t, err)

				ev := matchingStack.LatestEvent()
				assert.Equal(t, pkger.StackEventUninstalled, ev.EventType)
				assert.Empty(t, ev.Resources)

				validateAllResourcesRemoved(t, sum)
			})
		})

		t.Run("delete a stack", func(t *testing.T) {
			t.Run("should delete the stack and all resources associated with it", func(t *testing.T) {
				newStack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				newEndpointPkgName := "non-existent-endpoint"
				allResourcesPkg := newTemplate(
					newBucketObject("non-existent-bucket", "", ""),
					newCheckDeadmanObject(t, "non-existent-check", "", time.Minute),
					newDashObject("non-existent-dash", "", ""),
					newEndpointHTTP(newEndpointPkgName, "", ""),
					newLabelObject("non-existent-label", "", "", ""),
					newRuleObject(t, "non-existent-rule", "", newEndpointPkgName, ""),
					newTaskObject("non-existent-task", "", ""),
					newTelegrafObject("non-existent-tele", "", ""),
					newVariableObject("non-existent-var", "", ""),
				)

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(allResourcesPkg),
					pkger.ApplyWithStackID(newStack.ID),
				)
				require.NoError(t, err)

				sum := impact.Summary

				require.Len(t, sum.Buckets, 1)
				assert.NotZero(t, sum.Buckets[0].ID)
				require.Len(t, sum.Checks, 1)
				assert.NotZero(t, sum.Checks[0].Check.GetID())
				require.Len(t, sum.Dashboards, 1)
				assert.NotZero(t, sum.Dashboards[0].ID)
				require.Len(t, sum.Labels, 1)
				assert.NotZero(t, sum.Labels[0].ID)
				require.Len(t, sum.NotificationEndpoints, 1)
				assert.NotZero(t, sum.NotificationEndpoints[0].NotificationEndpoint.GetID())
				require.Len(t, sum.NotificationRules, 1)
				assert.NotZero(t, sum.NotificationRules[0].ID)
				require.Len(t, sum.Tasks, 1)
				assert.NotZero(t, sum.Tasks[0].ID)
				require.Len(t, sum.TelegrafConfigs, 1)
				assert.NotZero(t, sum.TelegrafConfigs[0].TelegrafConfig.ID)
				require.Len(t, sum.Variables, 1)
				assert.NotZero(t, sum.Variables[0].ID)

				err = svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{
					OrgID:   l.Org.ID,
					UserID:  l.User.ID,
					StackID: newStack.ID,
				})
				require.NoError(t, err)

				validateAllResourcesRemoved(t, sum)
			})

			t.Run("that has already been deleted should be successful", func(t *testing.T) {
				newStack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				err := svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{
					OrgID:   l.Org.ID,
					UserID:  l.User.ID,
					StackID: newStack.ID,
				})
				require.NoError(t, err)

				// delete same stack
				err = svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{
					OrgID:   l.Org.ID,
					UserID:  l.User.ID,
					StackID: newStack.ID,
				})
				require.NoError(t, err)
			})

			t.Run("that doesn't exist should be successful", func(t *testing.T) {
				// delete stack that doesn't exist
				err := svc.DeleteStack(ctx, struct{ OrgID, UserID, StackID platform.ID }{
					OrgID:   l.Org.ID,
					UserID:  l.User.ID,
					StackID: 9000,
				})
				require.NoError(t, err)
			})
		})

		t.Run("read a stack", func(t *testing.T) {
			newStack1, cleanup1 := newStackFn(t, pkger.StackCreate{
				Name: "first stack",
			})
			defer cleanup1()

			newStack2, cleanup2 := newStackFn(t, pkger.StackCreate{
				Name: "second stack",
			})
			defer cleanup2()

			actual, err := svc.ReadStack(ctx, newStack1.ID)
			require.NoError(t, err)
			assert.Equal(t, newStack1, actual)

			actual, err = svc.ReadStack(ctx, newStack2.ID)
			require.NoError(t, err)
			assert.Equal(t, newStack2, actual)

			_, err = svc.ReadStack(ctx, platform.ID(9000))
			require.Equal(t, errors2.ENotFound, errors2.ErrorCode(err))
		})

		t.Run("updating a stack", func(t *testing.T) {
			t.Run("bootstrapped updates successfully", func(t *testing.T) {
				stack, cleanup := newStackFn(t, pkger.StackCreate{
					OrgID:        l.Org.ID,
					Name:         "first name",
					Description:  "first desc",
					TemplateURLs: []string{},
				})
				defer cleanup()

				// Wait for time.Now() to move ahead of the stack's latest update time.
				// We do this so that on Windows (where time.Now() is updated relatively slowly)
				// it doesn't appear that the stack was updated at the same time it was created.
				for now := time.Now(); now.Equal(stack.LatestEvent().UpdatedAt); now = time.Now() {
					time.Sleep(time.Millisecond)
				}

				assertStack := func(t *testing.T, st pkger.Stack) {
					t.Helper()
					assert.Equal(t, stack.ID, st.ID)
					ev := st.LatestEvent()
					assert.Equal(t, "2nd name", ev.Name)
					assert.Equal(t, "2nd desc", ev.Description)
					assert.Equal(t, []string{"http://example.com"}, ev.TemplateURLs)
					resources := []pkger.StackResource{
						{
							APIVersion: pkger.APIVersion,
							ID:         1,
							Kind:       pkger.KindBucket,
							MetaName:   "bucket-meta",
						},
					}
					assert.Equal(t, resources, ev.Resources)
					assert.True(t, ev.UpdatedAt.After(stack.LatestEvent().UpdatedAt))
				}

				updStack, err := svc.UpdateStack(ctx, pkger.StackUpdate{
					ID:           stack.ID,
					Name:         strPtr("2nd name"),
					Description:  strPtr("2nd desc"),
					TemplateURLs: []string{"http://example.com"},
					AdditionalResources: []pkger.StackAdditionalResource{
						{
							APIVersion: pkger.APIVersion,
							ID:         1,
							Kind:       pkger.KindBucket,
							MetaName:   "bucket-meta",
						},
					},
				})
				require.NoError(t, err)
				assertStack(t, updStack)

				readStack, err := svc.ReadStack(ctx, stack.ID)
				require.NoError(t, err)
				assertStack(t, readStack)
			})

			t.Run("associated with installed template returns valid resources", func(t *testing.T) {
				tmpl := newTemplate(newLabelObject("label-1", "", "", ""))
				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(tmpl))
				require.NoError(t, err)
				defer deleteStackFn(t, impact.StackID)

				stack, err := svc.ReadStack(ctx, impact.StackID)
				require.NoError(t, err)

				assertStack := func(t *testing.T, st pkger.Stack) {
					t.Helper()
					assert.Equal(t, stack.ID, st.ID)
					ev := st.LatestEvent()
					assert.Equal(t, "2nd name", ev.Name)
					resources := []pkger.StackResource{{
						APIVersion: pkger.APIVersion,
						ID:         1,
						Kind:       pkger.KindBucket,
						MetaName:   "bucket-meta",
					}}
					resources = append(resources, stack.LatestEvent().Resources...)
					assert.Equal(t, resources, ev.Resources)
				}

				updStack, err := svc.UpdateStack(ctx, pkger.StackUpdate{
					ID:   stack.ID,
					Name: strPtr("2nd name"),
					AdditionalResources: []pkger.StackAdditionalResource{
						{
							APIVersion: pkger.APIVersion,
							ID:         1,
							Kind:       pkger.KindBucket,
							MetaName:   "bucket-meta",
						},
					},
				})
				require.NoError(t, err)
				assertStack(t, updStack)

				readStack, err := svc.ReadStack(ctx, stack.ID)
				require.NoError(t, err)
				assertStack(t, readStack)
			})
		})

		t.Run("apply with only a stackID succeeds when stack has URLs", func(t *testing.T) {
			if runtime.GOOS == "windows" {
				t.Skip("https://github.com/influxdata/influxdb/issues/22474")
			}

			svr := httptest.NewServer(nethttp.HandlerFunc(func(w nethttp.ResponseWriter, r *nethttp.Request) {
				pkg := newTemplate(newBucketObject("bucket-0", "", ""))
				b, err := pkg.Encode(pkger.EncodingJSON)
				if err != nil {
					w.WriteHeader(nethttp.StatusInternalServerError)
					return
				}
				w.Write(b)
			}))
			defer svr.Close()

			f, err := os.CreateTemp("", "pkg.yml")
			require.NoError(t, err)
			defer f.Close()

			pkg := newTemplate(newBucketObject("bucket-1", "", ""))
			b, err := pkg.Encode(pkger.EncodingYAML)
			require.NoError(t, err)
			f.Write(b)
			require.NoError(t, f.Close())

			expectedURLs := []string{
				// URL for http call
				svr.URL + "/pkg.json",
				// URL for file
				"file://" + f.Name(),
			}

			newStack, cleanup := newStackFn(t, pkger.StackCreate{
				TemplateURLs: expectedURLs,
			})
			defer cleanup()

			sumEquals := func(t *testing.T, impact pkger.ImpactSummary) {
				t.Helper()

				assert.Equal(t, expectedURLs, impact.Sources)

				sum := impact.Summary
				require.Len(t, sum.Buckets, 2)
				assert.Equal(t, "bucket-0", sum.Buckets[0].MetaName)
				assert.Equal(t, "bucket-0", sum.Buckets[0].Name)
				assert.Equal(t, "bucket-1", sum.Buckets[1].MetaName)
				assert.Equal(t, "bucket-1", sum.Buckets[1].Name)
			}

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(newStack.ID))
			require.NoError(t, err)
			sumEquals(t, impact)

			impact, err = svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithStackID(newStack.ID))
			require.NoError(t, err)
			sumEquals(t, impact)

			stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{
				StackIDs: []platform.ID{newStack.ID},
			})
			require.NoError(t, err)

			require.Len(t, stacks, 1)
			assert.Equal(t, expectedURLs, stacks[0].LatestEvent().Sources)
		})

		t.Run("apply a pkg with a stack and associations", func(t *testing.T) {
			testLabelMappingFn := func(t *testing.T, stackID platform.ID, pkg *pkger.Template, assertAssociatedLabelsFn func(pkger.Summary, []*influxdb.Label, influxdb.ResourceType)) pkger.Summary {
				t.Helper()

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(pkg),
					pkger.ApplyWithStackID(stackID),
				)
				require.NoError(t, err)

				assert.Equal(t, []string{"byte stream"}, impact.Sources)

				sum := impact.Summary

				require.Len(t, sum.Buckets, 1)
				assert.NotZero(t, sum.Buckets[0].ID)
				assert.Equal(t, "bucket", sum.Buckets[0].Name)

				require.Len(t, sum.Checks, 1)
				assert.NotZero(t, sum.Checks[0].Check.GetID())
				assert.Equal(t, "check-0", sum.Checks[0].Check.GetName())

				require.Len(t, sum.Dashboards, 1)
				assert.NotZero(t, sum.Dashboards[0].ID)
				assert.Equal(t, "dash-0", sum.Dashboards[0].Name)

				require.Len(t, sum.NotificationEndpoints, 1)
				assert.NotZero(t, sum.NotificationEndpoints[0].NotificationEndpoint.GetID())
				assert.Equal(t, "endpoint-0", sum.NotificationEndpoints[0].NotificationEndpoint.GetName())

				require.Len(t, sum.NotificationRules, 1)
				assert.NotZero(t, sum.NotificationRules[0].ID)
				assert.Equal(t, "rule-0", sum.NotificationRules[0].Name)

				require.Len(t, sum.Labels, 1)
				assert.NotZero(t, sum.Labels[0].ID)
				assert.Equal(t, "label 1", sum.Labels[0].Name)

				require.Len(t, sum.Tasks, 1)
				assert.NotZero(t, sum.Tasks[0].ID)
				assert.Equal(t, "task-0", sum.Tasks[0].Name)

				require.Len(t, sum.TelegrafConfigs, 1)
				assert.NotZero(t, sum.TelegrafConfigs[0].TelegrafConfig.ID)
				assert.Equal(t, "tele-0", sum.TelegrafConfigs[0].TelegrafConfig.Name)

				resources := []struct {
					resID        platform.ID
					resourceType influxdb.ResourceType
				}{
					{resID: platform.ID(sum.Buckets[0].ID), resourceType: influxdb.BucketsResourceType},
					{resID: sum.Checks[0].Check.GetID(), resourceType: influxdb.ChecksResourceType},
					{resID: platform.ID(sum.Dashboards[0].ID), resourceType: influxdb.DashboardsResourceType},
					{resID: sum.NotificationEndpoints[0].NotificationEndpoint.GetID(), resourceType: influxdb.NotificationEndpointResourceType},
					{resID: platform.ID(sum.NotificationRules[0].ID), resourceType: influxdb.NotificationRuleResourceType},
					{resID: platform.ID(sum.Tasks[0].ID), resourceType: influxdb.TasksResourceType},
					{resID: sum.TelegrafConfigs[0].TelegrafConfig.ID, resourceType: influxdb.TelegrafsResourceType},
					{resID: platform.ID(sum.Variables[0].ID), resourceType: influxdb.VariablesResourceType},
				}
				for _, res := range resources {
					mappedLabels, err := l.LabelService(t).FindResourceLabels(ctx, influxdb.LabelMappingFilter{
						ResourceID:   res.resID,
						ResourceType: res.resourceType,
					})
					require.NoError(t, err, "resource_type="+res.resourceType)
					assertAssociatedLabelsFn(sum, mappedLabels, res.resourceType)
				}

				return sum
			}

			newObjectsFn := func() []pkger.Object {
				return []pkger.Object{
					newBucketObject("bucket", "", ""),
					newCheckDeadmanObject(t, "check-0", "", time.Hour),
					newDashObject("dash-0", "", ""),
					newEndpointHTTP("endpoint-0", "", ""),
					newRuleObject(t, "rule-0", "", "endpoint-0", ""),
					newTaskObject("task-0", "", ""),
					newTelegrafObject("tele-0", "", ""),
					newVariableObject("var-0", "", ""),
				}
			}
			labelObj := newLabelObject("label-1", "label 1", "", "")

			testAssociationFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) {
				t.Helper()

				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer func() {
					if t.Failed() {
						// if test fails in setup, then we attempt to clean it up
						// so it doesn't pollute other tests
						cleanup()
					}
				}()

				pkgObjects := newObjectsFn()
				for _, obj := range pkgObjects {
					obj.AddAssociations(pkger.ObjectAssociation{
						Kind:     pkger.KindLabel,
						MetaName: labelObj.Name(),
					})
				}
				pkgObjects = append(pkgObjects, labelObj)

				pkg := newTemplate(pkgObjects...)

				sum := testLabelMappingFn(t, stack.ID, pkg, func(sum pkger.Summary, mappedLabels []*influxdb.Label, resType influxdb.ResourceType) {
					require.Len(t, mappedLabels, 1, "resource_type="+resType)
					assert.Equal(t, sum.Labels[0].ID, pkger.SafeID(mappedLabels[0].ID))
				})

				return sum, stack, cleanup
			}

			t.Run("should associate resources with labels", func(t *testing.T) {
				_, _, cleanup := testAssociationFn(t)
				cleanup()
			})

			t.Run("should rollback to previous state when errors in creation", func(t *testing.T) {
				sum, stack, cleanup := testAssociationFn(t)
				defer cleanup()

				logger := l.log.With(zap.String("service", "pkger"))
				var svc pkger.SVC = pkger.NewService(
					pkger.WithLogger(logger),
					pkger.WithBucketSVC(l.BucketService(t)),
					pkger.WithDashboardSVC(l.DashboardService(t)),
					pkger.WithCheckSVC(l.CheckService()),
					pkger.WithLabelSVC(&fakeLabelSVC{
						// can't use the LabelService HTTP client b/c it doesn't cover the
						// all the resources pkger supports... :sadpanda:
						LabelService:    l.LabelService(t),
						createKillCount: -1,
						deleteKillCount: 3,
					}),
					pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)),
					pkger.WithNotificationRuleSVC(l.NotificationRuleService(t)),
					pkger.WithStore(pkger.NewStoreKV(l.Launcher.kvStore)),
					pkger.WithTaskSVC(l.TaskServiceKV(t)),
					pkger.WithTelegrafSVC(l.TelegrafService(t)),
					pkger.WithVariableSVC(l.VariableService(t)),
				)
				svc = pkger.MWLogging(logger)(svc)

				pkg := newTemplate(append(newObjectsFn(), labelObj)...)
				_, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(pkg),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.Error(t, err)

				resources := []struct {
					resID        platform.ID
					resourceType influxdb.ResourceType
				}{
					{resID: platform.ID(sum.Buckets[0].ID), resourceType: influxdb.BucketsResourceType},
					{resID: sum.Checks[0].Check.GetID(), resourceType: influxdb.ChecksResourceType},
					{resID: platform.ID(sum.Dashboards[0].ID), resourceType: influxdb.DashboardsResourceType},
					{resID: sum.NotificationEndpoints[0].NotificationEndpoint.GetID(), resourceType: influxdb.NotificationEndpointResourceType},
					{resID: platform.ID(sum.NotificationRules[0].ID), resourceType: influxdb.NotificationRuleResourceType},
					{resID: platform.ID(sum.Tasks[0].ID), resourceType: influxdb.TasksResourceType},
					{resID: sum.TelegrafConfigs[0].TelegrafConfig.ID, resourceType: influxdb.TelegrafsResourceType},
					{resID: platform.ID(sum.Variables[0].ID), resourceType: influxdb.VariablesResourceType},
				}
				for _, res := range resources {
					mappedLabels, err := l.LabelService(t).FindResourceLabels(ctx, influxdb.LabelMappingFilter{
						ResourceID:   res.resID,
						ResourceType: res.resourceType,
					})
					require.NoError(t, err)

					assert.Len(t, mappedLabels, 1, res.resourceType)
					if len(mappedLabels) == 1 {
						assert.Equal(t, sum.Labels[0].ID, pkger.SafeID(mappedLabels[0].ID))
					}
				}
			})

			t.Run("should unassociate resources from label when removing association in pkg", func(t *testing.T) {
				_, stack, cleanup := testAssociationFn(t)
				defer cleanup()

				objects := newObjectsFn()
				pkg := newTemplate(append(objects, labelObj)...)

				testLabelMappingFn(t, stack.ID, pkg, func(sum pkger.Summary, mappedLabels []*influxdb.Label, resType influxdb.ResourceType) {
					assert.Empty(t, mappedLabels, "res_type="+resType)
				})
			})
		})

		t.Run("apply a pkg with a stack and all resources", func(t *testing.T) {
			testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) {
				t.Helper()

				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer func() {
					if t.Failed() {
						cleanup()
					}
				}()

				var (
					initialBucketPkgName   = "rucketeer-1"
					initialCheckPkgName    = "checkers"
					initialDashPkgName     = "dash-of-salt"
					initialEndpointPkgName = "endzo"
					initialLabelPkgName    = "labelino"
					initialRulePkgName     = "oh-doyle-rules"
					initialTaskPkgName     = "tap"
					initialTelegrafPkgName = "teletype"
					initialVariablePkgName = "laces-out-dan"
				)
				initialPkg := newTemplate(
					newBucketObject(initialBucketPkgName, "display name", "init desc"),
					newCheckDeadmanObject(t, initialCheckPkgName, "check_0", time.Minute),
					newDashObject(initialDashPkgName, "dash_0", "init desc"),
					newEndpointHTTP(initialEndpointPkgName, "endpoint_0", "init desc"),
					newLabelObject(initialLabelPkgName, "label 1", "init desc", "#222eee"),
					newRuleObject(t, initialRulePkgName, "rule_0", initialEndpointPkgName, "init desc"),
					newTaskObject(initialTaskPkgName, "task_0", "init desc"),
					newTelegrafObject(initialTelegrafPkgName, "tele_0", "init desc"),
					newVariableObject(initialVariablePkgName, "var char", "init desc"),
				)

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(initialPkg),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.NoError(t, err)

				summary := impact.Summary

				require.Len(t, summary.Buckets, 1)
				assert.NotZero(t, summary.Buckets[0].ID)
				assert.Equal(t, "display name", summary.Buckets[0].Name)
				assert.Equal(t, "init desc", summary.Buckets[0].Description)

				require.Len(t, summary.Checks, 1)
				assert.NotZero(t, summary.Checks[0].Check.GetID())
				assert.Equal(t, "check_0", summary.Checks[0].Check.GetName())

				require.Len(t, summary.Dashboards, 1)
				assert.NotZero(t, summary.Dashboards[0].ID)
				assert.Equal(t, "dash_0", summary.Dashboards[0].Name)

				require.Len(t, summary.NotificationEndpoints, 1)
				assert.NotZero(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID())
				assert.Equal(t, "endpoint_0", summary.NotificationEndpoints[0].NotificationEndpoint.GetName())

				require.Len(t, summary.Labels, 1)
				assert.NotZero(t, summary.Labels[0].ID)
				assert.Equal(t, "label 1", summary.Labels[0].Name)
				assert.Equal(t, "init desc", summary.Labels[0].Properties.Description)
				assert.Equal(t, "#222eee", summary.Labels[0].Properties.Color)

				require.Len(t, summary.NotificationRules, 1)
				assert.NotZero(t, summary.NotificationRules[0].ID)
				assert.Equal(t, "rule_0", summary.NotificationRules[0].Name)
				assert.Equal(t, initialEndpointPkgName, summary.NotificationRules[0].EndpointMetaName)
				assert.Equal(t, "init desc", summary.NotificationRules[0].Description)

				require.Len(t, summary.Tasks, 1)
				assert.NotZero(t, summary.Tasks[0].ID)
				assert.Equal(t, "task_0", summary.Tasks[0].Name)
				assert.Equal(t, "init desc", summary.Tasks[0].Description)

				require.Len(t, summary.TelegrafConfigs, 1)
				assert.NotZero(t, summary.TelegrafConfigs[0].TelegrafConfig.ID)
				assert.Equal(t, "tele_0", summary.TelegrafConfigs[0].TelegrafConfig.Name)
				assert.Equal(t, "init desc", summary.TelegrafConfigs[0].TelegrafConfig.Description)

				require.Len(t, summary.Variables, 1)
				assert.NotZero(t, summary.Variables[0].ID)
				assert.Equal(t, "var char", summary.Variables[0].Name)
				assert.Equal(t, "init desc", summary.Variables[0].Description)

				t.Log("\tverify changes reflected in platform")
				{
					actualBkt := resourceCheck.mustGetBucket(t, byName("display name"))
					assert.Equal(t, summary.Buckets[0].ID, pkger.SafeID(actualBkt.ID))

					actualCheck := resourceCheck.mustGetCheck(t, byName("check_0"))
					assert.Equal(t, summary.Checks[0].Check.GetID(), actualCheck.GetID())

					actualDash := resourceCheck.mustGetDashboard(t, byName("dash_0"))
					assert.Equal(t, summary.Dashboards[0].ID, pkger.SafeID(actualDash.ID))

					actualEndpint := resourceCheck.mustGetEndpoint(t, byName("endpoint_0"))
					assert.Equal(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID(), actualEndpint.GetID())

					actualLabel := resourceCheck.mustGetLabel(t, byName("label 1"))
					assert.Equal(t, summary.Labels[0].ID, pkger.SafeID(actualLabel.ID))

					actualRule := resourceCheck.mustGetRule(t, byName("rule_0"))
					assert.Equal(t, summary.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID()))

					actualTask := resourceCheck.mustGetTask(t, byName("task_0"))
					assert.Equal(t, summary.Tasks[0].ID, pkger.SafeID(actualTask.ID))

					actualTele := resourceCheck.mustGetTelegrafConfig(t, byName("tele_0"))
					assert.Equal(t, summary.TelegrafConfigs[0].TelegrafConfig.ID, actualTele.ID)

					actualVar := resourceCheck.mustGetVariable(t, byName("var char"))
					assert.Equal(t, summary.Variables[0].ID, pkger.SafeID(actualVar.ID))
				}

				return summary, stack, cleanup
			}

			t.Run("apply pkg with stack id", func(t *testing.T) {
				_, _, cleanup := testStackApplyFn(t)
				cleanup()
			})

			t.Run("apply pkg with stack id where resources change", func(t *testing.T) {
				initialSum, stack, cleanup := testStackApplyFn(t)
				defer cleanup()

				var (
					updateBucketName   = "new bucket"
					updateCheckName    = "new check"
					updateDashName     = "new dash"
					updateEndpointName = "new endpoint"
					updateLabelName    = "new label"
					updateRuleName     = "new rule"
					updateTaskName     = "new task"
					updateTelegrafName = "new telegraf"
					updateVariableName = "new variable"
				)
				updatedPkg := newTemplate(
					newBucketObject(initialSum.Buckets[0].MetaName, updateBucketName, ""),
					newCheckDeadmanObject(t, initialSum.Checks[0].MetaName, updateCheckName, time.Hour),
					newDashObject(initialSum.Dashboards[0].MetaName, updateDashName, ""),
					newEndpointHTTP(initialSum.NotificationEndpoints[0].MetaName, updateEndpointName, ""),
					newLabelObject(initialSum.Labels[0].MetaName, updateLabelName, "", ""),
					newRuleObject(
						t,
						initialSum.NotificationRules[0].MetaName,
						updateRuleName,
						initialSum.NotificationEndpoints[0].MetaName,
						"",
					),
					newTaskObject(initialSum.Tasks[0].MetaName, updateTaskName, ""),
					newTelegrafObject(initialSum.TelegrafConfigs[0].MetaName, updateTelegrafName, ""),
					newVariableObject(initialSum.Variables[0].MetaName, updateVariableName, ""),
				)
				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(updatedPkg),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.NoError(t, err)

				sum := impact.Summary

				require.Len(t, sum.Buckets, 1)
				assert.Equal(t, initialSum.Buckets[0].ID, sum.Buckets[0].ID)
				assert.Equal(t, updateBucketName, sum.Buckets[0].Name)

				require.Len(t, sum.Checks, 1)
				assert.Equal(t, initialSum.Checks[0].Check.GetID(), sum.Checks[0].Check.GetID())
				assert.Equal(t, updateCheckName, sum.Checks[0].Check.GetName())

				require.Len(t, sum.Dashboards, 1)
				assert.Equal(t, initialSum.Dashboards[0].ID, sum.Dashboards[0].ID)
				assert.Equal(t, updateDashName, sum.Dashboards[0].Name)

				require.Len(t, sum.NotificationEndpoints, 1)
				sumEndpoint := sum.NotificationEndpoints[0].NotificationEndpoint
				assert.Equal(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID(), sumEndpoint.GetID())
				assert.Equal(t, updateEndpointName, sumEndpoint.GetName())

				require.Len(t, sum.NotificationRules, 1)
				sumRule := sum.NotificationRules[0]
				assert.Equal(t, initialSum.NotificationRules[0].ID, sumRule.ID)
				assert.Equal(t, updateRuleName, sumRule.Name)

				require.Len(t, sum.Labels, 1)
				assert.Equal(t, initialSum.Labels[0].ID, sum.Labels[0].ID)
				assert.Equal(t, updateLabelName, sum.Labels[0].Name)

				require.Len(t, sum.Tasks, 1)
				assert.Equal(t, initialSum.Tasks[0].ID, sum.Tasks[0].ID)
				assert.Equal(t, updateTaskName, sum.Tasks[0].Name)

				require.Len(t, sum.TelegrafConfigs, 1)
				updatedTele := sum.TelegrafConfigs[0].TelegrafConfig
				assert.Equal(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, updatedTele.ID)
				assert.Equal(t, updateTelegrafName, updatedTele.Name)

				require.Len(t, sum.Variables, 1)
				assert.Equal(t, initialSum.Variables[0].ID, sum.Variables[0].ID)
				assert.Equal(t, updateVariableName, sum.Variables[0].Name)

				t.Log("\tverify changes reflected in platform")
				{
					actualBkt := resourceCheck.mustGetBucket(t, byName(updateBucketName))
					require.Equal(t, initialSum.Buckets[0].ID, pkger.SafeID(actualBkt.ID))

					actualCheck := resourceCheck.mustGetCheck(t, byName(updateCheckName))
					require.Equal(t, initialSum.Checks[0].Check.GetID(), actualCheck.GetID())

					actualDash := resourceCheck.mustGetDashboard(t, byName(updateDashName))
					require.Equal(t, initialSum.Dashboards[0].ID, pkger.SafeID(actualDash.ID))

					actualEndpoint := resourceCheck.mustGetEndpoint(t, byName(updateEndpointName))
					assert.Equal(t, sumEndpoint.GetID(), actualEndpoint.GetID())

					actualLabel := resourceCheck.mustGetLabel(t, byName(updateLabelName))
					require.Equal(t, initialSum.Labels[0].ID, pkger.SafeID(actualLabel.ID))

					actualRule := resourceCheck.mustGetRule(t, byName(updateRuleName))
					require.Equal(t, initialSum.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID()))

					actualTask := resourceCheck.mustGetTask(t, byName(updateTaskName))
					require.Equal(t, initialSum.Tasks[0].ID, pkger.SafeID(actualTask.ID))

					actualTelegraf := resourceCheck.mustGetTelegrafConfig(t, byName(updateTelegrafName))
					require.Equal(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, actualTelegraf.ID)

					actualVar := resourceCheck.mustGetVariable(t, byName(updateVariableName))
					assert.Equal(t, sum.Variables[0].ID, pkger.SafeID(actualVar.ID))
				}
			})

			t.Run("an error during application roles back resources to previous state", func(t *testing.T) {
				initialSum, stack, cleanup := testStackApplyFn(t)
				defer cleanup()

				logger := l.log.With(zap.String("service", "pkger"))
				var svc pkger.SVC = pkger.NewService(
					pkger.WithLogger(logger),
					pkger.WithBucketSVC(l.BucketService(t)),
					pkger.WithDashboardSVC(l.DashboardService(t)),
					pkger.WithCheckSVC(l.CheckService()),
					pkger.WithLabelSVC(l.LabelService(t)),
					pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)),
					pkger.WithNotificationRuleSVC(&fakeRuleStore{
						NotificationRuleStore: l.NotificationRuleService(t),
						createKillCount:       2,
					}),
					pkger.WithStore(pkger.NewStoreKV(l.Launcher.kvStore)),
					pkger.WithTaskSVC(l.TaskServiceKV(t)),
					pkger.WithTelegrafSVC(l.TelegrafService(t)),
					pkger.WithVariableSVC(l.VariableService(t)),
				)
				svc = pkger.MWLogging(logger)(svc)

				endpointPkgName := "z-endpoint-rolls-back"

				pkgWithDelete := newTemplate(
					newBucketObject("z-roll-me-back", "", ""),
					newBucketObject("z-rolls-back-too", "", ""),
					newDashObject("z-rolls-dash", "", ""),
					newLabelObject("z-label-roller", "", "", ""),
					newCheckDeadmanObject(t, "z-check", "", time.Hour),
					newEndpointHTTP(endpointPkgName, "", ""),
					newRuleObject(t, "z-rules-back", "", endpointPkgName, ""),
					newRuleObject(t, "z-rules-back-2", "", endpointPkgName, ""),
					newRuleObject(t, "z-rules-back-3", "", endpointPkgName, ""),
					newTaskObject("z-task-rolls-back", "", ""),
					newTelegrafObject("z-telegraf-rolls-back", "", ""),
					newVariableObject("z-var-rolls-back", "", ""),
				)
				_, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(pkgWithDelete),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.Error(t, err)

				t.Log("validate all resources are rolled back")
				{
					actualBkt := resourceCheck.mustGetBucket(t, byName(initialSum.Buckets[0].Name))
					assert.NotEqual(t, initialSum.Buckets[0].ID, pkger.SafeID(actualBkt.ID))

					actualCheck := resourceCheck.mustGetCheck(t, byName(initialSum.Checks[0].Check.GetName()))
					assert.NotEqual(t, initialSum.Checks[0].Check.GetID(), actualCheck.GetID())

					actualDash := resourceCheck.mustGetDashboard(t, byName(initialSum.Dashboards[0].Name))
					assert.NotEqual(t, initialSum.Dashboards[0].ID, pkger.SafeID(actualDash.ID))

					actualEndpoint := resourceCheck.mustGetEndpoint(t, byName(initialSum.NotificationEndpoints[0].NotificationEndpoint.GetName()))
					assert.NotEqual(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID(), actualEndpoint.GetID())

					actualRule := resourceCheck.mustGetRule(t, byName(initialSum.NotificationRules[0].Name))
					assert.NotEqual(t, initialSum.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID()))

					actualLabel := resourceCheck.mustGetLabel(t, byName(initialSum.Labels[0].Name))
					assert.NotEqual(t, initialSum.Labels[0].ID, pkger.SafeID(actualLabel.ID))

					actualTask := resourceCheck.mustGetTask(t, byName(initialSum.Tasks[0].Name))
					assert.NotEqual(t, initialSum.Tasks[0].ID, pkger.SafeID(actualTask.ID))

					actualTelegraf := resourceCheck.mustGetTelegrafConfig(t, byName(initialSum.TelegrafConfigs[0].TelegrafConfig.Name))
					assert.NotEqual(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, actualTelegraf.ID)

					actualVariable := resourceCheck.mustGetVariable(t, byName(initialSum.Variables[0].Name))
					assert.NotEqual(t, initialSum.Variables[0].ID, pkger.SafeID(actualVariable.ID))
				}

				t.Log("validate all changes do not persist")
				{
					for _, name := range []string{"z-roll-me-back", "z-rolls-back-too"} {
						_, err := resourceCheck.getBucket(t, byName(name))
						assert.Error(t, err)
					}

					for _, name := range []string{"z-rules-back", "z-rules-back-2", "z-rules-back-3"} {
						_, err = resourceCheck.getRule(t, byName(name))
						assert.Error(t, err)
					}

					_, err := resourceCheck.getCheck(t, byName("z-check"))
					assert.Error(t, err)

					_, err = resourceCheck.getDashboard(t, byName("z-rolls_dash"))
					assert.Error(t, err)

					_, err = resourceCheck.getEndpoint(t, byName("z-endpoint-rolls-back"))
					assert.Error(t, err)

					_, err = resourceCheck.getLabel(t, byName("z-label-roller"))
					assert.Error(t, err)

					_, err = resourceCheck.getTelegrafConfig(t, byName("z-telegraf-rolls-back"))
					assert.Error(t, err)

					_, err = resourceCheck.getVariable(t, byName("z-var-rolls-back"))
					assert.Error(t, err)
				}
			})

			t.Run("apply pkg with stack id where resources have been removed since last run", func(t *testing.T) {
				initialSum, stack, cleanup := testStackApplyFn(t)
				defer cleanup()

				newEndpointPkgName := "non-existent-endpoint"
				allNewResourcesPkg := newTemplate(
					newBucketObject("non-existent-bucket", "", ""),
					newCheckDeadmanObject(t, "non-existent-check", "", time.Minute),
					newDashObject("non-existent-dash", "", ""),
					newEndpointHTTP(newEndpointPkgName, "", ""),
					newLabelObject("non-existent-label", "", "", ""),
					newRuleObject(t, "non-existent-rule", "", newEndpointPkgName, ""),
					newTaskObject("non-existent-task", "", ""),
					newTelegrafObject("non-existent-tele", "", ""),
					newVariableObject("non-existent-var", "", ""),
				)
				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(allNewResourcesPkg),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.NoError(t, err)

				sum := impact.Summary

				require.Len(t, sum.Buckets, 1)
				assert.NotEqual(t, initialSum.Buckets[0].ID, sum.Buckets[0].ID)
				assert.NotZero(t, sum.Buckets[0].ID)
				assert.Equal(t, "non-existent-bucket", sum.Buckets[0].Name)

				require.Len(t, sum.Checks, 1)
				assert.NotEqual(t, initialSum.Checks[0].Check.GetID(), sum.Checks[0].Check.GetID())
				assert.NotZero(t, sum.Checks[0].Check.GetID())
				assert.Equal(t, "non-existent-check", sum.Checks[0].Check.GetName())

				require.Len(t, sum.Dashboards, 1)
				assert.NotEqual(t, initialSum.Dashboards[0].ID, sum.Dashboards[0].ID)
				assert.NotZero(t, sum.Dashboards[0].ID)
				assert.Equal(t, "non-existent-dash", sum.Dashboards[0].Name)

				require.Len(t, sum.NotificationEndpoints, 1)
				sumEndpoint := sum.NotificationEndpoints[0].NotificationEndpoint
				assert.NotEqual(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID(), sumEndpoint.GetID())
				assert.NotZero(t, sumEndpoint.GetID())
				assert.Equal(t, newEndpointPkgName, sumEndpoint.GetName())

				require.Len(t, sum.NotificationRules, 1)
				sumRule := sum.NotificationRules[0]
				assert.NotEqual(t, initialSum.NotificationRules[0].ID, sumRule.ID)
				assert.NotZero(t, sumRule.ID)
				assert.Equal(t, "non-existent-rule", sumRule.Name)

				require.Len(t, sum.Labels, 1)
				assert.NotEqual(t, initialSum.Labels[0].ID, sum.Labels[0].ID)
				assert.NotZero(t, sum.Labels[0].ID)
				assert.Equal(t, "non-existent-label", sum.Labels[0].Name)

				require.Len(t, sum.Tasks, 1)
				assert.NotEqual(t, initialSum.Tasks[0].ID, sum.Tasks[0].ID)
				assert.NotZero(t, sum.Tasks[0].ID)
				assert.Equal(t, "non-existent-task", sum.Tasks[0].Name)

				require.Len(t, sum.TelegrafConfigs, 1)
				newTele := sum.TelegrafConfigs[0].TelegrafConfig
				assert.NotEqual(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID, newTele.ID)
				assert.NotZero(t, newTele.ID)
				assert.Equal(t, "non-existent-tele", newTele.Name)

				require.Len(t, sum.Variables, 1)
				assert.NotEqual(t, initialSum.Variables[0].ID, sum.Variables[0].ID)
				assert.NotZero(t, sum.Variables[0].ID)
				assert.Equal(t, "non-existent-var", sum.Variables[0].Name)

				t.Log("\tvalidate all resources are created")
				{
					bkt := resourceCheck.mustGetBucket(t, byName("non-existent-bucket"))
					assert.Equal(t, pkger.SafeID(bkt.ID), sum.Buckets[0].ID)

					chk := resourceCheck.mustGetCheck(t, byName("non-existent-check"))
					assert.Equal(t, chk.GetID(), sum.Checks[0].Check.GetID())

					endpoint := resourceCheck.mustGetEndpoint(t, byName(newEndpointPkgName))
					assert.Equal(t, endpoint.GetID(), sum.NotificationEndpoints[0].NotificationEndpoint.GetID())

					label := resourceCheck.mustGetLabel(t, byName("non-existent-label"))
					assert.Equal(t, pkger.SafeID(label.ID), sum.Labels[0].ID)

					actualRule := resourceCheck.mustGetRule(t, byName("non-existent-rule"))
					assert.Equal(t, pkger.SafeID(actualRule.GetID()), sum.NotificationRules[0].ID)

					task := resourceCheck.mustGetTask(t, byName("non-existent-task"))
					assert.Equal(t, pkger.SafeID(task.ID), sum.Tasks[0].ID)

					tele := resourceCheck.mustGetTelegrafConfig(t, byName("non-existent-tele"))
					assert.Equal(t, tele.ID, sum.TelegrafConfigs[0].TelegrafConfig.ID)

					variable := resourceCheck.mustGetVariable(t, byName("non-existent-var"))
					assert.Equal(t, pkger.SafeID(variable.ID), sum.Variables[0].ID)
				}

				t.Log("\tvalidate all previous resources are removed")
				{
					_, err = resourceCheck.getBucket(t, byName(initialSum.Buckets[0].Name))
					require.Error(t, err)

					_, err = resourceCheck.getCheck(t, byName(initialSum.Checks[0].Check.GetName()))
					require.Error(t, err)

					_, err = resourceCheck.getEndpoint(t, byName(initialSum.NotificationEndpoints[0].NotificationEndpoint.GetName()))
					require.Error(t, err)

					_, err = resourceCheck.getLabel(t, byName(initialSum.Labels[0].Name))
					require.Error(t, err)

					_, err = resourceCheck.getTask(t, byName(initialSum.Tasks[0].Name))
					require.Error(t, err)

					_, err = resourceCheck.getTelegrafConfig(t, byName(initialSum.TelegrafConfigs[0].TelegrafConfig.Name))
					require.Error(t, err)

					_, err = resourceCheck.getVariable(t, byName(initialSum.Variables[0].Name))
					require.Error(t, err)
				}
			})
		})

		t.Run("apply should handle cases where users have changed platform data", func(t *testing.T) {
			initializeStackPkg := func(t *testing.T, pkg *pkger.Template) (platform.ID, func(), pkger.Summary) {
				t.Helper()

				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer func() {
					if t.Failed() {
						cleanup()
					}
				}()

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(pkg),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.NoError(t, err)

				return stack.ID, cleanup, impact.Summary
			}

			testValidRemoval := func(t *testing.T, stackID platform.ID) {
				t.Helper()
				_, err := svc.Apply(
					ctx,
					l.Org.ID,
					l.User.ID,
					pkger.ApplyWithStackID(stackID),
				)
				require.NoError(t, err)
			}

			t.Run("when a user has deleted a variable that was previously created by a stack", func(t *testing.T) {
				testUserDeletedVariable := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialVarObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newVariableObject("var-1", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.Variables, 1)
					require.NotZero(t, initialSum.Variables[0].ID)
					resourceCheck.mustDeleteVariable(t, platform.ID(initialSum.Variables[0].ID))

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedVariable(t, func(t *testing.T, stackID platform.ID, initialVarObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialVarObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.Variables, 1)
						initVar, updateVar := initialSum.Variables[0], updateSum.Variables[0]
						assert.NotEqual(t, initVar.ID, updateVar.ID)
						initVar.ID, updateVar.ID = 0, 0
						assert.Equal(t, initVar, updateVar)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedVariable(t, func(t *testing.T, stackID platform.ID, initialVarObj pkger.Object, initialSum pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a bucket that was previously created by a stack", func(t *testing.T) {
				testUserDeletedBucket := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newBucketObject("bucket-1", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.Buckets, 1)
					require.NotZero(t, initialSum.Buckets[0].ID)
					resourceCheck.mustDeleteBucket(t, platform.ID(initialSum.Buckets[0].ID))

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedBucket(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.Buckets, 1)
						intial, updated := initialSum.Buckets[0], updateSum.Buckets[0]
						assert.NotEqual(t, intial.ID, updated.ID)
						intial.ID, updated.ID = 0, 0
						assert.Equal(t, intial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedBucket(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a check that was previously created by a stack", func(t *testing.T) {
				testUserDeletedCheck := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newCheckDeadmanObject(t, "check-1", "", time.Hour)
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.Checks, 1)
					require.NotZero(t, initialSum.Checks[0].Check.GetID())
					resourceCheck.mustDeleteCheck(t, initialSum.Checks[0].Check.GetID())

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedCheck(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.Checks, 1)
						intial, updated := initialSum.Checks[0].Check, updateSum.Checks[0].Check
						assert.NotEqual(t, intial.GetID(), updated.GetID())
						intial.SetID(0)
						updated.SetID(0)
						assert.Equal(t, intial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedCheck(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a dashboard that was previously created by a stack", func(t *testing.T) {
				testUserDeletedDashboard := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newDashObject("dash-1", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.Dashboards, 1)
					require.NotZero(t, initialSum.Dashboards[0].ID)
					resourceCheck.mustDeleteDashboard(t, platform.ID(initialSum.Dashboards[0].ID))

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedDashboard(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.Dashboards, 1)
						initial, updated := initialSum.Dashboards[0], updateSum.Dashboards[0]
						assert.NotEqual(t, initial.ID, updated.ID)
						initial.ID, updated.ID = 0, 0
						assert.Equal(t, initial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedDashboard(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a label that was previously created by a stack", func(t *testing.T) {
				testUserDeletedLabel := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newLabelObject("label-1", "", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.Labels, 1)
					require.NotZero(t, initialSum.Labels[0].ID)
					resourceCheck.mustDeleteLabel(t, platform.ID(initialSum.Labels[0].ID))

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedLabel(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.Labels, 1)
						initial, updated := initialSum.Labels[0], updateSum.Labels[0]
						assert.NotEqual(t, initial.ID, updated.ID, "label ID should be different")
						initial.ID, updated.ID = 0, 0
						assert.Equal(t, initial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedLabel(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a notification endpoint that was previously created by a stack", func(t *testing.T) {
				testUserDeletedEndpoint := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newEndpointHTTP("endpoint-1", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.NotificationEndpoints, 1)
					require.NotZero(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID())
					resourceCheck.mustDeleteEndpoint(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID())

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedEndpoint(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.NotificationEndpoints, 1)
						initial, updated := initialSum.NotificationEndpoints[0].NotificationEndpoint, updateSum.NotificationEndpoints[0].NotificationEndpoint
						assert.NotEqual(t, initial.GetID(), updated.GetID())
						initial.SetID(0)
						updated.SetID(0)
						assert.Equal(t, initial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedEndpoint(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a notification rule that was previously created by a stack", func(t *testing.T) {
				testUserDeletedRule := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObjects []pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					endpointObj := newEndpointHTTP("endpoint-1", "", "")
					ruleObj := newRuleObject(t, "rule-0", "", endpointObj.Name(), "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(endpointObj, ruleObj))
					defer cleanup()

					require.Len(t, initialSum.NotificationEndpoints, 1)
					require.NotZero(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID())
					require.Len(t, initialSum.NotificationRules, 1)
					require.NotZero(t, initialSum.NotificationRules[0].ID)
					resourceCheck.mustDeleteRule(t, platform.ID(initialSum.NotificationRules[0].ID))

					actionFn(t, stackID, []pkger.Object{ruleObj, endpointObj}, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedRule(t, func(t *testing.T, stackID platform.ID, initialObjects []pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObjects...)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.NotificationRules, 1)
						initial, updated := initialSum.NotificationRules[0], updateSum.NotificationRules[0]
						assert.NotEqual(t, initial.ID, updated.ID)
						initial.ID, updated.ID = 0, 0
						assert.Equal(t, initial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedRule(t, func(t *testing.T, stackID platform.ID, _ []pkger.Object, _ pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a task that was previously created by a stack", func(t *testing.T) {
				testUserDeletedTask := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newTaskObject("task-1", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.Tasks, 1)
					require.NotZero(t, initialSum.Tasks[0].ID)
					resourceCheck.mustDeleteTask(t, platform.ID(initialSum.Tasks[0].ID))

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedTask(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.Tasks, 1)
						initial, updated := initialSum.Tasks[0], updateSum.Tasks[0]
						assert.NotEqual(t, initial.ID, updated.ID)
						initial.ID, updated.ID = 0, 0
						assert.Equal(t, initial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedTask(t, func(t *testing.T, stackID platform.ID, _ pkger.Object, _ pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})

			t.Run("when a user has deleted a telegraf config that was previously created by a stack", func(t *testing.T) {
				testUserDeletedTelegraf := func(t *testing.T, actionFn func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
					t.Helper()

					obj := newTelegrafObject("tele-1", "", "")
					stackID, cleanup, initialSum := initializeStackPkg(t, newTemplate(obj))
					defer cleanup()

					require.Len(t, initialSum.TelegrafConfigs, 1)
					require.NotZero(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID)
					resourceCheck.mustDeleteTelegrafConfig(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID)

					actionFn(t, stackID, obj, initialSum)
				}

				t.Run("should create new resource when attempting to update", func(t *testing.T) {
					testUserDeletedTelegraf(t, func(t *testing.T, stackID platform.ID, initialObj pkger.Object, initialSum pkger.Summary) {
						pkg := newTemplate(initialObj)
						impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stackID))
						require.NoError(t, err)

						updateSum := impact.Summary

						require.Len(t, updateSum.TelegrafConfigs, 1)
						initial, updated := initialSum.TelegrafConfigs[0].TelegrafConfig, updateSum.TelegrafConfigs[0].TelegrafConfig
						assert.NotEqual(t, initial.ID, updated.ID)
						initial.ID, updated.ID = 0, 0
						assert.Equal(t, initial, updated)
					})
				})

				t.Run("should not error when attempting to remove", func(t *testing.T) {
					testUserDeletedTelegraf(t, func(t *testing.T, stackID platform.ID, _ pkger.Object, _ pkger.Summary) {
						testValidRemoval(t, stackID)
					})
				})
			})
		})

		t.Run("applying updates to existing variable should be successful", func(t *testing.T) {
			stack, cleanup := newStackFn(t, pkger.StackCreate{})
			defer cleanup()

			impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
				pkger.ApplyWithStackID(stack.ID),
				pkger.ApplyWithTemplate(newTemplate(newVariableObject("var", "", ""))),
			)
			require.NoError(t, err)

			vars := impact.Summary.Variables
			require.Len(t, vars, 1)
			v := resourceCheck.mustGetVariable(t, byID(platform.ID(vars[0].ID)))
			assert.Empty(t, v.Selected)

			impact, err = svc.Apply(ctx, l.Org.ID, l.User.ID,
				pkger.ApplyWithStackID(stack.ID),
				pkger.ApplyWithTemplate(newTemplate(newVariableObject("var", "", "", "selected"))),
			)
			require.NoError(t, err)

			vars = impact.Summary.Variables
			require.Len(t, vars, 1)
			v = resourceCheck.mustGetVariable(t, byID(platform.ID(vars[0].ID)))
			assert.Equal(t, []string{"selected"}, v.Selected)
		})

		t.Run("apply with actions", func(t *testing.T) {
			var (
				bucketPkgName   = "rucketeer-1"
				checkPkgName    = "checkers"
				dashPkgName     = "dash-of-salt"
				endpointPkgName = "endzo"
				labelPkgName    = "labelino"
				rulePkgName     = "oh-doyle-rules"
				taskPkgName     = "tap"
				telegrafPkgName = "teletype"
				variablePkgName = "laces-out-dan"
			)

			defaultPkgFn := func(*testing.T) *pkger.Template {
				return newTemplate(
					newBucketObject(bucketPkgName, "", ""),
					newCheckDeadmanObject(t, checkPkgName, "", time.Hour),
					newDashObject(dashPkgName, "", ""),
					newEndpointHTTP(endpointPkgName, "", ""),
					newLabelObject(labelPkgName, "", "", ""),
					newRuleObject(t, rulePkgName, "", endpointPkgName, ""),
					newTaskObject(taskPkgName, "", ""),
					newTelegrafObject(telegrafPkgName, "", ""),
					newVariableObject(variablePkgName, "", ""),
				)
			}

			tests := []struct {
				name      string
				pkgFn     func(t *testing.T) *pkger.Template
				applyOpts []pkger.ApplyOptFn
				assertFn  func(t *testing.T, impact pkger.ImpactSummary)
			}{
				{
					name:  "skip resource",
					pkgFn: defaultPkgFn,
					applyOpts: []pkger.ApplyOptFn{
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindBucket,
							MetaName: bucketPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindCheckDeadman,
							MetaName: checkPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindDashboard,
							MetaName: dashPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindNotificationEndpointHTTP,
							MetaName: endpointPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindLabel,
							MetaName: labelPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindNotificationRule,
							MetaName: rulePkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindTask,
							MetaName: taskPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindTelegraf,
							MetaName: telegrafPkgName,
						}),
						pkger.ApplyWithResourceSkip(pkger.ActionSkipResource{
							Kind:     pkger.KindVariable,
							MetaName: variablePkgName,
						}),
					},
					assertFn: func(t *testing.T, impact pkger.ImpactSummary) {
						summary := impact.Summary
						assert.Empty(t, summary.Buckets)
						assert.Empty(t, summary.Checks)
						assert.Empty(t, summary.Dashboards)
						assert.Empty(t, summary.NotificationEndpoints)
						assert.Empty(t, summary.Labels)
						assert.Empty(t, summary.NotificationRules, 0)
						assert.Empty(t, summary.Tasks)
						assert.Empty(t, summary.TelegrafConfigs)
						assert.Empty(t, summary.Variables)
					},
				},
				{
					name:  "skip kind",
					pkgFn: defaultPkgFn,
					applyOpts: []pkger.ApplyOptFn{
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindBucket,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindCheckDeadman,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindDashboard,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindNotificationEndpointHTTP,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindLabel,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindNotificationRule,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindTask,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindTelegraf,
						}),
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindVariable,
						}),
					},
					assertFn: func(t *testing.T, impact pkger.ImpactSummary) {
						summary := impact.Summary
						assert.Empty(t, summary.Buckets)
						assert.Empty(t, summary.Checks)
						assert.Empty(t, summary.Dashboards)
						assert.Empty(t, summary.NotificationEndpoints)
						assert.Empty(t, summary.Labels)
						assert.Empty(t, summary.NotificationRules, 0)
						assert.Empty(t, summary.Tasks)
						assert.Empty(t, summary.TelegrafConfigs)
						assert.Empty(t, summary.Variables)
					},
				},
				{
					name: "skip label and assoications should be dropped",
					pkgFn: func(t *testing.T) *pkger.Template {
						objs := []pkger.Object{
							newBucketObject(bucketPkgName, "", ""),
							newCheckDeadmanObject(t, checkPkgName, "", time.Hour),
							newDashObject(dashPkgName, "", ""),
							newEndpointHTTP(endpointPkgName, "", ""),
							newRuleObject(t, rulePkgName, "", endpointPkgName, ""),
							newTaskObject(taskPkgName, "", ""),
							newTelegrafObject(telegrafPkgName, "", ""),
							newVariableObject(variablePkgName, "", ""),
						}
						for _, obj := range objs {
							obj.AddAssociations(pkger.ObjectAssociation{
								Kind:     pkger.KindLabel,
								MetaName: labelPkgName,
							})
						}

						objs = append(objs, newLabelObject(labelPkgName, "", "", ""))

						return newTemplate(objs...)
					},
					applyOpts: []pkger.ApplyOptFn{
						pkger.ApplyWithKindSkip(pkger.ActionSkipKind{
							Kind: pkger.KindLabel,
						}),
					},
					assertFn: func(t *testing.T, impact pkger.ImpactSummary) {
						summary := impact.Summary
						assert.Empty(t, summary.Labels, 0)
						assert.Empty(t, summary.LabelMappings)

						assert.Len(t, summary.Buckets, 1)
						assert.Empty(t, summary.Buckets[0].LabelAssociations)
						assert.Len(t, summary.Checks, 1)
						assert.Empty(t, summary.Checks[0].LabelAssociations)
						assert.Len(t, summary.Dashboards, 1)
						assert.Empty(t, summary.Dashboards[0].LabelAssociations)
						assert.Len(t, summary.NotificationEndpoints, 1)
						assert.Empty(t, summary.NotificationEndpoints[0].LabelAssociations)
						assert.Len(t, summary.NotificationRules, 1)
						assert.Empty(t, summary.NotificationRules[0].LabelAssociations)
						assert.Len(t, summary.Tasks, 1)
						assert.Empty(t, summary.Tasks[0].LabelAssociations)
						assert.Len(t, summary.TelegrafConfigs, 1)
						assert.Empty(t, summary.TelegrafConfigs[0].LabelAssociations)
						assert.Len(t, summary.Variables, 1)
						assert.Empty(t, summary.Variables[0].LabelAssociations)
					},
				},
			}

			for _, tt := range tests {
				fn := func(t *testing.T) {
					stack, cleanup := newStackFn(t, pkger.StackCreate{})
					defer cleanup()

					impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
						append(
							tt.applyOpts,
							pkger.ApplyWithTemplate(tt.pkgFn(t)),
							pkger.ApplyWithStackID(stack.ID),
						)...,
					)
					require.NoError(t, err)

					tt.assertFn(t, impact)
				}

				t.Run(tt.name, fn)
			}
		})

		t.Run("exporting the existing state of stack resources as template", func(t *testing.T) {
			testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) {
				t.Helper()

				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer func() {
					if t.Failed() {
						cleanup()
					}
				}()

				var (
					initialBucketPkgName   = "rucketeer-1"
					initialCheckPkgName    = "checkers"
					initialDashPkgName     = "dash-of-salt"
					initialEndpointPkgName = "endzo"
					initialLabelPkgName    = "labelino"
					initialRulePkgName     = "oh-doyle-rules"
					initialTaskPkgName     = "tap"
					initialTelegrafPkgName = "teletype"
					initialVariablePkgName = "laces-out-dan"
				)

				labelObj := newLabelObject(initialLabelPkgName, "label 1", "init desc", "#222eee")
				setAssociation := func(o pkger.Object) pkger.Object {
					o.AddAssociations(pkger.ObjectAssociation{
						Kind:     pkger.KindLabel,
						MetaName: labelObj.Name(),
					})
					return o
				}

				initialPkg := newTemplate(
					setAssociation(newBucketObject(initialBucketPkgName, "display name", "init desc")),
					setAssociation(newCheckDeadmanObject(t, initialCheckPkgName, "check_0", time.Minute)),
					setAssociation(newDashObject(initialDashPkgName, "dash_0", "init desc")),
					setAssociation(newEndpointHTTP(initialEndpointPkgName, "endpoint_0", "init desc")),
					labelObj,
					setAssociation(newRuleObject(t, initialRulePkgName, "rule_0", initialEndpointPkgName, "init desc")),
					setAssociation(newTaskObject(initialTaskPkgName, "task_0", "init desc")),
					setAssociation(newTelegrafObject(initialTelegrafPkgName, "tele_0", "init desc")),
					setAssociation(newVariableObject(initialVariablePkgName, "var char", "init desc")),
				)

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithTemplate(initialPkg),
					pkger.ApplyWithStackID(stack.ID),
				)
				require.NoError(t, err)

				summary := impact.Summary

				hasAssociation := func(t *testing.T, actual []pkger.SummaryLabel) {
					t.Helper()
					require.Len(t, actual, 1, "unexpected number of label mappings")
					assert.Equal(t, actual[0].MetaName, labelObj.Name())
				}

				require.Len(t, summary.Buckets, 1)
				assert.NotZero(t, summary.Buckets[0].ID)
				assert.Equal(t, "display name", summary.Buckets[0].Name)
				assert.Equal(t, "init desc", summary.Buckets[0].Description)
				hasAssociation(t, summary.Buckets[0].LabelAssociations)

				require.Len(t, summary.Checks, 1)
				assert.NotZero(t, summary.Checks[0].Check.GetID())
				assert.Equal(t, "check_0", summary.Checks[0].Check.GetName())
				hasAssociation(t, summary.Checks[0].LabelAssociations)

				require.Len(t, summary.Dashboards, 1)
				assert.NotZero(t, summary.Dashboards[0].ID)
				assert.Equal(t, "dash_0", summary.Dashboards[0].Name)
				hasAssociation(t, summary.Dashboards[0].LabelAssociations)

				require.Len(t, summary.NotificationEndpoints, 1)
				assert.NotZero(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID())
				assert.Equal(t, "endpoint_0", summary.NotificationEndpoints[0].NotificationEndpoint.GetName())
				hasAssociation(t, summary.NotificationEndpoints[0].LabelAssociations)

				require.Len(t, summary.Labels, 1)
				assert.NotZero(t, summary.Labels[0].ID)
				assert.Equal(t, "label 1", summary.Labels[0].Name)
				assert.Equal(t, "init desc", summary.Labels[0].Properties.Description)
				assert.Equal(t, "#222eee", summary.Labels[0].Properties.Color)

				require.Len(t, summary.NotificationRules, 1)
				assert.NotZero(t, summary.NotificationRules[0].ID)
				assert.Equal(t, "rule_0", summary.NotificationRules[0].Name)
				assert.Equal(t, initialEndpointPkgName, summary.NotificationRules[0].EndpointMetaName)
				assert.Equal(t, "init desc", summary.NotificationRules[0].Description)
				hasAssociation(t, summary.NotificationRules[0].LabelAssociations)

				require.Len(t, summary.Tasks, 1)
				assert.NotZero(t, summary.Tasks[0].ID)
				assert.Equal(t, "task_0", summary.Tasks[0].Name)
				assert.Equal(t, "init desc", summary.Tasks[0].Description)
				hasAssociation(t, summary.Tasks[0].LabelAssociations)

				require.Len(t, summary.TelegrafConfigs, 1)
				assert.NotZero(t, summary.TelegrafConfigs[0].TelegrafConfig.ID)
				assert.Equal(t, "tele_0", summary.TelegrafConfigs[0].TelegrafConfig.Name)
				assert.Equal(t, "init desc", summary.TelegrafConfigs[0].TelegrafConfig.Description)
				hasAssociation(t, summary.TelegrafConfigs[0].LabelAssociations)

				require.Len(t, summary.Variables, 1)
				assert.NotZero(t, summary.Variables[0].ID)
				assert.Equal(t, "var char", summary.Variables[0].Name)
				assert.Equal(t, "init desc", summary.Variables[0].Description)
				hasAssociation(t, summary.Variables[0].LabelAssociations)

				// verify changes reflected in platform
				{
					actualBkt := resourceCheck.mustGetBucket(t, byName("display name"))
					assert.Equal(t, summary.Buckets[0].ID, pkger.SafeID(actualBkt.ID))

					actualCheck := resourceCheck.mustGetCheck(t, byName("check_0"))
					assert.Equal(t, summary.Checks[0].Check.GetID(), actualCheck.GetID())

					actualDash := resourceCheck.mustGetDashboard(t, byName("dash_0"))
					assert.Equal(t, summary.Dashboards[0].ID, pkger.SafeID(actualDash.ID))

					actualEndpint := resourceCheck.mustGetEndpoint(t, byName("endpoint_0"))
					assert.Equal(t, summary.NotificationEndpoints[0].NotificationEndpoint.GetID(), actualEndpint.GetID())

					actualLabel := resourceCheck.mustGetLabel(t, byName("label 1"))
					assert.Equal(t, summary.Labels[0].ID, pkger.SafeID(actualLabel.ID))

					actualRule := resourceCheck.mustGetRule(t, byName("rule_0"))
					assert.Equal(t, summary.NotificationRules[0].ID, pkger.SafeID(actualRule.GetID()))

					actualTask := resourceCheck.mustGetTask(t, byName("task_0"))
					assert.Equal(t, summary.Tasks[0].ID, pkger.SafeID(actualTask.ID))

					actualTele := resourceCheck.mustGetTelegrafConfig(t, byName("tele_0"))
					assert.Equal(t, summary.TelegrafConfigs[0].TelegrafConfig.ID, actualTele.ID)

					actualVar := resourceCheck.mustGetVariable(t, byName("var char"))
					assert.Equal(t, summary.Variables[0].ID, pkger.SafeID(actualVar.ID))
				}

				return summary, stack, cleanup
			}

			t.Run("should be return pkg matching source pkg when all resources are unchanged", func(t *testing.T) {
				initialSum, stack, cleanup := testStackApplyFn(t)
				defer cleanup()

				exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(stack.ID))
				require.NoError(t, err)

				hasAssociation := func(t *testing.T, actual []pkger.SummaryLabel) {
					t.Helper()
					assert.Len(t, actual, 1, "unexpected number of label mappings")
					if len(actual) != 1 {
						return
					}
					assert.Equal(t, actual[0].MetaName, initialSum.Labels[0].MetaName)
				}

				sum := exportedTemplate.Summary()

				require.Len(t, sum.Buckets, 1, "missing required buckets")
				assert.Equal(t, initialSum.Buckets[0].MetaName, sum.Buckets[0].MetaName)
				assert.Equal(t, initialSum.Buckets[0].Name, sum.Buckets[0].Name)
				hasAssociation(t, sum.Buckets[0].LabelAssociations)

				require.Len(t, sum.Checks, 1, "missing required checks")
				assert.Equal(t, initialSum.Checks[0].MetaName, sum.Checks[0].MetaName)
				assert.Equal(t, initialSum.Checks[0].Check.GetName(), sum.Checks[0].Check.GetName())
				hasAssociation(t, sum.Checks[0].LabelAssociations)

				require.Len(t, sum.Dashboards, 1, "missing required dashboards")
				assert.Equal(t, initialSum.Dashboards[0].MetaName, sum.Dashboards[0].MetaName)
				assert.Equal(t, initialSum.Dashboards[0].Name, sum.Dashboards[0].Name)
				hasAssociation(t, sum.Dashboards[0].LabelAssociations)

				require.Len(t, sum.Labels, 1, "missing required labels")
				assert.Equal(t, initialSum.Labels[0].MetaName, sum.Labels[0].MetaName)
				assert.Equal(t, initialSum.Labels[0].Name, sum.Labels[0].Name)

				require.Len(t, sum.NotificationRules, 1, "missing required rules")
				assert.Equal(t, initialSum.NotificationRules[0].MetaName, sum.NotificationRules[0].MetaName)
				assert.Equal(t, initialSum.NotificationRules[0].Name, sum.NotificationRules[0].Name)
				assert.Equal(t, initialSum.NotificationRules[0].EndpointMetaName, sum.NotificationRules[0].EndpointMetaName)
				assert.Equal(t, initialSum.NotificationRules[0].EndpointType, sum.NotificationRules[0].EndpointType)
				hasAssociation(t, sum.NotificationRules[0].LabelAssociations)

				require.Len(t, sum.Tasks, 1, "missing required tasks")
				assert.Equal(t, initialSum.Tasks[0].MetaName, sum.Tasks[0].MetaName)
				assert.Equal(t, initialSum.Tasks[0].Name, sum.Tasks[0].Name)
				hasAssociation(t, sum.Tasks[0].LabelAssociations)

				require.Len(t, sum.TelegrafConfigs, 1, "missing required telegraf configs")
				assert.Equal(t, initialSum.TelegrafConfigs[0].MetaName, sum.TelegrafConfigs[0].MetaName)
				assert.Equal(t, initialSum.TelegrafConfigs[0].TelegrafConfig.Name, sum.TelegrafConfigs[0].TelegrafConfig.Name)
				hasAssociation(t, sum.TelegrafConfigs[0].LabelAssociations)

				require.Len(t, sum.Variables, 1, "missing required variables")
				assert.Equal(t, initialSum.Variables[0].MetaName, sum.Variables[0].MetaName)
				assert.Equal(t, initialSum.Variables[0].Name, sum.Variables[0].Name)
				hasAssociation(t, sum.Variables[0].LabelAssociations)
			})

			t.Run("should return all associated dashboards in full", func(t *testing.T) {
				dash := influxdb.Dashboard{
					Name: "dasher",
					Cells: []*influxdb.Cell{
						{
							ID: 1,
							CellProperty: influxdb.CellProperty{
								H: 1,
								W: 2,
							},
							View: &influxdb.View{
								ViewContents: influxdb.ViewContents{
									Name: "name",
								},
								Properties: influxdb.MarkdownViewProperties{
									Type: influxdb.ViewPropertyTypeMarkdown,
									Note: "the markdown",
								},
							},
						},
					},
				}

				pkg := newTemplate(pkger.DashboardToObject("", dash))

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg))
				require.NoError(t, err)

				defer deleteStackFn(t, impact.StackID)

				require.Len(t, impact.Summary.Dashboards, 1)

				exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(impact.StackID))
				require.NoError(t, err)

				summary := exportedTemplate.Summary()
				require.Len(t, summary.Dashboards, 1)

				exportedDash := summary.Dashboards[0]
				require.Len(t, exportedDash.Charts, 1)

				expectedChartProps := dash.Cells[0].View.Properties
				assert.Equal(t, expectedChartProps, exportedDash.Charts[0].Properties)
			})

			t.Run("when label associations have changed", func(t *testing.T) {
				newLabelAssociationTestFn := func(t *testing.T) (pkger.Stack, pkger.Summary, func()) {
					t.Helper()

					stack, cleanup := newStackFn(t, pkger.StackCreate{})
					defer func() {
						if t.Failed() {
							cleanup()
						}
					}()

					labelObj := newLabelObject("test-label", "", "", "")
					bktObj := newBucketObject("test-bucket", "", "")
					bktObj.AddAssociations(pkger.ObjectAssociation{
						Kind:     pkger.KindLabel,
						MetaName: labelObj.Name(),
					})
					pkg := newTemplate(bktObj, labelObj)

					impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg), pkger.ApplyWithStackID(stack.ID))
					require.NoError(t, err)

					require.Len(t, impact.Summary.Labels, 1)
					require.Len(t, impact.Summary.Buckets, 1)
					require.Len(t, impact.Summary.Buckets[0].LabelAssociations, 1)

					return stack, impact.Summary, cleanup
				}

				t.Run("should not export associations removed in platform", func(t *testing.T) {
					stack, initialSummary, cleanup := newLabelAssociationTestFn(t)
					defer cleanup()

					err := l.LabelService(t).DeleteLabelMapping(ctx, &influxdb.LabelMapping{
						LabelID:      platform.ID(initialSummary.Labels[0].ID),
						ResourceID:   platform.ID(initialSummary.Buckets[0].ID),
						ResourceType: influxdb.BucketsResourceType,
					})
					require.NoError(t, err)

					exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(stack.ID))
					require.NoError(t, err)

					exportedSum := exportedTemplate.Summary()
					require.Len(t, exportedSum.Labels, 1)
					require.Len(t, exportedSum.Buckets, 1)
					require.Empty(t, exportedSum.Buckets[0].LabelAssociations, "received unexpected label associations")
				})

				t.Run("should export associations platform resources not associated with stack", func(t *testing.T) {
					stack, initialSummary, cleanup := newLabelAssociationTestFn(t)
					defer cleanup()

					newLabel := &influxdb.Label{
						OrgID: l.Org.ID,
						Name:  "test-label-2",
					}
					require.NoError(t, l.LabelService(t).CreateLabel(ctx, newLabel))
					defer resourceCheck.mustDeleteLabel(t, newLabel.ID)

					err := l.LabelService(t).CreateLabelMapping(ctx, &influxdb.LabelMapping{
						LabelID:      newLabel.ID,
						ResourceID:   platform.ID(initialSummary.Buckets[0].ID),
						ResourceType: influxdb.BucketsResourceType,
					})
					require.NoError(t, err)

					exportedTemplate, err := svc.Export(ctx, pkger.ExportWithStackID(stack.ID))
					require.NoError(t, err)

					exportedSum := exportedTemplate.Summary()
					assert.Len(t, exportedSum.Labels, 2)
					require.Len(t, exportedSum.Buckets, 1)
					require.Len(t, exportedSum.Buckets[0].LabelAssociations, 2)

					expectedAssociation := initialSummary.Labels[0]
					expectedAssociation.ID, expectedAssociation.OrgID = 0, 0
					assert.Contains(t, exportedSum.Buckets[0].LabelAssociations, expectedAssociation)
				})
			})
		})
	})

	t.Run("errors incurred during application of template rolls back to state before template", func(t *testing.T) {
		stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{})
		require.NoError(t, err)
		require.Empty(t, stacks)

		svc := pkger.NewService(
			pkger.WithBucketSVC(l.BucketService(t)),
			pkger.WithDashboardSVC(l.DashboardService(t)),
			pkger.WithCheckSVC(l.CheckService()),
			pkger.WithLabelSVC(&fakeLabelSVC{
				LabelService:    l.LabelService(t),
				createKillCount: 2, // hits error on 3rd attempt at creating a mapping
			}),
			pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)),
			pkger.WithNotificationRuleSVC(l.NotificationRuleService(t)),
			pkger.WithOrganizationService(l.OrganizationService()),
			pkger.WithStore(pkger.NewStoreKV(l.kvStore)),
			pkger.WithTaskSVC(l.TaskServiceKV(t)),
			pkger.WithTelegrafSVC(l.TelegrafService(t)),
			pkger.WithVariableSVC(l.VariableService(t)),
		)

		_, err = svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t)))
		require.Error(t, err)

		bkts, _, err := l.BucketService(t).FindBuckets(ctx, influxdb.BucketFilter{OrganizationID: &l.Org.ID})
		require.NoError(t, err)
		for _, b := range bkts {
			if influxdb.BucketTypeSystem == b.Type {
				continue
			}
			// verify system buckets and org bucket are the buckets available
			assert.Equal(t, l.Bucket.Name, b.Name)
		}

		labels, err := l.LabelService(t).FindLabels(ctx, influxdb.LabelFilter{OrgID: &l.Org.ID})
		require.NoError(t, err)
		assert.Empty(t, labels)

		dashs, _, err := l.DashboardService(t).FindDashboards(ctx, influxdb.DashboardFilter{
			OrganizationID: &l.Org.ID,
		}, influxdb.DefaultDashboardFindOptions)
		require.NoError(t, err)
		assert.Empty(t, dashs)

		endpoints, _, err := l.NotificationEndpointService(t).FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{
			OrgID: &l.Org.ID,
		})
		require.NoError(t, err)
		assert.Empty(t, endpoints)

		rules, _, err := l.NotificationRuleService(t).FindNotificationRules(ctx, influxdb.NotificationRuleFilter{
			OrgID: &l.Org.ID,
		})
		require.NoError(t, err)
		assert.Empty(t, rules)

		tasks, _, err := l.TaskServiceKV(t).FindTasks(ctx, taskmodel.TaskFilter{
			OrganizationID: &l.Org.ID,
		})
		require.NoError(t, err)
		assert.Empty(t, tasks)

		teles, _, err := l.TelegrafService(t).FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{
			OrgID: &l.Org.ID,
		})
		require.NoError(t, err)
		assert.Empty(t, teles)

		vars, err := l.VariableService(t).FindVariables(ctx, influxdb.VariableFilter{OrganizationID: &l.Org.ID})
		require.NoError(t, err)
		assert.Empty(t, vars)

		afterStacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{})
		require.NoError(t, err)
		require.Empty(t, afterStacks)
	})

	hasLabelAssociations := func(t *testing.T, associations []pkger.SummaryLabel, numAss int, expectedNames ...string) {
		t.Helper()
		hasAss := func(t *testing.T, expected string) {
			t.Helper()
			for _, ass := range associations {
				if ass.Name == expected {
					return
				}
			}
			require.FailNow(t, "did not find expected association: "+expected)
		}

		require.Len(t, associations, numAss)
		for _, expected := range expectedNames {
			hasAss(t, expected)
		}
	}

	t.Run("dry run", func(t *testing.T) {
		t.Run("template with no existing resources", func(t *testing.T) {
			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t)))
			require.NoError(t, err)

			sum, diff := impact.Summary, impact.Diff

			require.Len(t, diff.Buckets, 1)
			assert.True(t, diff.Buckets[0].IsNew())

			require.Len(t, diff.Checks, 2)
			for _, ch := range diff.Checks {
				assert.True(t, ch.IsNew())
			}

			require.Len(t, diff.Labels, 2)
			assert.True(t, diff.Labels[0].IsNew())
			assert.True(t, diff.Labels[1].IsNew())

			require.Len(t, diff.Variables, 1)
			assert.True(t, diff.Variables[0].IsNew())

			require.Len(t, diff.NotificationRules, 1)
			// the pkg being run here has a relationship with the rule and the endpoint within the pkg.
			assert.Equal(t, "http", diff.NotificationRules[0].New.EndpointType)

			require.Len(t, diff.Dashboards, 1)
			require.Len(t, diff.NotificationEndpoints, 1)
			require.Len(t, diff.Tasks, 1)
			require.Len(t, diff.Telegrafs, 1)

			labels := sum.Labels
			require.Len(t, labels, 2)
			assert.Equal(t, "label-1", labels[0].Name)
			assert.Equal(t, "the 2nd label", labels[1].Name)

			bkts := sum.Buckets
			require.Len(t, bkts, 1)
			assert.Equal(t, "rucketeer", bkts[0].Name)
			hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label")

			checks := sum.Checks
			require.Len(t, checks, 2)
			assert.Equal(t, "check 0 name", checks[0].Check.GetName())
			hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1")
			assert.Equal(t, "check-1", checks[1].Check.GetName())
			hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1")

			dashs := sum.Dashboards
			require.Len(t, dashs, 1)
			assert.Equal(t, "dash_1", dashs[0].Name)
			assert.Equal(t, "desc1", dashs[0].Description)
			hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label")

			endpoints := sum.NotificationEndpoints
			require.Len(t, endpoints, 1)
			assert.Equal(t, "no auth endpoint", endpoints[0].NotificationEndpoint.GetName())
			assert.Equal(t, "http none auth desc", endpoints[0].NotificationEndpoint.GetDescription())
			hasLabelAssociations(t, endpoints[0].LabelAssociations, 1, "label-1")

			require.Len(t, sum.Tasks, 1)
			task := sum.Tasks[0]
			assert.Equal(t, "task_1", task.Name)
			assert.Equal(t, "desc_1", task.Description)
			assert.Equal(t, "15 * * * *", task.Cron)
			hasLabelAssociations(t, task.LabelAssociations, 1, "label-1")

			teles := sum.TelegrafConfigs
			require.Len(t, teles, 1)
			assert.Equal(t, "first tele config", teles[0].TelegrafConfig.Name)
			assert.Equal(t, "desc", teles[0].TelegrafConfig.Description)
			hasLabelAssociations(t, teles[0].LabelAssociations, 1, "label-1")

			vars := sum.Variables
			require.Len(t, vars, 1)
			assert.Equal(t, "query var", vars[0].Name)
			hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1")
			varArgs := vars[0].Arguments
			require.NotNil(t, varArgs)
			assert.Equal(t, "query", varArgs.Type)
			assert.Equal(t, influxdb.VariableQueryValues{
				Query:    "buckets()  |> filter(fn: (r) => r.name !~ /^_/)  |> rename(columns: {name: \"_value\"})  |> keep(columns: [\"_value\"])",
				Language: "flux",
			}, varArgs.Values)
		})

		t.Run("template with env ref", func(t *testing.T) {
			pkgStr := fmt.Sprintf(`
apiVersion: %[1]s
kind: Label
metadata:
  name:
    envRef:
      key: label-1-name-ref
spec:
---
apiVersion: %[1]s
kind: Bucket
metadata:
  name:
    envRef:
      key: bkt-1-name-ref
spec:
  associations:
    - kind: Label
      name:
        envRef:
          key: label-1-name-ref
`, pkger.APIVersion)

			pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr))
			require.NoError(t, err)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID,
				pkger.ApplyWithTemplate(pkg),
				pkger.ApplyWithEnvRefs(map[string]interface{}{
					"bkt-1-name-ref":   "new-bkt-name",
					"label-1-name-ref": "new-label-name",
				}),
			)
			require.NoError(t, err)

			sum := impact.Summary

			require.Len(t, sum.Buckets, 1)
			assert.Equal(t, "new-bkt-name", sum.Buckets[0].Name)

			require.Len(t, sum.Labels, 1)
			assert.Equal(t, "new-label-name", sum.Labels[0].Name)
		})

		t.Run("bucket", func(t *testing.T) {
			template := newTemplate(
				newBucketObject("foo", "", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.Buckets, 1)
			assert.Equal(t, pkger.KindBucket, impact.Diff.Buckets[0].Kind)
		})

		t.Run("check", func(t *testing.T) {
			template := newTemplate(
				newCheckDeadmanObject(t, "check1", "", time.Hour),
				newCheckThresholdObject(t, "check2", "", time.Hour),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.Checks, 2)
			assert.Equal(t, pkger.KindCheckDeadman, impact.Diff.Checks[0].Kind)
			assert.Equal(t, pkger.KindCheckThreshold, impact.Diff.Checks[1].Kind)
		})

		t.Run("dashboards", func(t *testing.T) {
			newQuery := func() influxdb.DashboardQuery {
				return influxdb.DashboardQuery{
					BuilderConfig: influxdb.BuilderConfig{
						Buckets: []string{},
						Tags: []struct {
							Key                   string   `json:"key"`
							Values                []string `json:"values"`
							AggregateFunctionType string   `json:"aggregateFunctionType"`
						}{},
						Functions: []struct {
							Name string `json:"name"`
						}{},
					},
					Text:     "from(v.bucket) |> count()",
					EditMode: "advanced",
				}
			}

			newAxes := func() map[string]influxdb.Axis {
				return map[string]influxdb.Axis{
					"x": {
						Bounds: []string{},
						Label:  "labx",
						Prefix: "pre",
						Suffix: "suf",
						Base:   "base",
						Scale:  "linear",
					},
					"y": {
						Bounds: []string{},
						Label:  "laby",
						Prefix: "pre",
						Suffix: "suf",
						Base:   "base",
						Scale:  "linear",
					},
				}
			}

			newColors := func(types ...string) []influxdb.ViewColor {
				var out []influxdb.ViewColor
				for _, t := range types {
					out = append(out, influxdb.ViewColor{
						Type:  t,
						Hex:   time.Now().Format(time.RFC3339),
						Name:  time.Now().Format(time.RFC3339),
						Value: float64(time.Now().Unix()),
					})
				}
				return out
			}

			tests := []struct {
				name  string
				props influxdb.ViewProperties
			}{
				{
					name: "gauge",
					props: influxdb.GaugeViewProperties{
						Type:              influxdb.ViewPropertyTypeGauge,
						DecimalPlaces:     influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
						Note:              "a note",
						Prefix:            "pre",
						TickPrefix:        "true",
						Suffix:            "suf",
						TickSuffix:        "false",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ShowNoteWhenEmpty: true,
						ViewColors:        newColors("min", "max", "threshold"),
					},
				},
				{
					name: "heatmap",
					props: influxdb.HeatmapViewProperties{
						Type:              influxdb.ViewPropertyTypeHeatMap,
						Note:              "a note",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ShowNoteWhenEmpty: true,
						ViewColors:        []string{"#8F8AF4", "#8F8AF4", "#8F8AF4"},
						XColumn:           "x",
						YColumn:           "y",
						XDomain:           []float64{0, 10},
						YDomain:           []float64{0, 100},
						XAxisLabel:        "x_label",
						XPrefix:           "x_prefix",
						XSuffix:           "x_suffix",
						YAxisLabel:        "y_label",
						YPrefix:           "y_prefix",
						YSuffix:           "y_suffix",
						BinSize:           10,
						TimeFormat:        "",
					},
				},
				{
					name: "histogram",
					props: influxdb.HistogramViewProperties{
						Type:              influxdb.ViewPropertyTypeHistogram,
						Note:              "a note",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ShowNoteWhenEmpty: true,
						ViewColors:        []influxdb.ViewColor{{Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}},
						FillColumns:       []string{"a", "b"},
						XColumn:           "_value",
						XDomain:           []float64{0, 10},
						XAxisLabel:        "x_label",
						BinCount:          30,
						Position:          "stacked",
					},
				},
				{
					name: "markdown",
					props: influxdb.MarkdownViewProperties{
						Type: influxdb.ViewPropertyTypeMarkdown,
						Note: "the note is here with **markdown**",
					},
				},
				{
					name: "scatter",
					props: influxdb.ScatterViewProperties{
						Type:              influxdb.ViewPropertyTypeScatter,
						Note:              "a note",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ShowNoteWhenEmpty: true,
						ViewColors:        []string{"#8F8AF4", "#8F8AF4", "#8F8AF4"},
						XColumn:           "x",
						YColumn:           "y",
						XDomain:           []float64{0, 10},
						YDomain:           []float64{0, 100},
						XAxisLabel:        "x_label",
						XPrefix:           "x_prefix",
						XSuffix:           "x_suffix",
						YAxisLabel:        "y_label",
						YPrefix:           "y_prefix",
						YSuffix:           "y_suffix",
						TimeFormat:        "",
					},
				},
				{
					name: "single stat",
					props: influxdb.SingleStatViewProperties{
						Type:              influxdb.ViewPropertyTypeSingleStat,
						DecimalPlaces:     influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
						Note:              "a note",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						Prefix:            "pre",
						TickPrefix:        "false",
						ShowNoteWhenEmpty: true,
						Suffix:            "suf",
						TickSuffix:        "true",
						ViewColors:        []influxdb.ViewColor{{Type: "text", Hex: "red"}},
					},
				},
				{
					name: "single stat plus line",
					props: influxdb.LinePlusSingleStatProperties{
						Type:              influxdb.ViewPropertyTypeSingleStatPlusLine,
						Axes:              newAxes(),
						DecimalPlaces:     influxdb.DecimalPlaces{IsEnforced: true, Digits: 1},
						Note:              "a note",
						Prefix:            "pre",
						Suffix:            "suf",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ShadeBelow:        true,
						HoverDimension:    "y",
						ShowNoteWhenEmpty: true,
						ViewColors:        []influxdb.ViewColor{{Type: "text", Hex: "red"}},
						XColumn:           "x",
						YColumn:           "y",
						Position:          "stacked",
					},
				},
				{
					name: "table",
					props: influxdb.TableViewProperties{
						Type:              influxdb.ViewPropertyTypeTable,
						Note:              "a note",
						ShowNoteWhenEmpty: true,
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ViewColors:        []influxdb.ViewColor{{Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}},
						TableOptions: influxdb.TableOptions{
							VerticalTimeAxis: true,
							SortBy: influxdb.RenamableField{
								InternalName: "_time",
							},
							Wrapping:       "truncate",
							FixFirstColumn: true,
						},
						FieldOptions: []influxdb.RenamableField{
							{
								InternalName: "_time",
								DisplayName:  "time (ms)",
								Visible:      true,
							},
						},
						TimeFormat: "YYYY:MM:DD",
						DecimalPlaces: influxdb.DecimalPlaces{
							IsEnforced: true,
							Digits:     1,
						},
					},
				},
				{
					name: "xy",
					props: influxdb.XYViewProperties{
						Type:              influxdb.ViewPropertyTypeXY,
						Axes:              newAxes(),
						Geom:              "step",
						Note:              "a note",
						Queries:           []influxdb.DashboardQuery{newQuery()},
						ShadeBelow:        true,
						HoverDimension:    "y",
						ShowNoteWhenEmpty: true,
						ViewColors:        []influxdb.ViewColor{{Type: "text", Hex: "red"}},
						XColumn:           "x",
						YColumn:           "y",
						Position:          "overlaid",
						TimeFormat:        "",
					},
				},
			}

			for _, tt := range tests {
				fn := func(t *testing.T) {
					obj := pkger.DashboardToObject("", influxdb.Dashboard{
						OrganizationID: l.Org.ID,
						Name:           tt.name,
						Cells: []*influxdb.Cell{
							{
								CellProperty: influxdb.CellProperty{
									X: 2, Y: 2,
									H: 5, W: 5,
								},
								View: &influxdb.View{Properties: tt.props},
							},
						},
					})
					template := newTemplate(obj)

					impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
					require.NoError(t, err)

					diff := impact.Diff.Dashboards
					require.Len(t, diff, 1)

					actual := diff[0]
					assert.Equal(t, pkger.KindDashboard, actual.Kind)
					assert.Equal(t, tt.name, actual.New.Name)

					charts := actual.New.Charts
					require.Len(t, charts, 1)
					require.NotNil(t, charts[0].Properties)
					assert.Equal(t, tt.props, charts[0].Properties)
				}

				t.Run(tt.name, fn)
			}
		})

		t.Run("labels", func(t *testing.T) {
			template := newTemplate(
				newLabelObject("label", "", "", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.Labels, 1)
			assert.Equal(t, pkger.KindLabel, impact.Diff.Labels[0].Kind)
		})

		t.Run("endpoints", func(t *testing.T) {
			template := newTemplate(
				newEndpointHTTP("http", "", ""),
				newEndpointPagerDuty("pager", "", ""),
				newEndpointSlack("slack", "", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.NotificationEndpoints, 3)
			assert.Equal(t, pkger.KindNotificationEndpointHTTP, impact.Diff.NotificationEndpoints[0].Kind)
			assert.Equal(t, pkger.KindNotificationEndpointPagerDuty, impact.Diff.NotificationEndpoints[1].Kind)
			assert.Equal(t, pkger.KindNotificationEndpointSlack, impact.Diff.NotificationEndpoints[2].Kind)
		})

		t.Run("rules", func(t *testing.T) {
			template := newTemplate(
				newEndpointHTTP("http", "", ""),
				newRuleObject(t, "rule", "", "http", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.NotificationRules, 1)
			assert.Equal(t, pkger.KindNotificationRule, impact.Diff.NotificationRules[0].Kind)
		})

		t.Run("tasks", func(t *testing.T) {
			template := newTemplate(
				newTaskObject("task", "", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.Tasks, 1)
			assert.Equal(t, pkger.KindTask, impact.Diff.Tasks[0].Kind)
		})

		t.Run("telegraf configs", func(t *testing.T) {
			template := newTemplate(
				newTelegrafObject("tele", "", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.Telegrafs, 1)
			assert.Equal(t, pkger.KindTelegraf, impact.Diff.Telegrafs[0].Kind)
		})

		t.Run("variables", func(t *testing.T) {
			template := newTemplate(
				newVariableObject("var", "", ""),
			)

			impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(template))
			require.NoError(t, err)

			require.Len(t, impact.Diff.Variables, 1)
			assert.Equal(t, pkger.KindVariable, impact.Diff.Variables[0].Kind)
		})
	})

	t.Run("apply a template of all new resources", func(t *testing.T) {
		// this initial test is also setup for the sub tests
		impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t)))
		require.NoError(t, err)
		defer deleteStackFn(t, impact.StackID)

		assert.NotZero(t, impact.StackID)

		sum1 := impact.Summary

		labels := sum1.Labels
		require.Len(t, labels, 2)
		assert.Equal(t, pkger.KindLabel, labels[0].Kind)
		assert.NotZero(t, labels[0].ID)
		assert.Equal(t, "label-1", labels[0].Name)
		assert.Equal(t, "the 2nd label", labels[1].Name)

		bkts := sum1.Buckets
		require.Len(t, bkts, 1)
		assert.Equal(t, pkger.KindBucket, bkts[0].Kind)
		assert.NotZero(t, bkts[0].ID)
		assert.NotEmpty(t, bkts[0].MetaName)
		assert.Equal(t, "rucketeer", bkts[0].Name)
		hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label")

		checks := sum1.Checks
		require.Len(t, checks, 2)
		assert.Equal(t, "check 0 name", checks[0].Check.GetName())
		assert.Equal(t, pkger.KindCheckThreshold, checks[0].Kind)
		hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1")
		assert.Equal(t, "check-1", checks[1].Check.GetName())
		assert.Equal(t, pkger.KindCheckDeadman, checks[1].Kind)
		hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1")
		for _, ch := range checks {
			assert.NotZero(t, ch.Check.GetID())
		}

		dashs := sum1.Dashboards
		require.Len(t, dashs, 1)
		assert.Equal(t, pkger.KindDashboard, dashs[0].Kind)
		assert.NotZero(t, dashs[0].ID)
		assert.NotEmpty(t, dashs[0].Name)
		assert.Equal(t, "dash_1", dashs[0].Name)
		assert.Equal(t, "desc1", dashs[0].Description)
		hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label")
		require.Len(t, dashs[0].Charts, 1)
		assert.Equal(t, influxdb.ViewPropertyTypeSingleStat, dashs[0].Charts[0].Properties.GetType())

		endpoints := sum1.NotificationEndpoints
		require.Len(t, endpoints, 1)
		assert.Equal(t, pkger.KindNotificationEndpointHTTP, endpoints[0].Kind)
		assert.NotZero(t, endpoints[0].NotificationEndpoint.GetID())
		assert.Equal(t, "no auth endpoint", endpoints[0].NotificationEndpoint.GetName())
		assert.Equal(t, "http none auth desc", endpoints[0].NotificationEndpoint.GetDescription())
		assert.Equal(t, taskmodel.TaskStatusInactive, string(endpoints[0].NotificationEndpoint.GetStatus()))
		hasLabelAssociations(t, endpoints[0].LabelAssociations, 1, "label-1")

		require.Len(t, sum1.NotificationRules, 1)
		rule := sum1.NotificationRules[0]
		assert.Equal(t, pkger.KindNotificationRule, rule.Kind)
		assert.NotZero(t, rule.ID)
		assert.Equal(t, "rule_0", rule.Name)
		assert.Equal(t, pkger.SafeID(endpoints[0].NotificationEndpoint.GetID()), rule.EndpointID)
		assert.Equal(t, "http-none-auth-notification-endpoint", rule.EndpointMetaName)
		assert.Equalf(t, "http", rule.EndpointType, "rule: %+v", rule)

		require.Len(t, sum1.Tasks, 1)
		task := sum1.Tasks[0]
		assert.Equal(t, pkger.KindTask, task.Kind)
		assert.NotZero(t, task.ID)
		assert.Equal(t, "task_1", task.Name)
		assert.Equal(t, "desc_1", task.Description)

		teles := sum1.TelegrafConfigs
		require.Len(t, teles, 1)
		assert.Equal(t, pkger.KindTelegraf, teles[0].Kind)
		assert.NotZero(t, teles[0].TelegrafConfig.ID)
		assert.Equal(t, l.Org.ID, teles[0].TelegrafConfig.OrgID)
		assert.Equal(t, "first tele config", teles[0].TelegrafConfig.Name)
		assert.Equal(t, "desc", teles[0].TelegrafConfig.Description)
		assert.Equal(t, telegrafCfg, teles[0].TelegrafConfig.Config)

		vars := sum1.Variables
		require.Len(t, vars, 1)
		assert.Equal(t, pkger.KindVariable, vars[0].Kind)
		assert.NotZero(t, vars[0].ID)
		assert.Equal(t, "query var", vars[0].Name)
		assert.Equal(t, []string{"rucketeer"}, vars[0].Selected)
		hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1")
		varArgs := vars[0].Arguments
		require.NotNil(t, varArgs)
		assert.Equal(t, "query", varArgs.Type)
		assert.Equal(t, influxdb.VariableQueryValues{
			Query:    "buckets()  |> filter(fn: (r) => r.name !~ /^_/)  |> rename(columns: {name: \"_value\"})  |> keep(columns: [\"_value\"])",
			Language: "flux",
		}, varArgs.Values)
		platformVar := resourceCheck.mustGetVariable(t, byID(platform.ID(vars[0].ID)))
		assert.Equal(t, []string{"rucketeer"}, platformVar.Selected)

		newSumMapping := func(id pkger.SafeID, pkgName, name string, rt influxdb.ResourceType) pkger.SummaryLabelMapping {
			return pkger.SummaryLabelMapping{
				Status:           pkger.StateStatusNew,
				ResourceID:       id,
				ResourceType:     rt,
				ResourceMetaName: pkgName,
				ResourceName:     name,
				LabelMetaName:    labels[0].MetaName,
				LabelName:        labels[0].Name,
				LabelID:          labels[0].ID,
			}
		}

		mappings := sum1.LabelMappings

		mappingsContain := func(t *testing.T, id pkger.SafeID, pkgName, name string, resourceType influxdb.ResourceType) {
			t.Helper()
			assert.Contains(t, mappings, newSumMapping(id, pkgName, name, resourceType))
		}

		require.Len(t, mappings, 11)
		mappingsContain(t, bkts[0].ID, bkts[0].MetaName, bkts[0].Name, influxdb.BucketsResourceType)
		mappingsContain(t, pkger.SafeID(checks[0].Check.GetID()), checks[0].MetaName, checks[0].Check.GetName(), influxdb.ChecksResourceType)
		mappingsContain(t, dashs[0].ID, dashs[0].MetaName, dashs[0].Name, influxdb.DashboardsResourceType)
		mappingsContain(t, pkger.SafeID(endpoints[0].NotificationEndpoint.GetID()), endpoints[0].MetaName, endpoints[0].NotificationEndpoint.GetName(), influxdb.NotificationEndpointResourceType)
		mappingsContain(t, rule.ID, rule.MetaName, rule.Name, influxdb.NotificationRuleResourceType)
		mappingsContain(t, task.ID, task.MetaName, task.Name, influxdb.TasksResourceType)
		mappingsContain(t, pkger.SafeID(teles[0].TelegrafConfig.ID), teles[0].MetaName, teles[0].TelegrafConfig.Name, influxdb.TelegrafsResourceType)
		mappingsContain(t, vars[0].ID, vars[0].MetaName, vars[0].Name, influxdb.VariablesResourceType)

		var (
			// used in dependent subtests
			sum1Bkts      = sum1.Buckets
			sum1Checks    = sum1.Checks
			sum1Dashs     = sum1.Dashboards
			sum1Endpoints = sum1.NotificationEndpoints
			sum1Labels    = sum1.Labels
			sum1Rules     = sum1.NotificationRules
			sum1Tasks     = sum1.Tasks
			sum1Teles     = sum1.TelegrafConfigs
			sum1Vars      = sum1.Variables
		)

		t.Run("exporting all resources for an org", func(t *testing.T) {
			t.Run("getting everything", func(t *testing.T) {
				newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources(
					pkger.ExportByOrgIDOpt{
						OrgID: l.Org.ID,
					},
				))
				require.NoError(t, err)

				sum := newPkg.Summary()

				labels := sum.Labels
				require.Len(t, labels, 2)
				sortLabels(labels)
				assert.Equal(t, "label-1", labels[0].Name)
				assert.Equal(t, "the 2nd label", labels[1].Name)

				bkts := sum.Buckets
				require.Len(t, bkts, 1)
				assert.NotEmpty(t, bkts[0].MetaName)
				assert.Equal(t, "rucketeer", bkts[0].Name)
				hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label")

				checks := sum.Checks
				require.Len(t, checks, 2)
				sortChecks(checks)
				assert.Equal(t, "check 0 name", checks[0].Check.GetName())
				hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1")
				assert.Equal(t, "check-1", checks[1].Check.GetName())
				hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1")

				dashs := sum.Dashboards
				require.Len(t, dashs, 1)
				assert.NotEmpty(t, dashs[0].Name)
				assert.Equal(t, "dash_1", dashs[0].Name)
				assert.Equal(t, "desc1", dashs[0].Description)
				hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label")
				require.Len(t, dashs[0].Charts, 1)
				assert.Equal(t, influxdb.ViewPropertyTypeSingleStat, dashs[0].Charts[0].Properties.GetType())

				endpoints := sum.NotificationEndpoints
				require.Len(t, endpoints, 1)
				assert.Equal(t, "no auth endpoint", endpoints[0].NotificationEndpoint.GetName())
				assert.Equal(t, "http none auth desc", endpoints[0].NotificationEndpoint.GetDescription())
				assert.Equal(t, taskmodel.TaskStatusInactive, string(endpoints[0].NotificationEndpoint.GetStatus()))
				hasLabelAssociations(t, endpoints[0].LabelAssociations, 1, "label-1")

				require.Len(t, sum.NotificationRules, 1)
				rule := sum.NotificationRules[0]
				assert.Equal(t, "rule_0", rule.Name)
				assert.Equal(t, pkger.SafeID(endpoints[0].NotificationEndpoint.GetID()), rule.EndpointID)
				assert.NotEmpty(t, rule.EndpointMetaName)

				require.Len(t, sum.Tasks, 1)
				task := sum.Tasks[0]
				assert.Equal(t, "task_1", task.Name)
				assert.Equal(t, "desc_1", task.Description)

				teles := sum.TelegrafConfigs
				require.Len(t, teles, 1)
				assert.Equal(t, "first tele config", teles[0].TelegrafConfig.Name)
				assert.Equal(t, "desc", teles[0].TelegrafConfig.Description)
				assert.Equal(t, telegrafCfg, teles[0].TelegrafConfig.Config)

				vars := sum.Variables
				require.Len(t, vars, 1)
				assert.Equal(t, "query var", vars[0].Name)
				hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1")
				varArgs := vars[0].Arguments
				require.NotNil(t, varArgs)
				assert.Equal(t, "query", varArgs.Type)
				assert.Equal(t, influxdb.VariableQueryValues{
					Query:    "buckets()  |> filter(fn: (r) => r.name !~ /^_/)  |> rename(columns: {name: \"_value\"})  |> keep(columns: [\"_value\"])",
					Language: "flux",
				}, varArgs.Values)

				newSumMapping := func(id pkger.SafeID, pkgName, name string, rt influxdb.ResourceType) pkger.SummaryLabelMapping {
					return pkger.SummaryLabelMapping{
						Status:           pkger.StateStatusNew,
						ResourceID:       id,
						ResourceType:     rt,
						ResourceMetaName: pkgName,
						ResourceName:     name,
						LabelMetaName:    labels[0].MetaName,
						LabelName:        labels[0].Name,
						LabelID:          labels[0].ID,
					}
				}

				mappings := sum.LabelMappings
				require.Len(t, mappings, 11)
				assert.Contains(t, mappings, newSumMapping(bkts[0].ID, bkts[0].MetaName, bkts[0].Name, influxdb.BucketsResourceType))

				ch0 := checks[0]
				assert.Contains(t, mappings, newSumMapping(pkger.SafeID(ch0.Check.GetID()), ch0.MetaName, ch0.Check.GetName(), influxdb.ChecksResourceType))

				ch1 := checks[0]
				assert.Contains(t, mappings, newSumMapping(pkger.SafeID(ch1.Check.GetID()), ch1.MetaName, ch1.Check.GetName(), influxdb.ChecksResourceType))

				ne := endpoints[0]
				assert.Contains(t, mappings, newSumMapping(pkger.SafeID(ne.NotificationEndpoint.GetID()), ne.MetaName, ne.NotificationEndpoint.GetName(), influxdb.NotificationEndpointResourceType))

				assert.Contains(t, mappings, newSumMapping(dashs[0].ID, dashs[0].MetaName, dashs[0].Name, influxdb.DashboardsResourceType))
				assert.Contains(t, mappings, newSumMapping(rule.ID, rule.MetaName, rule.Name, influxdb.NotificationRuleResourceType))
				assert.Contains(t, mappings, newSumMapping(task.ID, task.MetaName, task.Name, influxdb.TasksResourceType))
				assert.Contains(t, mappings, newSumMapping(pkger.SafeID(teles[0].TelegrafConfig.ID), teles[0].MetaName, teles[0].TelegrafConfig.Name, influxdb.TelegrafsResourceType))
				assert.Contains(t, mappings, newSumMapping(vars[0].ID, vars[0].MetaName, vars[0].Name, influxdb.VariablesResourceType))
			})

			t.Run("filtered by resource types", func(t *testing.T) {
				newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources(
					pkger.ExportByOrgIDOpt{
						OrgID:         l.Org.ID,
						ResourceKinds: []pkger.Kind{pkger.KindCheck, pkger.KindTask},
					},
				))
				require.NoError(t, err)

				newSum := newPkg.Summary()
				assert.NotEmpty(t, newSum.Checks)
				assert.NotEmpty(t, newSum.Labels)
				assert.NotEmpty(t, newSum.Tasks)
				assert.Empty(t, newSum.Buckets)
				assert.Empty(t, newSum.Dashboards)
				assert.Empty(t, newSum.NotificationEndpoints)
				assert.Empty(t, newSum.NotificationRules)
				assert.Empty(t, newSum.TelegrafConfigs)
				assert.Empty(t, newSum.Variables)
			})

			t.Run("filtered by label resource type", func(t *testing.T) {
				newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources(
					pkger.ExportByOrgIDOpt{
						OrgID:         l.Org.ID,
						ResourceKinds: []pkger.Kind{pkger.KindLabel},
					},
				))
				require.NoError(t, err)

				newSum := newPkg.Summary()
				assert.NotEmpty(t, newSum.Labels)
				assert.Empty(t, newSum.Buckets)
				assert.Empty(t, newSum.Checks)
				assert.Empty(t, newSum.Dashboards)
				assert.Empty(t, newSum.NotificationEndpoints)
				assert.Empty(t, newSum.NotificationRules)
				assert.Empty(t, newSum.Tasks)
				assert.Empty(t, newSum.TelegrafConfigs)
				assert.Empty(t, newSum.Variables)
			})

			t.Run("filtered by label name", func(t *testing.T) {
				newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources(
					pkger.ExportByOrgIDOpt{
						OrgID:      l.Org.ID,
						LabelNames: []string{"the 2nd label"},
					},
				))
				require.NoError(t, err)

				newSum := newPkg.Summary()
				assert.NotEmpty(t, newSum.Buckets)
				assert.NotEmpty(t, newSum.Dashboards)
				assert.NotEmpty(t, newSum.Labels)
				assert.Empty(t, newSum.Checks)
				assert.Empty(t, newSum.NotificationEndpoints)
				assert.Empty(t, newSum.NotificationRules)
				assert.Empty(t, newSum.Tasks)
				assert.Empty(t, newSum.Variables)
			})

			t.Run("filtered by label name and resource type", func(t *testing.T) {
				newPkg, err := svc.Export(ctx, pkger.ExportWithAllOrgResources(
					pkger.ExportByOrgIDOpt{
						OrgID:         l.Org.ID,
						LabelNames:    []string{"the 2nd label"},
						ResourceKinds: []pkger.Kind{pkger.KindDashboard},
					},
				))
				require.NoError(t, err)

				newSum := newPkg.Summary()
				assert.NotEmpty(t, newSum.Dashboards)
				assert.NotEmpty(t, newSum.Labels)
				assert.Empty(t, newSum.Buckets)
				assert.Empty(t, newSum.Checks)
				assert.Empty(t, newSum.NotificationEndpoints)
				assert.Empty(t, newSum.NotificationRules)
				assert.Empty(t, newSum.Tasks)
				assert.Empty(t, newSum.Variables)
			})
		})

		t.Run("pkg with same bkt-var-label does nto create new resources for them", func(t *testing.T) {
			// validate the new template doesn't create new resources for bkts/labels/vars
			// since names collide.
			impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(newCompletePkg(t)))
			require.NoError(t, err)

			sum2 := impact.Summary

			require.Equal(t, sum1.Buckets, sum2.Buckets)
			require.Equal(t, sum1.Labels, sum2.Labels)
			require.Equal(t, sum1.NotificationEndpoints, sum2.NotificationEndpoints)
			require.Equal(t, sum1.Variables, sum2.Variables)

			// dashboards should be new
			require.NotEqual(t, sum1.Dashboards, sum2.Dashboards)
		})

		t.Run("referenced secret values provided do not create new secrets", func(t *testing.T) {
			applyPkgStr := func(t *testing.T, pkgStr string) pkger.Summary {
				t.Helper()
				pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr))
				require.NoError(t, err)

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg))
				require.NoError(t, err)
				return impact.Summary
			}

			pkgWithSecretRaw := fmt.Sprintf(`
apiVersion: %[1]s
kind: NotificationEndpointPagerDuty
metadata:
  name:      pager-duty-notification-endpoint
spec:
  url:  http://localhost:8080/orgs/7167eb6719fa34e5/alert-history
  routingKey: secret-sauce
`, pkger.APIVersion)

			secretSum := applyPkgStr(t, pkgWithSecretRaw)
			require.Len(t, secretSum.NotificationEndpoints, 1)

			id := secretSum.NotificationEndpoints[0].NotificationEndpoint.GetID()
			expected := influxdb.SecretField{
				Key: id.String() + "-routing-key",
			}
			secrets := secretSum.NotificationEndpoints[0].NotificationEndpoint.SecretFields()
			require.Len(t, secrets, 1)
			assert.Equal(t, expected, secrets[0])

			const pkgWithSecretRef = `
apiVersion: %[1]s
kind: NotificationEndpointPagerDuty
metadata:
  name:      pager-duty-notification-endpoint
spec:
  url:  http://localhost:8080/orgs/7167eb6719fa34e5/alert-history
  routingKey:
    secretRef:
      key: %s-routing-key
`
			secretSum = applyPkgStr(t, fmt.Sprintf(pkgWithSecretRef, pkger.APIVersion, id.String()))
			require.Len(t, secretSum.NotificationEndpoints, 1)

			expected = influxdb.SecretField{
				Key: id.String() + "-routing-key",
			}
			secrets = secretSum.NotificationEndpoints[0].NotificationEndpoint.SecretFields()
			require.Len(t, secrets, 1)
			assert.Equal(t, expected, secrets[0])
		})

		t.Run("exporting resources with existing ids should return a valid pkg", func(t *testing.T) {
			resToClone := []pkger.ResourceToClone{
				{
					Kind: pkger.KindBucket,
					ID:   platform.ID(sum1Bkts[0].ID),
				},
				{
					Kind: pkger.KindCheck,
					ID:   sum1Checks[0].Check.GetID(),
				},
				{
					Kind: pkger.KindCheck,
					ID:   sum1Checks[1].Check.GetID(),
				},
				{
					Kind: pkger.KindDashboard,
					ID:   platform.ID(sum1Dashs[0].ID),
				},
				{
					Kind: pkger.KindLabel,
					ID:   platform.ID(sum1Labels[0].ID),
				},
				{
					Kind: pkger.KindNotificationEndpoint,
					ID:   sum1Endpoints[0].NotificationEndpoint.GetID(),
				},
				{
					Kind: pkger.KindTask,
					ID:   platform.ID(sum1Tasks[0].ID),
				},
				{
					Kind: pkger.KindTelegraf,
					ID:   sum1Teles[0].TelegrafConfig.ID,
				},
			}

			resWithNewName := []pkger.ResourceToClone{
				{
					Kind: pkger.KindNotificationRule,
					Name: "new rule name",
					ID:   platform.ID(sum1Rules[0].ID),
				},
				{
					Kind: pkger.KindVariable,
					Name: "new name",
					ID:   platform.ID(sum1Vars[0].ID),
				},
			}

			newPkg, err := svc.Export(ctx,
				pkger.ExportWithExistingResources(append(resToClone, resWithNewName...)...),
			)
			require.NoError(t, err)

			newSum := newPkg.Summary()

			labels := newSum.Labels
			require.Len(t, labels, 2)
			sortLabels(labels)
			assert.Zero(t, labels[0].ID)
			assert.Equal(t, "label-1", labels[0].Name)
			assert.Zero(t, labels[1].ID)
			assert.Equal(t, "the 2nd label", labels[1].Name)

			bkts := newSum.Buckets
			require.Len(t, bkts, 1)
			assert.Zero(t, bkts[0].ID)
			assert.Equal(t, "rucketeer", bkts[0].Name)
			hasLabelAssociations(t, bkts[0].LabelAssociations, 2, "label-1", "the 2nd label")

			checks := newSum.Checks
			require.Len(t, checks, 2)
			sortChecks(checks)
			assert.Equal(t, "check 0 name", checks[0].Check.GetName())
			hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1")
			assert.Equal(t, "check-1", checks[1].Check.GetName())
			hasLabelAssociations(t, checks[1].LabelAssociations, 1, "label-1")

			dashs := newSum.Dashboards
			require.Len(t, dashs, 1)
			assert.Zero(t, dashs[0].ID)
			assert.Equal(t, "dash_1", dashs[0].Name)
			assert.Equal(t, "desc1", dashs[0].Description)
			hasLabelAssociations(t, dashs[0].LabelAssociations, 2, "label-1", "the 2nd label")
			require.Len(t, dashs[0].Charts, 1)
			assert.Equal(t, influxdb.ViewPropertyTypeSingleStat, dashs[0].Charts[0].Properties.GetType())

			newEndpoints := newSum.NotificationEndpoints
			require.Len(t, newEndpoints, 1)
			assert.Equal(t, sum1Endpoints[0].NotificationEndpoint.GetName(), newEndpoints[0].NotificationEndpoint.GetName())
			assert.Equal(t, sum1Endpoints[0].NotificationEndpoint.GetDescription(), newEndpoints[0].NotificationEndpoint.GetDescription())
			hasLabelAssociations(t, newEndpoints[0].LabelAssociations, 1, "label-1")

			require.Len(t, newSum.NotificationRules, 1)
			newRule := newSum.NotificationRules[0]
			assert.Equal(t, "new rule name", newRule.Name)
			assert.Zero(t, newRule.EndpointID)
			assert.NotEmpty(t, newRule.EndpointMetaName)
			hasLabelAssociations(t, newRule.LabelAssociations, 1, "label-1")

			require.Len(t, newSum.Tasks, 1)
			newTask := newSum.Tasks[0]
			assert.Equal(t, sum1Tasks[0].Name, newTask.Name)
			assert.Equal(t, sum1Tasks[0].Description, newTask.Description)
			assert.Equal(t, sum1Tasks[0].Cron, newTask.Cron)
			assert.Equal(t, sum1Tasks[0].Every, newTask.Every)
			assert.Equal(t, sum1Tasks[0].Offset, newTask.Offset)
			assert.Equal(t, sum1Tasks[0].Query, newTask.Query)
			assert.Equal(t, sum1Tasks[0].Status, newTask.Status)

			require.Len(t, newSum.TelegrafConfigs, 1)
			assert.Equal(t, sum1Teles[0].TelegrafConfig.Name, newSum.TelegrafConfigs[0].TelegrafConfig.Name)
			assert.Equal(t, sum1Teles[0].TelegrafConfig.Description, newSum.TelegrafConfigs[0].TelegrafConfig.Description)
			hasLabelAssociations(t, newSum.TelegrafConfigs[0].LabelAssociations, 1, "label-1")

			vars := newSum.Variables
			require.Len(t, vars, 1)
			assert.Zero(t, vars[0].ID)
			assert.Equal(t, "new name", vars[0].Name) // new name
			hasLabelAssociations(t, vars[0].LabelAssociations, 1, "label-1")
			varArgs := vars[0].Arguments
			require.NotNil(t, varArgs)
			assert.Equal(t, "query", varArgs.Type)
			assert.Equal(t, influxdb.VariableQueryValues{
				Query:    "buckets()  |> filter(fn: (r) => r.name !~ /^_/)  |> rename(columns: {name: \"_value\"})  |> keep(columns: [\"_value\"])",
				Language: "flux",
			}, varArgs.Values)
		})

		t.Run("error incurred during template application when resources already exist rollsback to prev state", func(t *testing.T) {
			updatePkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(updatePkgYMLStr))
			require.NoError(t, err)

			svc := pkger.NewService(
				pkger.WithBucketSVC(&fakeBucketSVC{
					BucketService:   l.BucketService(t),
					updateKillCount: 0, // kill on first update for bucket
				}),
				pkger.WithCheckSVC(l.CheckService()),
				pkger.WithDashboardSVC(l.DashboardService(t)),
				pkger.WithLabelSVC(l.LabelService(t)),
				pkger.WithNotificationEndpointSVC(l.NotificationEndpointService(t)),
				pkger.WithNotificationRuleSVC(l.NotificationRuleService(t)),
				pkger.WithOrganizationService(l.OrganizationService()),
				pkger.WithStore(pkger.NewStoreKV(l.kvStore)),
				pkger.WithTaskSVC(l.TaskServiceKV(t)),
				pkger.WithTelegrafSVC(l.TelegrafService(t)),
				pkger.WithVariableSVC(l.VariableService(t)),
			)

			_, err = svc.Apply(ctx, l.Org.ID, 0, pkger.ApplyWithTemplate(updatePkg))
			require.Error(t, err)

			bkt, err := l.BucketService(t).FindBucketByID(ctx, platform.ID(sum1Bkts[0].ID))
			require.NoError(t, err)
			// make sure the desc change is not applied and is rolled back to prev desc
			assert.Equal(t, sum1Bkts[0].Description, bkt.Description)

			ch, err := l.CheckService().FindCheckByID(ctx, sum1Checks[0].Check.GetID())
			require.NoError(t, err)
			ch.SetOwnerID(0)
			deadman, ok := ch.(*check.Threshold)
			require.True(t, ok)
			// validate the change to query is not persisting returned to previous state.
			// not checking entire bits, b/c we dont' save userID and so forth and makes a
			// direct comparison very annoying...
			assert.Equal(t, sum1Checks[0].Check.(*check.Threshold).Query.Text, deadman.Query.Text)

			label, err := l.LabelService(t).FindLabelByID(ctx, platform.ID(sum1Labels[0].ID))
			require.NoError(t, err)
			assert.Equal(t, sum1Labels[0].Properties.Description, label.Properties["description"])

			endpoint, err := l.NotificationEndpointService(t).FindNotificationEndpointByID(ctx, sum1Endpoints[0].NotificationEndpoint.GetID())
			require.NoError(t, err)
			assert.Equal(t, sum1Endpoints[0].NotificationEndpoint.GetDescription(), endpoint.GetDescription())

			v, err := l.VariableService(t).FindVariableByID(ctx, platform.ID(sum1Vars[0].ID))
			require.NoError(t, err)
			assert.Equal(t, sum1Vars[0].Description, v.Description)
		})
	})

	t.Run("apply a task template with a complex query", func(t *testing.T) {
		// validates bug: https://github.com/influxdata/influxdb/issues/17069

		pkgStr := fmt.Sprintf(`
apiVersion: %[1]s
kind: Task
metadata:
    name: http-post-synthetic
spec:
    name: Http.POST Synthetic (POST)
    every: 5m
    query: |-
        import "strings"
        import "csv"
        import "http"
        import "system"

        timeDiff = (t1, t2) => {
        	return duration(v: uint(v: t2) - uint(v: t1))
        }
        timeDiffNum = (t1, t2) => {
        	return uint(v: t2) - uint(v: t1)
        }
        urlToPost = "http://www.duckduckgo.com"
        timeBeforeCall = system.time()
        responseCode = http.post(url: urlToPost, data: bytes(v: "influxdata"))
        timeAfterCall = system.time()
        responseTime = timeDiff(t1: timeBeforeCall, t2: timeAfterCall)
        responseTimeNum = timeDiffNum(t1: timeBeforeCall, t2: timeAfterCall)
        data = "#group,false,false,true,true,true,true,true,true
        #datatype,string,long,string,string,string,string,string,string
        #default,mean,,,,,,,
        ,result,table,service,response_code,time_before,time_after,response_time_duration,response_time_ns
        ,,0,http_post_ping,${string(v: responseCode)},${string(v: timeBeforeCall)},${string(v: timeAfterCall)},${string(v: responseTime)},${string(v: responseTimeNum)}"
        theTable = csv.from(csv: data)

        theTable
        	|> map(fn: (r) =>
        		({r with _time: now()}))
        	|> map(fn: (r) =>
        		({r with _measurement: "PingService", url: urlToPost, method: "POST"}))
        	|> drop(columns: ["time_before", "time_after", "response_time_duration"])
        	|> to(bucket: "Pingpire", orgID: "039346c3777a1000", fieldFn: (r) =>
        		({"responseCode": r.response_code, "responseTime": int(v: r.response_time_ns)}))
`, pkger.APIVersion)

		pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr))
		require.NoError(t, err)

		impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg))
		require.NoError(t, err)
		assert.NotZero(t, impact.StackID)

		require.Len(t, impact.Summary.Tasks, 1)
	})

	t.Run("applying a template with an invalid template returns parser errors", func(t *testing.T) {
		stack, cleanup := newStackFn(t, pkger.StackCreate{})
		defer cleanup()

		template := newTemplate(newTelegrafObject("with_underscore-is-bad", "", ""))

		_, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
			pkger.ApplyWithTemplate(template),
			pkger.ApplyWithStackID(stack.ID),
		)
		require.Error(t, err)
		require.True(t, pkger.IsParseErr(err))

		vErrs := err.(pkger.ParseError).ValidationErrs()
		require.Len(t, vErrs, 1)

		// this check is to make sure we aren't creating duplicate error messages like
		// was witnessed from CLI recently.
		require.Equal(t, 1, strings.Count(err.Error(), "DNS-1123"))
	})

	t.Run("applying a pkg without a stack will have a stack created for it", func(t *testing.T) {
		pkg := newTemplate(newBucketObject("room", "for", "more"))

		impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg))
		require.NoError(t, err)

		require.NotZero(t, impact.StackID)
		defer deleteStackFn(t, impact.StackID)

		require.Len(t, impact.Summary.Buckets, 1)
		require.NotZero(t, impact.Summary.Buckets[0].ID)

		stacks, err := svc.ListStacks(ctx, l.Org.ID, pkger.ListFilter{
			StackIDs: []platform.ID{impact.StackID},
		})
		require.NoError(t, err)

		require.Len(t, stacks, 1)
		ev := stacks[0].LatestEvent()
		require.Len(t, ev.Resources, 1)
		assert.Equal(t, ev.Resources[0].MetaName, "room")
		assert.Equal(t, platform.ID(impact.Summary.Buckets[0].ID), ev.Resources[0].ID)
	})

	t.Run("apply a template with env refs", func(t *testing.T) {
		pkgStr := fmt.Sprintf(`
apiVersion: %[1]s
kind: Bucket
metadata:
  name:
    envRef:
      key: "bkt-1-name-ref"
spec:
  associations:
    - kind: Label
      name:
        envRef:
          key: label-1-name-ref
---
apiVersion: %[1]s
kind: Label
metadata:
  name:
    envRef:
      key: "label-1-name-ref"
spec:
---
apiVersion: influxdata.com/v2alpha1
kind: CheckDeadman
metadata:
  name:
    envRef:
      key: check-1-name-ref
spec:
  every: 5m
  level: cRiT
  query:  >
    from(bucket: "rucket_1") |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
---
apiVersion: influxdata.com/v2alpha1
kind: Dashboard
metadata:
  name:
    envRef:
      key: dash-1-name-ref
spec:
---
apiVersion: influxdata.com/v2alpha1
kind: NotificationEndpointSlack
metadata:
  name:
    envRef:
      key: endpoint-1-name-ref
spec:
  url: https://hooks.slack.com/services/bip/piddy/boppidy
---
apiVersion: influxdata.com/v2alpha1
kind: NotificationRule
metadata:
  name:
    envRef:
      key: rule-1-name-ref
spec:
  endpointName:
    envRef:
      key: endpoint-1-name-ref
  every: 10m
  messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
  statusRules:
    - currentLevel: WARN
---
apiVersion: influxdata.com/v2alpha1
kind: Telegraf
metadata:
  name:
    envRef:
      key: telegraf-1-name-ref
spec:
  config: |
    [agent]
      interval = "10s"
---
apiVersion: influxdata.com/v2alpha1
kind: Task
metadata:
  name:
    envRef:
      key: task-1-name-ref
spec:
  cron: 15 * * * *
  query:  >
    from(bucket: "rucket_1")
---
apiVersion: influxdata.com/v2alpha1
kind: Variable
metadata:
  name:
    envRef:
      key: var-1-name-ref
spec:
  type: constant
  values: [first val]
`, pkger.APIVersion)

		pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgStr))
		require.NoError(t, err)

		impact, err := svc.DryRun(ctx, l.Org.ID, l.User.ID, pkger.ApplyWithTemplate(pkg))
		require.NoError(t, err)
		assert.Zero(t, impact.StackID)

		sum := impact.Summary

		require.Len(t, sum.Buckets, 1)
		assert.Equal(t, "env-bkt-1-name-ref", sum.Buckets[0].Name)
		assert.Len(t, sum.Buckets[0].LabelAssociations, 1)
		require.Len(t, sum.Checks, 1)
		assert.Equal(t, "env-check-1-name-ref", sum.Checks[0].Check.GetName())
		require.Len(t, sum.Dashboards, 1)
		assert.Equal(t, "env-dash-1-name-ref", sum.Dashboards[0].Name)
		require.Len(t, sum.Labels, 1)
		assert.Equal(t, "env-label-1-name-ref", sum.Labels[0].Name)
		require.Len(t, sum.NotificationEndpoints, 1)
		assert.Equal(t, "env-endpoint-1-name-ref", sum.NotificationEndpoints[0].NotificationEndpoint.GetName())
		require.Len(t, sum.NotificationRules, 1)
		assert.Equal(t, "env-rule-1-name-ref", sum.NotificationRules[0].Name)
		require.Len(t, sum.TelegrafConfigs, 1)
		assert.Equal(t, "env-task-1-name-ref", sum.Tasks[0].Name)
		require.Len(t, sum.TelegrafConfigs, 1)
		assert.Equal(t, "env-telegraf-1-name-ref", sum.TelegrafConfigs[0].TelegrafConfig.Name)
		require.Len(t, sum.Variables, 1)
		assert.Equal(t, "env-var-1-name-ref", sum.Variables[0].Name)

		expectedMissingEnvs := []string{
			"bkt-1-name-ref",
			"check-1-name-ref",
			"dash-1-name-ref",
			"endpoint-1-name-ref",
			"label-1-name-ref",
			"rule-1-name-ref",
			"task-1-name-ref",
			"telegraf-1-name-ref",
			"var-1-name-ref",
		}
		assert.Equal(t, expectedMissingEnvs, sum.MissingEnvs)

		impact, err = svc.Apply(ctx, l.Org.ID, l.User.ID,
			pkger.ApplyWithTemplate(pkg),
			pkger.ApplyWithEnvRefs(map[string]interface{}{
				"bkt-1-name-ref":      "rucket_threeve",
				"check-1-name-ref":    "check_threeve",
				"dash-1-name-ref":     "dash_threeve",
				"endpoint-1-name-ref": "endpoint_threeve",
				"label-1-name-ref":    "label_threeve",
				"rule-1-name-ref":     "rule_threeve",
				"telegraf-1-name-ref": "telegraf_threeve",
				"task-1-name-ref":     "task_threeve",
				"var-1-name-ref":      "var_threeve",
			}),
		)
		require.NoError(t, err)
		assert.NotZero(t, impact.StackID)

		sum = impact.Summary

		assert.Equal(t, "rucket_threeve", sum.Buckets[0].Name)
		assert.Equal(t, "check_threeve", sum.Checks[0].Check.GetName())
		assert.Equal(t, "dash_threeve", sum.Dashboards[0].Name)
		assert.Equal(t, "endpoint_threeve", sum.NotificationEndpoints[0].NotificationEndpoint.GetName())
		assert.Equal(t, "label_threeve", sum.Labels[0].Name)
		assert.Equal(t, "rule_threeve", sum.NotificationRules[0].Name)
		assert.Equal(t, "endpoint_threeve", sum.NotificationRules[0].EndpointMetaName)
		assert.Equal(t, "telegraf_threeve", sum.TelegrafConfigs[0].TelegrafConfig.Name)
		assert.Equal(t, "task_threeve", sum.Tasks[0].Name)
		assert.Equal(t, "var_threeve", sum.Variables[0].Name)
		assert.Empty(t, sum.MissingEnvs)
	})

	t.Run("apply a template with query refs", func(t *testing.T) {
		t.Run("dashboard", func(t *testing.T) {
			dashName := "dash-1"
			newDashTmpl := func(t *testing.T) *pkger.Template {
				t.Helper()

				tmplStr := `
apiVersion: influxdata.com/v2alpha1
kind: Dashboard
metadata:
  name: %s
spec:
  charts:
    - kind: Single_Stat
      name: single stat
      xPos: 1
      yPos: 2
      width: 6
      height: 3
      queries:
        - query: |
            option params = {
              bucket:   "foo",
              start:    -1d,
              stop:     now(),
              name:     "max",
              floatVal: 1.0,
              minVal:   10
            }

            from(bucket: params.bucket)
              |> range(start: params.start, end: params.stop)
              |> filter(fn: (r) => r._measurement == "processes")
              |> filter(fn: (r) => r.floater == params.floatVal)
              |> filter(fn: (r) => r._value > params.minVal)
              |> aggregateWindow(every: v.windowPeriod, fn: max)
              |> yield(name: params.name)

          params:
            - key: bucket
              default: "bar"
              type: string
            - key: start
              type: duration
            - key: stop
              type: time
            - key: floatVal
              default: 37.2
              type: float
            - key: minVal
              type: int
            - key: name # infer type
      colors:
        - name: laser
          type: text
          hex: "#8F8AF4"
          value: 3`
				tmplStr = fmt.Sprintf(tmplStr, dashName)

				template, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(tmplStr))
				require.NoError(t, err)
				return template
			}

			isExpectedQuery := func(t *testing.T, actual pkger.SummaryDashboard, expectedParams string) {
				t.Helper()

				require.Len(t, actual.Charts, 1)

				props, ok := actual.Charts[0].Properties.(influxdb.SingleStatViewProperties)
				require.True(t, ok, "unexpected chart properties")

				require.Len(t, props.Queries, 1)

				expectedQuery := expectedParams + `

from(bucket: params.bucket)
    |> range(start: params.start, end: params.stop)
    |> filter(fn: (r) => r._measurement == "processes")
    |> filter(fn: (r) => r.floater == params.floatVal)
    |> filter(fn: (r) => r._value > params.minVal)
    |> aggregateWindow(every: v.windowPeriod, fn: max)
    |> yield(name: params.name)
`

				assert.Equal(t, expectedQuery, props.Queries[0].Text)
				assert.Equal(t, "advanced", props.Queries[0].EditMode)
			}

			envKey := func(paramKey string) string {
				return fmt.Sprintf(
					"dashboards[%s].spec.charts[0].queries[0].params.%s",
					dashName,
					paramKey,
				)
			}

			t.Run("using default values", func(t *testing.T) {
				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithStackID(stack.ID),
					pkger.ApplyWithTemplate(newDashTmpl(t)),
				)
				require.NoError(t, err)

				require.Len(t, impact.Summary.Dashboards, 1)

				actual := impact.Summary.Dashboards[0]

				expectedParams := `option params = {
    bucket: "bar",
    start: -24h0m0s,
    stop: now(),
    name: "max",
    floatVal: 37.2,
    minVal: 10,
}`
				isExpectedQuery(t, actual, expectedParams)

				require.Len(t, actual.EnvReferences, 6)

				expectedRefs := []pkger.SummaryReference{
					{
						Field:        "spec.charts[0].queries[0].params.bucket",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.bucket`,
						ValType:      "string",
						DefaultValue: "bar",
					},
					{
						Field:        "spec.charts[0].queries[0].params.floatVal",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.floatVal`,
						ValType:      "float",
						DefaultValue: 37.2,
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)])

				// check necessary since json can flip int to float type and fail assertions
				// in a flakey manner
				expectedIntRef := pkger.SummaryReference{
					Field:        "spec.charts[0].queries[0].params.minVal",
					EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.minVal`,
					ValType:      "integer",
					DefaultValue: int64(10),
				}
				actualIntRef := actual.EnvReferences[len(expectedRefs)]
				if f, ok := actualIntRef.DefaultValue.(float64); ok {
					actualIntRef.DefaultValue = int64(f)
				}
				assert.Equal(t, expectedIntRef, actualIntRef)

				expectedRefs = []pkger.SummaryReference{
					{
						Field:        "spec.charts[0].queries[0].params.name",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.name`,
						ValType:      "string",
						DefaultValue: "max",
					},
					{
						Field:        "spec.charts[0].queries[0].params.start",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.start`,
						ValType:      "duration",
						DefaultValue: "-24h0m0s",
					},
					{
						Field:        "spec.charts[0].queries[0].params.stop",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.stop`,
						ValType:      "time",
						DefaultValue: "now()",
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[3:])
			})

			t.Run("with user provided values", func(t *testing.T) {
				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithStackID(stack.ID),
					pkger.ApplyWithTemplate(newDashTmpl(t)),
					pkger.ApplyWithEnvRefs(map[string]interface{}{
						envKey("bucket"):   "foobar",
						envKey("name"):     "min",
						envKey("start"):    "-5d",
						envKey("floatVal"): 33.3,
						envKey("minVal"):   3,
					}),
				)
				require.NoError(t, err)

				require.Len(t, impact.Summary.Dashboards, 1)

				actual := impact.Summary.Dashboards[0]

				expectedParams := `option params = {
    bucket: "foobar",
    start: -5d,
    stop: now(),
    name: "min",
    floatVal: 33.3,
    minVal: 3,
}`
				isExpectedQuery(t, actual, expectedParams)

				require.Len(t, actual.EnvReferences, 6)

				expectedRefs := []pkger.SummaryReference{
					{
						Field:        "spec.charts[0].queries[0].params.bucket",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.bucket`,
						ValType:      "string",
						Value:        "foobar",
						DefaultValue: "bar",
					},
					{
						Field:        "spec.charts[0].queries[0].params.floatVal",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.floatVal`,
						ValType:      "float",
						Value:        33.3,
						DefaultValue: 37.2,
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)])

				// check necessary since json can flip int to float type and fail assertions
				// in a flakey manner
				expectedIntRef := pkger.SummaryReference{
					Field:        "spec.charts[0].queries[0].params.minVal",
					EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.minVal`,
					ValType:      "integer",
					Value:        int64(3),
					DefaultValue: int64(10),
				}
				actualIntRef := actual.EnvReferences[len(expectedRefs)]
				if f, ok := actualIntRef.DefaultValue.(float64); ok {
					actualIntRef.DefaultValue = int64(f)
				}
				if f, ok := actualIntRef.Value.(float64); ok {
					actualIntRef.Value = int64(f)
				}
				assert.Equal(t, expectedIntRef, actualIntRef)

				expectedRefs = []pkger.SummaryReference{
					{
						Field:        "spec.charts[0].queries[0].params.name",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.name`,
						ValType:      "string",
						Value:        "min",
						DefaultValue: "max",
					},
					{
						Field:        "spec.charts[0].queries[0].params.start",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.start`,
						ValType:      "duration",
						Value:        "-5d",
						DefaultValue: "-24h0m0s",
					},
					{
						Field:        "spec.charts[0].queries[0].params.stop",
						EnvRefKey:    `dashboards[dash-1].spec.charts[0].queries[0].params.stop`,
						ValType:      "time",
						DefaultValue: "now()",
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[3:])
			})
		})

		t.Run("task", func(t *testing.T) {
			taskName := "task-1"
			newDashTmpl := func(t *testing.T) *pkger.Template {
				t.Helper()

				tmplStr := `
apiVersion: influxdata.com/v2alpha1
kind: Task
metadata:
  name: %s
spec:
  every: 10m
  query: |
    option params = {
      bucket:   "foo",
      start:    -1d,
      stop:     now(),
      name:     "max",
      floatVal: 1.0,
      minVal:   10
    }

    from(bucket: params.bucket)
      |> range(start: params.start, stop: params.stop)
      |> filter(fn: (r) => r._measurement == "processes")
      |> filter(fn: (r) => r.floater == params.floatVal)
      |> filter(fn: (r) => r._value > params.minVal)
      |> aggregateWindow(every: 1m, fn: max)
      |> yield(name: params.name)

  params:
    - key: bucket
      default: "bar"
      type: string
    - key: start
      type: duration
    - key: stop
      type: time
    - key: floatVal
      default: 37.2
      type: float
    - key: minVal
      type: int
    - key: name # infer type`
				tmplStr = fmt.Sprintf(tmplStr, taskName)

				template, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(tmplStr))
				require.NoError(t, err)
				return template
			}

			isExpectedQuery := func(t *testing.T, actual pkger.SummaryTask, expectedParams string) {
				t.Helper()

				expectedQuery := expectedParams + `

from(bucket: params.bucket)
    |> range(start: params.start, stop: params.stop)
    |> filter(fn: (r) => r._measurement == "processes")
    |> filter(fn: (r) => r.floater == params.floatVal)
    |> filter(fn: (r) => r._value > params.minVal)
    |> aggregateWindow(every: 1m, fn: max)
    |> yield(name: params.name)
`

				assert.Equal(t, expectedQuery, actual.Query)
			}

			envKey := func(paramKey string) string {
				return fmt.Sprintf(
					"tasks[%s].spec.params.%s",
					taskName,
					paramKey,
				)
			}

			t.Run("using default values", func(t *testing.T) {
				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithStackID(stack.ID),
					pkger.ApplyWithTemplate(newDashTmpl(t)),
				)
				require.NoError(t, err)

				require.Len(t, impact.Summary.Tasks, 1)

				actual := impact.Summary.Tasks[0]

				expectedParams := `option params = {
    bucket: "bar",
    start: -24h0m0s,
    stop: now(),
    name: "max",
    floatVal: 37.2,
    minVal: 10,
}`
				isExpectedQuery(t, actual, expectedParams)

				require.Len(t, actual.EnvReferences, 6)

				expectedRefs := []pkger.SummaryReference{
					{
						Field:        "spec.params.bucket",
						EnvRefKey:    `tasks[task-1].spec.params.bucket`,
						ValType:      "string",
						DefaultValue: "bar",
					},
					{
						Field:        "spec.params.floatVal",
						EnvRefKey:    `tasks[task-1].spec.params.floatVal`,
						ValType:      "float",
						DefaultValue: 37.2,
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)])

				// check necessary since json can flip int to float type and fail assertions
				// in a flakey manner
				expectedIntRef := pkger.SummaryReference{
					Field:        "spec.params.minVal",
					EnvRefKey:    `tasks[task-1].spec.params.minVal`,
					ValType:      "integer",
					DefaultValue: int64(10),
				}
				actualIntRef := actual.EnvReferences[len(expectedRefs)]
				if f, ok := actualIntRef.DefaultValue.(float64); ok {
					actualIntRef.DefaultValue = int64(f)
				}
				assert.Equal(t, expectedIntRef, actualIntRef)

				expectedRefs = []pkger.SummaryReference{
					{
						Field:        "spec.params.name",
						EnvRefKey:    `tasks[task-1].spec.params.name`,
						ValType:      "string",
						DefaultValue: "max",
					},
					{
						Field:        "spec.params.start",
						EnvRefKey:    `tasks[task-1].spec.params.start`,
						ValType:      "duration",
						DefaultValue: "-24h0m0s",
					},
					{
						Field:        "spec.params.stop",
						EnvRefKey:    `tasks[task-1].spec.params.stop`,
						ValType:      "time",
						DefaultValue: "now()",
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[3:])
			})

			t.Run("with user provided values", func(t *testing.T) {
				stack, cleanup := newStackFn(t, pkger.StackCreate{})
				defer cleanup()

				impact, err := svc.Apply(ctx, l.Org.ID, l.User.ID,
					pkger.ApplyWithStackID(stack.ID),
					pkger.ApplyWithTemplate(newDashTmpl(t)),
					pkger.ApplyWithEnvRefs(map[string]interface{}{
						envKey("bucket"):   "foobar",
						envKey("name"):     "min",
						envKey("start"):    "-5d",
						envKey("floatVal"): 33.3,
						envKey("minVal"):   3,
					}),
				)
				require.NoError(t, err)

				require.Len(t, impact.Summary.Tasks, 1)

				actual := impact.Summary.Tasks[0]

				expectedParams := `option params = {
    bucket: "foobar",
    start: -5d,
    stop: now(),
    name: "min",
    floatVal: 33.3,
    minVal: 3,
}`
				isExpectedQuery(t, actual, expectedParams)

				require.Len(t, actual.EnvReferences, 6)

				expectedRefs := []pkger.SummaryReference{
					{
						Field:        "spec.params.bucket",
						EnvRefKey:    `tasks[task-1].spec.params.bucket`,
						ValType:      "string",
						Value:        "foobar",
						DefaultValue: "bar",
					},
					{
						Field:        "spec.params.floatVal",
						EnvRefKey:    `tasks[task-1].spec.params.floatVal`,
						ValType:      "float",
						Value:        33.3,
						DefaultValue: 37.2,
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[:len(expectedRefs)])

				// check necessary since json can flip int to float type and fail assertions
				// in a flakey manner
				expectedIntRef := pkger.SummaryReference{
					Field:        "spec.params.minVal",
					EnvRefKey:    `tasks[task-1].spec.params.minVal`,
					ValType:      "integer",
					Value:        int64(3),
					DefaultValue: int64(10),
				}
				actualIntRef := actual.EnvReferences[len(expectedRefs)]
				if f, ok := actualIntRef.DefaultValue.(float64); ok {
					actualIntRef.DefaultValue = int64(f)
				}
				if f, ok := actualIntRef.Value.(float64); ok {
					actualIntRef.Value = int64(f)
				}
				assert.Equal(t, expectedIntRef, actualIntRef)

				expectedRefs = []pkger.SummaryReference{
					{
						Field:        "spec.params.name",
						EnvRefKey:    `tasks[task-1].spec.params.name`,
						ValType:      "string",
						Value:        "min",
						DefaultValue: "max",
					},
					{
						Field:        "spec.params.start",
						EnvRefKey:    `tasks[task-1].spec.params.start`,
						ValType:      "duration",
						Value:        "-5d",
						DefaultValue: "-24h0m0s",
					},
					{
						Field:        "spec.params.stop",
						EnvRefKey:    `tasks[task-1].spec.params.stop`,
						ValType:      "time",
						DefaultValue: "now()",
					},
				}
				assert.Equal(t, expectedRefs, actual.EnvReferences[3:])
			})
		})
	})
}

func newCompletePkg(t *testing.T) *pkger.Template {
	t.Helper()

	pkg, err := pkger.Parse(pkger.EncodingYAML, pkger.FromString(pkgYMLStr))
	require.NoError(t, err)
	return pkg
}

const telegrafCfg = `[agent]
  interval = "10s"
  metric_batch_size = 1000
  metric_buffer_limit = 10000
  collection_jitter = "0s"
  flush_interval = "10s"
[[outputs.influxdb_v2]]
  urls = ["http://localhost:8086"]
  token = "$INFLUX_TOKEN"
  organization = "rg"
  bucket = "rucket_3"
[[inputs.cpu]]
  percpu = true
`

var pkgYMLStr = fmt.Sprintf(`
apiVersion: %[1]s
kind: Label
metadata:
  name: label-1
---
apiVersion: %[1]s
kind: Label
metadata:
  name: the-2nd-label
spec:
  name: the 2nd label
---
apiVersion: %[1]s
kind: Bucket
metadata:
  name: rucket-1
spec:
  name: rucketeer
  associations:
    - kind: Label
      name: label-1
    - kind: Label
      name: the-2nd-label
---
apiVersion: %[1]s
kind: Dashboard
metadata:
  name: dash-uuid
spec:
  name: dash_1
  description: desc1
  charts:
    - kind:   Single_Stat
      name:   single stat
      suffix: days
      width:  6
      height: 3
      shade: true
      queries:
        - query: >
            from(bucket: v.bucket) |> range(start: v.timeRangeStart) |> filter(fn: (r) => r._measurement == "system") |> filter(fn: (r) => r._field == "uptime") |> last() |> map(fn: (r) => ({r with _value: r._value / 86400})) |> yield(name: "last")
      colors:
        - name: laser
          type: text
          hex: "#8F8AF4"
  associations:
    - kind: Label
      name: label-1
    - kind: Label
      name: the-2nd-label
---
apiVersion: %[1]s
kind: Variable
metadata:
  name:  var-query-1
spec:
  name: query var
  description: var_query_1 desc
  type: query
  language: flux
  query: |
    buckets()  |> filter(fn: (r) => r.name !~ /^_/)  |> rename(columns: {name: "_value"})  |> keep(columns: ["_value"])
  selected:
    - rucketeer
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: Telegraf
metadata:
  name:  first-tele-config
spec:
  name: first tele config
  description: desc
  associations:
    - kind: Label
      name: label-1
  config: %+q
---
apiVersion: %[1]s
kind: NotificationEndpointHTTP
metadata:
  name:  http-none-auth-notification-endpoint # on export of resource created from this, will not be same name as this
spec:
  name: no auth endpoint
  type: none
  description: http none auth desc
  method: GET
  url:  https://www.example.com/endpoint/noneauth
  status: inactive
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: CheckThreshold
metadata:
  name:  check-0
spec:
  name: check 0 name
  every: 1m
  query:  >
    from(bucket: "rucket_1")
      |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
      |> filter(fn: (r) => r._measurement == "cpu")
      |> filter(fn: (r) => r._field == "usage_idle")
      |> aggregateWindow(every: 1m, fn: mean)
      |> yield(name: "mean")
  statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
  tags:
    - key: tag_1
      value: val_1
  thresholds:
    - type: inside_range
      level: INfO
      min: 30.0
      max: 45.0
    - type: outside_range
      level: WARN
      min: 60.0
      max: 70.0
    - type: greater
      level: CRIT
      val: 80
    - type: lesser
      level: OK
      val: 30
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: CheckDeadman
metadata:
  name:  check-1
spec:
  description: desc_1
  every: 5m
  level: cRiT
  offset: 10s
  query:  >
    from(bucket: "rucket_1")
      |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
      |> filter(fn: (r) => r._measurement == "cpu")
      |> filter(fn: (r) => r._field == "usage_idle")
      |> aggregateWindow(every: 1m, fn: mean)
      |> yield(name: "mean")
  reportZero: true
  staleTime: 10m
  statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
  timeSince: 90s
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: NotificationRule
metadata:
  name:  rule-uuid
spec:
  name:  rule_0
  description: desc_0
  endpointName: http-none-auth-notification-endpoint
  every: 10m
  offset: 30s
  messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }"
  status: active
  statusRules:
    - currentLevel: WARN
    - currentLevel: CRIT
      previousLevel: OK
  tagRules:
    - key: k1
      value: v2
      operator: eQuAl
    - key: k1
      value: v1
      operator: eQuAl
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: Task
metadata:
  name:  task-uuid
spec:
  name:  task_1
  description: desc_1
  cron: 15 * * * *
  query:  >
    from(bucket: "rucket_1")
      |> yield()
  associations:
    - kind: Label
      name: label-1
`, pkger.APIVersion, telegrafCfg)

var updatePkgYMLStr = fmt.Sprintf(`
apiVersion: %[1]s
kind: Label
metadata:
  name:  label-1
spec:
  descriptin: new desc
---
apiVersion: %[1]s
kind: Bucket
metadata:
  name:  rucket-1
spec:
  descriptin: new desc
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: Variable
metadata:
  name:  var-query-1
spec:
  description: new desc
  type: query
  language: flux
  query: |
    buckets()  |> filter(fn: (r) => r.name !~ /^_/)  |> rename(columns: {name: "_value"})  |> keep(columns: ["_value"])
  associations:
    - kind: Label
      name: label-1
---
apiVersion: %[1]s
kind: NotificationEndpointHTTP
metadata:
  name:  http-none-auth-notification-endpoint
spec:
  name: no auth endpoint
  type: none
  description: new desc
  method: GET
  url:  https://www.example.com/endpoint/noneauth
  status: active
---
apiVersion: %[1]s
kind: CheckThreshold
metadata:
  name:  check-0
spec:
  every: 1m
  query:  >
    from("rucket1") |> yield()
  statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }"
  thresholds:
    - type: inside_range
      level: INfO
      min: 30.0
      max: 45.0
`, pkger.APIVersion)

type fakeBucketSVC struct {
	influxdb.BucketService
	createCallCount mock.SafeCount
	createKillCount int
	updateCallCount mock.SafeCount
	updateKillCount int
}

func (f *fakeBucketSVC) CreateBucket(ctx context.Context, b *influxdb.Bucket) error {
	defer f.createCallCount.IncrFn()()
	if f.createCallCount.Count() == f.createKillCount {
		return errors.New("reached kill count")
	}
	return f.BucketService.CreateBucket(ctx, b)
}

func (f *fakeBucketSVC) UpdateBucket(ctx context.Context, id platform.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) {
	defer f.updateCallCount.IncrFn()()
	if f.updateCallCount.Count() == f.updateKillCount {
		return nil, errors.New("reached kill count")
	}
	return f.BucketService.UpdateBucket(ctx, id, upd)
}

type fakeLabelSVC struct {
	influxdb.LabelService
	createCallCount mock.SafeCount
	createKillCount int

	deleteCallCount mock.SafeCount
	deleteKillCount int
}

func (f *fakeLabelSVC) CreateLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error {
	defer f.createCallCount.IncrFn()()
	if f.createCallCount.Count() == f.createKillCount {
		return errors.New("reached kill count")
	}
	return f.LabelService.CreateLabelMapping(ctx, m)
}

func (f *fakeLabelSVC) DeleteLabelMapping(ctx context.Context, m *influxdb.LabelMapping) error {
	defer f.deleteCallCount.IncrFn()()
	if f.deleteCallCount.Count() == f.deleteKillCount {
		return errors.New("reached kill count")
	}
	return f.LabelService.DeleteLabelMapping(ctx, m)
}

type fakeRuleStore struct {
	influxdb.NotificationRuleStore
	createCallCount mock.SafeCount
	createKillCount int
}

func (f *fakeRuleStore) CreateNotificationRule(ctx context.Context, nr influxdb.NotificationRuleCreate, userID platform.ID) error {
	defer f.createCallCount.IncrFn()()
	if f.createCallCount.Count() == f.createKillCount {
		return errors.New("reached kill count")
	}
	return f.NotificationRuleStore.CreateNotificationRule(ctx, nr, userID)
}

func assertErrorCode(t *testing.T, expected string, err error) {
	t.Helper()
	assert.Error(t, err)
	assert.Equal(t, expected, errors2.ErrorCode(err))
}

type resourceChecker struct {
	tl *TestLauncher
}

func newResourceChecker(tl *TestLauncher) resourceChecker {
	return resourceChecker{tl: tl}
}

type (
	getResourceOpt struct {
		id   platform.ID
		name string
	}

	getResourceOptFn func() getResourceOpt
)

func byID(id platform.ID) getResourceOptFn {
	return func() getResourceOpt {
		return getResourceOpt{id: id}
	}
}

func byName(name string) getResourceOptFn {
	return func() getResourceOpt {
		return getResourceOpt{name: name}
	}
}

func bySafeID(id pkger.SafeID) getResourceOptFn {
	return func() getResourceOpt {
		return getResourceOpt{id: platform.ID(id)}
	}
}

func (r resourceChecker) getBucket(t *testing.T, getOpt getResourceOptFn) (influxdb.Bucket, error) {
	t.Helper()

	bktSVC := r.tl.BucketService(t)

	var (
		bkt *influxdb.Bucket
		err error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		bkt, err = bktSVC.FindBucketByName(ctx, r.tl.Org.ID, opt.name)
	case opt.id != 0:
		bkt, err = bktSVC.FindBucketByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}
	if err != nil {
		return influxdb.Bucket{}, err
	}

	return *bkt, nil
}

func (r resourceChecker) mustGetBucket(t *testing.T, getOpt getResourceOptFn) influxdb.Bucket {
	t.Helper()

	bkt, err := r.getBucket(t, getOpt)
	require.NoError(t, err)
	return bkt
}

func (r resourceChecker) mustDeleteBucket(t *testing.T, id platform.ID) {
	t.Helper()
	require.NoError(t, r.tl.BucketService(t).DeleteBucket(ctx, id))
}

func (r resourceChecker) getCheck(t *testing.T, getOpt getResourceOptFn) (influxdb.Check, error) {
	t.Helper()

	checkSVC := r.tl.CheckService()

	var (
		ch  influxdb.Check
		err error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		ch, err = checkSVC.FindCheck(ctx, influxdb.CheckFilter{
			Name:  &opt.name,
			OrgID: &r.tl.Org.ID,
		})
	case opt.id != 0:
		ch, err = checkSVC.FindCheckByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}

	return ch, err
}

func (r resourceChecker) mustGetCheck(t *testing.T, getOpt getResourceOptFn) influxdb.Check {
	t.Helper()

	c, err := r.getCheck(t, getOpt)
	require.NoError(t, err)
	return c
}

func (r resourceChecker) mustDeleteCheck(t *testing.T, id platform.ID) {
	t.Helper()

	require.NoError(t, r.tl.CheckService().DeleteCheck(ctx, id))
}

func (r resourceChecker) getDashboard(t *testing.T, getOpt getResourceOptFn) (influxdb.Dashboard, error) {
	t.Helper()

	dashSVC := r.tl.DashboardService(t)

	var (
		dashboard *influxdb.Dashboard
		err       error
	)
	opt := getOpt()
	switch {
	case opt.name != "":
		dashs, _, err := dashSVC.FindDashboards(ctx, influxdb.DashboardFilter{}, influxdb.DefaultDashboardFindOptions)
		if err != nil {
			return influxdb.Dashboard{}, err
		}
		for _, d := range dashs {
			if d.Name == opt.name {
				dashboard = d
				break
			}
		}
	case opt.id != 0:
		dashboard, err = dashSVC.FindDashboardByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}
	if err != nil {
		return influxdb.Dashboard{}, err
	}
	if dashboard == nil {
		return influxdb.Dashboard{}, fmt.Errorf("failed to find desired dashboard with opts: %+v", opt)
	}

	return *dashboard, nil
}

func (r resourceChecker) mustGetDashboard(t *testing.T, getOpt getResourceOptFn) influxdb.Dashboard {
	t.Helper()

	dash, err := r.getDashboard(t, getOpt)
	require.NoError(t, err)
	return dash
}

func (r resourceChecker) mustDeleteDashboard(t *testing.T, id platform.ID) {
	t.Helper()

	require.NoError(t, r.tl.DashboardService(t).DeleteDashboard(ctx, id))
}

func (r resourceChecker) getEndpoint(t *testing.T, getOpt getResourceOptFn) (influxdb.NotificationEndpoint, error) {
	t.Helper()

	endpointSVC := r.tl.NotificationEndpointService(t)

	var (
		e   influxdb.NotificationEndpoint
		err error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		var endpoints []influxdb.NotificationEndpoint
		endpoints, _, err = endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{
			OrgID: &r.tl.Org.ID,
		})
		for _, existing := range endpoints {
			if existing.GetName() == opt.name {
				e = existing
				break
			}
		}
	case opt.id != 0:
		e, err = endpointSVC.FindNotificationEndpointByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}

	if e == nil {
		return nil, errors.New("did not find endpoint")
	}

	return e, err
}

func (r resourceChecker) mustGetEndpoint(t *testing.T, getOpt getResourceOptFn) influxdb.NotificationEndpoint {
	t.Helper()

	e, err := r.getEndpoint(t, getOpt)
	require.NoError(t, err)
	return e
}

func (r resourceChecker) mustDeleteEndpoint(t *testing.T, id platform.ID) {
	t.Helper()

	_, _, err := r.tl.NotificationEndpointService(t).DeleteNotificationEndpoint(ctx, id)
	require.NoError(t, err)
}

func (r resourceChecker) getLabel(t *testing.T, getOpt getResourceOptFn) (influxdb.Label, error) {
	t.Helper()

	labelSVC := r.tl.LabelService(t)

	var (
		label *influxdb.Label
		err   error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		labels, err := labelSVC.FindLabels(
			ctx,
			influxdb.LabelFilter{
				Name:  opt.name,
				OrgID: &r.tl.Org.ID,
			},
			influxdb.FindOptions{Limit: 1},
		)
		if err != nil {
			return influxdb.Label{}, err
		}
		if len(labels) == 0 {
			return influxdb.Label{}, errors.New("did not find label: " + opt.name)
		}
		label = labels[0]
	case opt.id != 0:
		label, err = labelSVC.FindLabelByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}
	if err != nil {
		return influxdb.Label{}, err
	}

	return *label, nil
}

func (r resourceChecker) mustGetLabel(t *testing.T, getOpt getResourceOptFn) influxdb.Label {
	t.Helper()

	l, err := r.getLabel(t, getOpt)
	require.NoError(t, err)
	return l
}

func (r resourceChecker) mustDeleteLabel(t *testing.T, id platform.ID) {
	t.Helper()
	require.NoError(t, r.tl.LabelService(t).DeleteLabel(ctx, id))
}

func (r resourceChecker) getRule(t *testing.T, getOpt getResourceOptFn) (influxdb.NotificationRule, error) {
	t.Helper()

	ruleSVC := r.tl.NotificationRuleService(t)

	var (
		rule influxdb.NotificationRule
		err  error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		var rules []influxdb.NotificationRule
		rules, _, err = ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{
			OrgID: &r.tl.Org.ID,
		})
		for _, existing := range rules {
			if existing.GetName() == opt.name {
				rule = existing
				break
			}
		}
	case opt.id != 0:
		rule, err = ruleSVC.FindNotificationRuleByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}

	if rule == nil {
		return nil, errors.New("did not find rule")
	}

	return rule, err
}

func (r resourceChecker) mustGetRule(t *testing.T, getOpt getResourceOptFn) influxdb.NotificationRule {
	t.Helper()

	rule, err := r.getRule(t, getOpt)
	require.NoError(t, err)
	return rule
}

func (r resourceChecker) mustDeleteRule(t *testing.T, id platform.ID) {
	t.Helper()

	require.NoError(t, r.tl.NotificationRuleService(t).DeleteNotificationRule(ctx, id))
}

func (r resourceChecker) getTask(t *testing.T, getOpt getResourceOptFn) (taskmodel.Task, error) {
	t.Helper()

	taskSVC := r.tl.TaskService(t)

	var (
		task *taskmodel.Task
		err  error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		tasks, _, err := taskSVC.FindTasks(ctx, taskmodel.TaskFilter{
			Name:           &opt.name,
			OrganizationID: &r.tl.Org.ID,
		})
		if err != nil {
			return taskmodel.Task{}, err
		}
		for _, tt := range tasks {
			if tt.Name == opt.name {
				task = tasks[0]
				break
			}
		}
	case opt.id != 0:
		task, err = taskSVC.FindTaskByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide a valid get option")
	}
	if task == nil {
		return taskmodel.Task{}, errors.New("did not find expected task by name")
	}

	return *task, err
}

func (r resourceChecker) mustGetTask(t *testing.T, getOpt getResourceOptFn) taskmodel.Task {
	t.Helper()

	task, err := r.getTask(t, getOpt)
	require.NoError(t, err)
	return task
}

func (r resourceChecker) mustDeleteTask(t *testing.T, id platform.ID) {
	t.Helper()

	require.NoError(t, r.tl.TaskService(t).DeleteTask(ctx, id))
}

func (r resourceChecker) getTelegrafConfig(t *testing.T, getOpt getResourceOptFn) (influxdb.TelegrafConfig, error) {
	t.Helper()

	teleSVC := r.tl.TelegrafService(t)

	var (
		config *influxdb.TelegrafConfig
		err    error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		teles, _, _ := teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{
			OrgID: &r.tl.Org.ID,
		})
		for _, tt := range teles {
			if opt.name != "" && tt.Name == opt.name {
				config = teles[0]
				break
			}
		}
	case opt.id != 0:
		config, err = teleSVC.FindTelegrafConfigByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide a valid get option")
	}
	if config == nil {
		return influxdb.TelegrafConfig{}, errors.New("did not find expected telegraf by name")
	}

	return *config, err
}

func (r resourceChecker) mustGetTelegrafConfig(t *testing.T, getOpt getResourceOptFn) influxdb.TelegrafConfig {
	t.Helper()

	tele, err := r.getTelegrafConfig(t, getOpt)
	require.NoError(t, err)
	return tele
}

func (r resourceChecker) mustDeleteTelegrafConfig(t *testing.T, id platform.ID) {
	t.Helper()

	require.NoError(t, r.tl.TelegrafService(t).DeleteTelegrafConfig(ctx, id))
}

func (r resourceChecker) getVariable(t *testing.T, getOpt getResourceOptFn) (influxdb.Variable, error) {
	t.Helper()

	varSVC := r.tl.VariableService(t)

	var (
		variable *influxdb.Variable
		err      error
	)
	switch opt := getOpt(); {
	case opt.name != "":
		vars, err := varSVC.FindVariables(ctx, influxdb.VariableFilter{
			OrganizationID: &r.tl.Org.ID,
		})
		if err != nil {
			return influxdb.Variable{}, err
		}
		for i := range vars {
			v := vars[i]
			if v.Name == opt.name {
				variable = v
				break
			}
		}
		if variable == nil {
			return influxdb.Variable{}, errors.New("did not find variable: " + opt.name)
		}
	case opt.id != 0:
		variable, err = varSVC.FindVariableByID(ctx, opt.id)
	default:
		require.Fail(t, "did not provide any get option")
	}
	if err != nil {
		return influxdb.Variable{}, err
	}
	return *variable, nil
}

func (r resourceChecker) mustGetVariable(t *testing.T, getOpt getResourceOptFn) influxdb.Variable {
	t.Helper()

	l, err := r.getVariable(t, getOpt)
	require.NoError(t, err)
	return l
}

func (r resourceChecker) mustDeleteVariable(t *testing.T, id platform.ID) {
	t.Helper()

	err := r.tl.VariableService(t).DeleteVariable(ctx, id)
	require.NoError(t, err)
}

func sortChecks(checks []pkger.SummaryCheck) {
	sort.Slice(checks, func(i, j int) bool {
		return checks[i].Check.GetName() < checks[j].Check.GetName()
	})
}

func sortLabels(labels []pkger.SummaryLabel) {
	sort.Slice(labels, func(i, j int) bool {
		return labels[i].Name < labels[j].Name
	})
}

func strPtr(s string) *string {
	return &s
}
