github.com/opentofu/opentofu@v1.7.1/internal/addrs/target_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package addrs 7 8 import ( 9 "fmt" 10 "testing" 11 ) 12 13 func TestTargetContains(t *testing.T) { 14 for _, test := range []struct { 15 addr, other Targetable 16 expect bool 17 }{ 18 { 19 mustParseTarget("module.foo"), 20 mustParseTarget("module.bar"), 21 false, 22 }, 23 { 24 mustParseTarget("module.foo"), 25 mustParseTarget("module.foo"), 26 true, 27 }, 28 { 29 RootModuleInstance, 30 mustParseTarget("module.foo"), 31 true, 32 }, 33 { 34 mustParseTarget("module.foo"), 35 RootModuleInstance, 36 false, 37 }, 38 { 39 mustParseTarget("module.foo"), 40 mustParseTarget("module.foo.module.bar[0]"), 41 true, 42 }, 43 { 44 mustParseTarget("module.foo"), 45 mustParseTarget("module.foo.module.bar[0]"), 46 true, 47 }, 48 { 49 mustParseTarget("module.foo[2]"), 50 mustParseTarget("module.foo[2].module.bar[0]"), 51 true, 52 }, 53 { 54 mustParseTarget("module.foo"), 55 mustParseTarget("module.foo.test_resource.bar"), 56 true, 57 }, 58 { 59 mustParseTarget("module.foo"), 60 mustParseTarget("module.foo.test_resource.bar[0]"), 61 true, 62 }, 63 64 // Resources 65 { 66 mustParseTarget("test_resource.foo"), 67 mustParseTarget("test_resource.foo[\"bar\"]"), 68 true, 69 }, 70 { 71 mustParseTarget(`test_resource.foo["bar"]`), 72 mustParseTarget(`test_resource.foo["bar"]`), 73 true, 74 }, 75 { 76 mustParseTarget("test_resource.foo"), 77 mustParseTarget("test_resource.foo[2]"), 78 true, 79 }, 80 { 81 mustParseTarget("test_resource.foo"), 82 mustParseTarget("module.bar.test_resource.foo[2]"), 83 false, 84 }, 85 { 86 mustParseTarget("module.bar.test_resource.foo"), 87 mustParseTarget("module.bar.test_resource.foo[2]"), 88 true, 89 }, 90 { 91 mustParseTarget("module.bar.test_resource.foo"), 92 mustParseTarget("module.bar[0].test_resource.foo[2]"), 93 false, 94 }, 95 { 96 mustParseTarget("module.bar.test_resource.foo"), 97 mustParseTarget("module.bar.test_resource.foo[0]"), 98 true, 99 }, 100 { 101 mustParseTarget("module.bax"), 102 mustParseTarget("module.bax[0].test_resource.foo[0]"), 103 true, 104 }, 105 106 // Config paths, while never returned from parsing a target, must still 107 // be targetable 108 { 109 ConfigResource{ 110 Module: []string{"bar"}, 111 Resource: Resource{ 112 Mode: ManagedResourceMode, 113 Type: "test_resource", 114 Name: "foo", 115 }, 116 }, 117 mustParseTarget("module.bar.test_resource.foo[2]"), 118 true, 119 }, 120 { 121 mustParseTarget("module.bar"), 122 ConfigResource{ 123 Module: []string{"bar"}, 124 Resource: Resource{ 125 Mode: ManagedResourceMode, 126 Type: "test_resource", 127 Name: "foo", 128 }, 129 }, 130 true, 131 }, 132 { 133 mustParseTarget("module.bar.test_resource.foo"), 134 ConfigResource{ 135 Module: []string{"bar"}, 136 Resource: Resource{ 137 Mode: ManagedResourceMode, 138 Type: "test_resource", 139 Name: "foo", 140 }, 141 }, 142 true, 143 }, 144 { 145 ConfigResource{ 146 Resource: Resource{ 147 Mode: ManagedResourceMode, 148 Type: "test_resource", 149 Name: "foo", 150 }, 151 }, 152 mustParseTarget("module.bar.test_resource.foo[2]"), 153 false, 154 }, 155 { 156 ConfigResource{ 157 Module: []string{"bar"}, 158 Resource: Resource{ 159 Mode: ManagedResourceMode, 160 Type: "test_resource", 161 Name: "foo", 162 }, 163 }, 164 mustParseTarget("module.bar[0].test_resource.foo"), 165 true, 166 }, 167 168 // Modules are also never the result of parsing a target, but also need 169 // to be targetable 170 { 171 Module{"bar"}, 172 Module{"bar", "baz"}, 173 true, 174 }, 175 { 176 Module{"bar"}, 177 mustParseTarget("module.bar[0]"), 178 true, 179 }, 180 { 181 // Parsing an ambiguous module path needs to ensure the 182 // ModuleInstance could contain the Module. This is safe because if 183 // the module could be expanded, it must have an index, meaning no 184 // index indicates that the module instance and module are 185 // functionally equivalent. 186 mustParseTarget("module.bar"), 187 Module{"bar"}, 188 true, 189 }, 190 { 191 // A specific ModuleInstance cannot contain a module 192 mustParseTarget("module.bar[0]"), 193 Module{"bar"}, 194 false, 195 }, 196 { 197 Module{"bar", "baz"}, 198 mustParseTarget("module.bar[0].module.baz.test_resource.foo[1]"), 199 true, 200 }, 201 { 202 mustParseTarget("module.bar[0].module.baz"), 203 Module{"bar", "baz"}, 204 false, 205 }, 206 } { 207 t.Run(fmt.Sprintf("%s-in-%s", test.other, test.addr), func(t *testing.T) { 208 got := test.addr.TargetContains(test.other) 209 if got != test.expect { 210 t.Fatalf("expected %q.TargetContains(%q) == %t", test.addr, test.other, test.expect) 211 } 212 }) 213 } 214 } 215 216 func TestResourceContains(t *testing.T) { 217 for _, test := range []struct { 218 in, other Targetable 219 expect bool 220 }{} { 221 t.Run(fmt.Sprintf("%s-in-%s", test.other, test.in), func(t *testing.T) { 222 got := test.in.TargetContains(test.other) 223 if got != test.expect { 224 t.Fatalf("expected %q.TargetContains(%q) == %t", test.in, test.other, test.expect) 225 } 226 }) 227 } 228 } 229 230 func mustParseTarget(str string) Targetable { 231 t, diags := ParseTargetStr(str) 232 if diags != nil { 233 panic(fmt.Sprintf("%s: %s", str, diags.ErrWithWarnings())) 234 } 235 return t.Subject 236 }