github.com/kevinklinger/open_terraform@v1.3.6/noninternal/checks/state_test.go (about) 1 package checks 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/kevinklinger/open_terraform/noninternal/addrs" 9 "github.com/kevinklinger/open_terraform/noninternal/configs/configload" 10 "github.com/kevinklinger/open_terraform/noninternal/initwd" 11 ) 12 13 func TestChecksHappyPath(t *testing.T) { 14 const fixtureDir = "testdata/happypath" 15 loader, close := configload.NewLoaderForTests(t) 16 defer close() 17 inst := initwd.NewModuleInstaller(loader.ModulesDir(), nil) 18 _, instDiags := inst.InstallModules(context.Background(), fixtureDir, true, initwd.ModuleInstallHooksImpl{}) 19 if instDiags.HasErrors() { 20 t.Fatal(instDiags.Err()) 21 } 22 if err := loader.RefreshModules(); err != nil { 23 t.Fatalf("failed to refresh modules after installation: %s", err) 24 } 25 26 ///////////////////////////////////////////////////////////////////////// 27 28 cfg, hclDiags := loader.LoadConfig(fixtureDir) 29 if hclDiags.HasErrors() { 30 t.Fatalf("invalid configuration: %s", hclDiags.Error()) 31 } 32 33 resourceA := addrs.Resource{ 34 Mode: addrs.ManagedResourceMode, 35 Type: "null_resource", 36 Name: "a", 37 }.InModule(addrs.RootModule) 38 resourceNoChecks := addrs.Resource{ 39 Mode: addrs.ManagedResourceMode, 40 Type: "null_resource", 41 Name: "no_checks", 42 }.InModule(addrs.RootModule) 43 resourceNonExist := addrs.Resource{ 44 Mode: addrs.ManagedResourceMode, 45 Type: "null_resource", 46 Name: "nonexist", 47 }.InModule(addrs.RootModule) 48 rootOutput := addrs.OutputValue{ 49 Name: "a", 50 }.InModule(addrs.RootModule) 51 moduleChild := addrs.RootModule.Child("child") 52 resourceB := addrs.Resource{ 53 Mode: addrs.ManagedResourceMode, 54 Type: "null_resource", 55 Name: "b", 56 }.InModule(moduleChild) 57 resourceC := addrs.Resource{ 58 Mode: addrs.ManagedResourceMode, 59 Type: "null_resource", 60 Name: "c", 61 }.InModule(moduleChild) 62 childOutput := addrs.OutputValue{ 63 Name: "b", 64 }.InModule(moduleChild) 65 66 // First some consistency checks to make sure our configuration is the 67 // shape we are relying on it to be. 68 if addr := resourceA; cfg.Module.ResourceByAddr(addr.Resource) == nil { 69 t.Fatalf("configuration does not include %s", addr) 70 } 71 if addr := resourceB; cfg.Children["child"].Module.ResourceByAddr(addr.Resource) == nil { 72 t.Fatalf("configuration does not include %s", addr) 73 } 74 if addr := resourceNoChecks; cfg.Module.ResourceByAddr(addr.Resource) == nil { 75 t.Fatalf("configuration does not include %s", addr) 76 } 77 if addr := resourceNonExist; cfg.Module.ResourceByAddr(addr.Resource) != nil { 78 t.Fatalf("configuration includes %s, which is not supposed to exist", addr) 79 } 80 81 ///////////////////////////////////////////////////////////////////////// 82 83 checks := NewState(cfg) 84 85 missing := 0 86 if addr := resourceA; !checks.ConfigHasChecks(addr) { 87 t.Errorf("checks not detected for %s", addr) 88 missing++ 89 } 90 if addr := resourceB; !checks.ConfigHasChecks(addr) { 91 t.Errorf("checks not detected for %s", addr) 92 missing++ 93 } 94 if addr := resourceC; !checks.ConfigHasChecks(addr) { 95 t.Errorf("checks not detected for %s", addr) 96 missing++ 97 } 98 if addr := rootOutput; !checks.ConfigHasChecks(addr) { 99 t.Errorf("checks not detected for %s", addr) 100 missing++ 101 } 102 if addr := childOutput; !checks.ConfigHasChecks(addr) { 103 t.Errorf("checks not detected for %s", addr) 104 missing++ 105 } 106 if addr := resourceNoChecks; checks.ConfigHasChecks(addr) { 107 t.Errorf("checks detected for %s, even though it has none", addr) 108 } 109 if addr := resourceNonExist; checks.ConfigHasChecks(addr) { 110 t.Errorf("checks detected for %s, even though it doesn't exist", addr) 111 } 112 if missing > 0 { 113 t.Fatalf("missing some configuration objects we'd need for subsequent testing") 114 } 115 116 ///////////////////////////////////////////////////////////////////////// 117 118 // Everything should start with status unknown. 119 120 { 121 wantConfigAddrs := addrs.MakeSet[addrs.ConfigCheckable]( 122 resourceA, 123 resourceB, 124 resourceC, 125 rootOutput, 126 childOutput, 127 ) 128 gotConfigAddrs := checks.AllConfigAddrs() 129 if diff := cmp.Diff(wantConfigAddrs, gotConfigAddrs); diff != "" { 130 t.Errorf("wrong detected config addresses\n%s", diff) 131 } 132 133 for _, configAddr := range gotConfigAddrs { 134 if got, want := checks.AggregateCheckStatus(configAddr), StatusUnknown; got != want { 135 t.Errorf("incorrect initial aggregate check status for %s: %s, but want %s", configAddr, got, want) 136 } 137 } 138 } 139 140 ///////////////////////////////////////////////////////////////////////// 141 142 // The following are steps that would normally be done by Terraform Core 143 // as part of visiting checkable objects during the graph walk. We're 144 // simulating a likely sequence of calls here for testing purposes, but 145 // Terraform Core won't necessarily visit all of these in exactly the 146 // same order every time and so this is just one possible valid ordering 147 // of calls. 148 149 resourceInstA := resourceA.Resource.Absolute(addrs.RootModuleInstance).Instance(addrs.NoKey) 150 rootOutputInst := rootOutput.OutputValue.Absolute(addrs.RootModuleInstance) 151 moduleChildInst := addrs.RootModuleInstance.Child("child", addrs.NoKey) 152 resourceInstB := resourceB.Resource.Absolute(moduleChildInst).Instance(addrs.NoKey) 153 resourceInstC0 := resourceC.Resource.Absolute(moduleChildInst).Instance(addrs.IntKey(0)) 154 resourceInstC1 := resourceC.Resource.Absolute(moduleChildInst).Instance(addrs.IntKey(1)) 155 childOutputInst := childOutput.OutputValue.Absolute(moduleChildInst) 156 157 checks.ReportCheckableObjects(resourceA, addrs.MakeSet[addrs.Checkable](resourceInstA)) 158 checks.ReportCheckResult(resourceInstA, addrs.ResourcePrecondition, 0, StatusPass) 159 checks.ReportCheckResult(resourceInstA, addrs.ResourcePrecondition, 1, StatusPass) 160 checks.ReportCheckResult(resourceInstA, addrs.ResourcePostcondition, 0, StatusPass) 161 162 checks.ReportCheckableObjects(resourceB, addrs.MakeSet[addrs.Checkable](resourceInstB)) 163 checks.ReportCheckResult(resourceInstB, addrs.ResourcePrecondition, 0, StatusPass) 164 165 checks.ReportCheckableObjects(resourceC, addrs.MakeSet[addrs.Checkable](resourceInstC0, resourceInstC1)) 166 checks.ReportCheckResult(resourceInstC0, addrs.ResourcePostcondition, 0, StatusPass) 167 checks.ReportCheckResult(resourceInstC1, addrs.ResourcePostcondition, 0, StatusPass) 168 169 checks.ReportCheckableObjects(childOutput, addrs.MakeSet[addrs.Checkable](childOutputInst)) 170 checks.ReportCheckResult(childOutputInst, addrs.OutputPrecondition, 0, StatusPass) 171 172 checks.ReportCheckableObjects(rootOutput, addrs.MakeSet[addrs.Checkable](rootOutputInst)) 173 checks.ReportCheckResult(rootOutputInst, addrs.OutputPrecondition, 0, StatusPass) 174 175 ///////////////////////////////////////////////////////////////////////// 176 177 // This "section" is simulating what we might do to report the results 178 // of the checks after a run completes. 179 180 { 181 configCount := 0 182 for _, configAddr := range checks.AllConfigAddrs() { 183 configCount++ 184 if got, want := checks.AggregateCheckStatus(configAddr), StatusPass; got != want { 185 t.Errorf("incorrect final aggregate check status for %s: %s, but want %s", configAddr, got, want) 186 } 187 } 188 if got, want := configCount, 5; got != want { 189 t.Errorf("incorrect number of known config addresses %d; want %d", got, want) 190 } 191 } 192 193 { 194 objAddrs := addrs.MakeSet[addrs.Checkable]( 195 resourceInstA, 196 rootOutputInst, 197 resourceInstB, 198 resourceInstC0, 199 resourceInstC1, 200 childOutputInst, 201 ) 202 for _, addr := range objAddrs { 203 if got, want := checks.ObjectCheckStatus(addr), StatusPass; got != want { 204 t.Errorf("incorrect final check status for object %s: %s, but want %s", addr, got, want) 205 } 206 } 207 } 208 }