github.com/mineiros-io/terradoc@v0.0.9-0.20220711062319-018bd4ae81f5/internal/parsers/docparser/docparser_test.go (about) 1 package docparser_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "strings" 7 "testing" 8 9 "github.com/madlambda/spells/assert" 10 "github.com/mineiros-io/terradoc/internal/entities" 11 "github.com/mineiros-io/terradoc/internal/parsers/docparser" 12 "github.com/mineiros-io/terradoc/internal/types" 13 "github.com/mineiros-io/terradoc/test" 14 ) 15 16 func TestParse(t *testing.T) { 17 for _, tt := range []struct { 18 desc string 19 inputFile string 20 want entities.Doc 21 }{ 22 { 23 desc: "with a valid input", 24 inputFile: "parser-input.tfdoc.hcl", 25 want: entities.Doc{ 26 Header: entities.Header{ 27 Image: "https://raw.githubusercontent.com/mineiros-io/brand/3bffd30e8bdbbde32c143e2650b2faa55f1df3ea/mineiros-primary-logo.svg", 28 URL: "https://www.mineiros.io", 29 }, 30 Sections: []entities.Section{ 31 { 32 Level: 1, 33 Title: "root section", 34 Content: `This is the root section content. 35 36 Section contents support anything markdown and allow us to make references like this one: [mineiros-website]`, 37 SubSections: []entities.Section{ 38 { 39 Level: 2, 40 Title: "sections with variables", 41 42 SubSections: []entities.Section{ 43 { 44 Level: 3, 45 Title: "example", 46 Variables: []entities.Variable{ 47 { 48 Name: "person", 49 Type: entities.Type{ 50 TFType: types.TerraformObject, 51 Label: "person", 52 }, 53 Description: "describes the last person who bothered to change this file", 54 Default: json.RawMessage("nathan"), 55 Attributes: []entities.Attribute{ 56 { 57 Name: "name", 58 Type: entities.Type{ 59 TFType: types.TerraformString, 60 }, 61 Description: "the person's name", 62 Default: json.RawMessage("nathan"), 63 }, 64 }, 65 }, 66 }, 67 }, 68 { 69 Level: 3, 70 Title: "section of beers", 71 Content: "an excuse to mention alcohol", 72 Variables: []entities.Variable{ 73 { 74 Name: "beers", 75 Type: entities.Type{ 76 TFType: types.TerraformList, 77 Nested: &entities.Type{ 78 TFType: types.TerraformObject, 79 Label: "beer", 80 }, 81 }, 82 Description: "a list of beers", 83 Default: json.RawMessage("[]"), 84 Required: true, 85 ForcesRecreation: true, 86 ReadmeExample: "", 87 Attributes: []entities.Attribute{ 88 { 89 Name: "name", 90 Type: entities.Type{ 91 TFType: types.TerraformString, 92 }, 93 Description: "the name of the beer", 94 ForcesRecreation: false, 95 }, 96 { 97 Name: "type", 98 Type: entities.Type{ 99 TFType: types.TerraformString, 100 }, 101 Description: "the type of the beer", 102 ForcesRecreation: true, 103 }, 104 { 105 Name: "abv", 106 Type: entities.Type{ 107 TFType: types.TerraformNumber, 108 }, 109 Description: "beer's alcohol by volume content", 110 ForcesRecreation: true, 111 }, 112 { 113 Name: "tags", 114 Type: entities.Type{ 115 TFType: types.TerraformList, 116 Nested: &entities.Type{ 117 TFType: types.TerraformString, 118 }, 119 }, 120 Description: "a list of tags for the beer", 121 }, 122 }, 123 }, 124 }, 125 }, 126 { 127 Level: 3, 128 Title: "Outputs!", 129 Outputs: []entities.Output{ 130 { 131 Name: "obj_output", 132 Description: "an example object", 133 Type: entities.Type{ 134 TFType: types.TerraformObject, 135 Label: "an_object_label", 136 }, 137 }, 138 { 139 Name: "string_output", 140 Description: "a string", 141 Type: entities.Type{ 142 TFType: types.TerraformString, 143 }, 144 }, 145 { 146 Name: "list_output", 147 Description: "a list of example objects", 148 Type: entities.Type{ 149 TFType: types.TerraformList, 150 Nested: &entities.Type{ 151 TFType: types.TerraformObject, 152 Label: "example", 153 }, 154 }, 155 }, 156 { 157 Name: "resource_output", 158 Description: "a resource output", 159 Type: entities.Type{ 160 TFType: types.TerraformResource, 161 Label: "google_xxxx", 162 }, 163 }, 164 }, 165 }, 166 }, 167 }, 168 }, 169 }, 170 }, 171 }, 172 }, 173 } { 174 t.Run(tt.desc, func(t *testing.T) { 175 r := test.OpenFixture(t, tt.inputFile) 176 // parsed definition 177 definition, err := docparser.Parse(r, "foo") 178 assert.NoError(t, err) 179 180 assertEqualDefinitions(t, tt.want, definition) // 181 }) 182 } 183 } 184 185 func TestParseInvalidContent(t *testing.T) { 186 for _, tt := range []struct { 187 desc string 188 wantErrorMsgContains string 189 content string 190 }{ 191 { 192 desc: "variable block without a label", 193 wantErrorMsgContains: "Missing name for variable; All variable blocks must have 1 labels (name).", 194 content: ` 195 section { 196 title = "test" 197 198 section { 199 title = "test" 200 201 variable { 202 type = string 203 } 204 } 205 } 206 207 `, 208 }, 209 { 210 desc: "variable block without a type", 211 wantErrorMsgContains: "Missing required argument; The argument \"type\" is required, but no definition was found.", 212 content: ` 213 section { 214 title = "test" 215 216 section { 217 title = "test" 218 219 variable "foo" { 220 default = [] 221 } 222 } 223 } 224 225 `, 226 }, 227 { 228 desc: "attribute block without a label", 229 wantErrorMsgContains: "Missing name for attribute; All attribute blocks must have 1 labels (name)", 230 content: ` 231 section { 232 title = "test" 233 234 section { 235 title = "test" 236 237 variable "test" { 238 type = string 239 240 attribute { 241 type = number 242 } 243 } 244 } 245 } 246 `, 247 }, 248 { 249 desc: "attribute block without a type", 250 wantErrorMsgContains: "Missing required argument; The argument \"type\" is required, but no definition was found", 251 content: ` 252 section { 253 title = "test" 254 255 section { 256 title = "test" 257 258 variable "test" { 259 type = string 260 261 attribute "bar" { 262 default = number 263 } 264 } 265 } 266 } 267 `, 268 }, 269 } { 270 t.Run(tt.desc, func(t *testing.T) { 271 r := bytes.NewBufferString(tt.content) 272 273 _, err := docparser.Parse(r, "foo-file") 274 assert.Error(t, err) 275 276 if !strings.Contains(err.Error(), tt.wantErrorMsgContains) { 277 t.Errorf("Expected error message to contain %q but got %q instead", tt.wantErrorMsgContains, err.Error()) 278 } 279 }) 280 } 281 } 282 283 func assertEqualDefinitions(t *testing.T, want, got entities.Doc) { 284 t.Helper() 285 286 assertEqualHeader(t, want.Header, got.Header) 287 assertEqualSections(t, want.Sections, got.Sections) 288 } 289 290 func assertEqualHeader(t *testing.T, want, got entities.Header) { 291 t.Helper() 292 293 assert.EqualStrings(t, want.Image, got.Image) 294 assert.EqualStrings(t, want.URL, got.URL) 295 296 assertEqualBadges(t, want.Badges, got.Badges) 297 298 } 299 300 func assertEqualBadges(t *testing.T, got, want []entities.Badge) { 301 t.Helper() 302 303 assert.EqualInts(t, len(want), len(got)) 304 305 // TODO: assert that badges are equivalent 306 } 307 308 func assertEqualSections(t *testing.T, want, got []entities.Section) { 309 t.Helper() 310 311 for _, section := range want { 312 assertContainsSection(t, got, section) 313 } 314 } 315 316 func assertContainsSection(t *testing.T, sectionsList []entities.Section, want entities.Section) { 317 t.Helper() 318 319 var found bool 320 for _, section := range sectionsList { 321 if section.Title == want.Title { 322 found = true 323 324 assertSectionEquals(t, want, section) 325 } 326 } 327 328 if !found { 329 t.Errorf("Expected sections list to contain section with title %q. Found none instead", want.Title) 330 } 331 } 332 333 func assertSectionEquals(t *testing.T, want, got entities.Section) { 334 t.Helper() 335 336 // redundant since we're finding the section by title 337 assert.EqualStrings(t, want.Title, got.Title) 338 assert.EqualStrings(t, want.Content, got.Content) 339 assert.EqualInts(t, want.Level, got.Level) 340 341 assertEqualVariables(t, want.Variables, got.Variables) 342 assertEqualOutputs(t, want.Outputs, got.Outputs) 343 assertEqualSections(t, want.SubSections, got.SubSections) 344 } 345 346 func assertEqualVariables(t *testing.T, want, got []entities.Variable) { 347 t.Helper() 348 349 for _, variable := range want { 350 assertContainsVariable(t, got, variable) 351 } 352 } 353 354 func assertContainsVariable(t *testing.T, variablesList []entities.Variable, want entities.Variable) { 355 t.Helper() 356 357 var found bool 358 for _, variable := range variablesList { 359 if variable.Name == want.Name { 360 found = true 361 362 assertVariableEquals(t, want, variable) 363 } 364 } 365 366 if !found { 367 t.Errorf("Expected variables list to contain %q but didn't find one", want.Name) 368 } 369 } 370 371 func assertVariableEquals(t *testing.T, want, got entities.Variable) { 372 t.Helper() 373 374 // redundant since we're finding the variable by name 375 assert.EqualStrings(t, want.Name, got.Name) 376 assert.EqualStrings(t, want.Description, got.Description) 377 378 test.AssertEqualTypes(t, want.Type, got.Type) 379 380 assertEqualAttributes(t, want.Attributes, got.Attributes) 381 } 382 383 func assertEqualAttributes(t *testing.T, want, got []entities.Attribute) { 384 t.Helper() 385 386 for _, attribute := range want { 387 assertContainsAttribute(t, got, attribute) 388 } 389 } 390 391 func assertContainsAttribute(t *testing.T, attributesList []entities.Attribute, want entities.Attribute) { 392 t.Helper() 393 394 var found bool 395 for _, attribute := range attributesList { 396 if attribute.Name == want.Name { 397 found = true 398 399 assertAttributeEquals(t, want, attribute) 400 } 401 } 402 403 if !found { 404 t.Errorf("Expected attributes list to contain %q but didn't find one", want.Name) 405 } 406 } 407 408 func assertAttributeEquals(t *testing.T, want, got entities.Attribute) { 409 t.Helper() 410 411 // redundant since we're finding the attribute by name 412 assert.EqualStrings(t, want.Name, got.Name) 413 assert.EqualStrings(t, want.Description, got.Description) 414 415 test.AssertEqualTypes(t, want.Type, got.Type) 416 417 assertEqualAttributes(t, want.Attributes, got.Attributes) 418 } 419 420 func assertEqualOutputs(t *testing.T, want, got []entities.Output) { 421 t.Helper() 422 423 for _, output := range want { 424 assertContainsOutput(t, got, output) 425 } 426 } 427 428 func assertContainsOutput(t *testing.T, outputsList []entities.Output, want entities.Output) { 429 t.Helper() 430 431 var found bool 432 for _, output := range outputsList { 433 if output.Name == want.Name { 434 found = true 435 436 assertOutputEquals(t, want, output) 437 } 438 } 439 440 if !found { 441 t.Errorf("Expected outputs list to contain %q but didn't find one", want.Name) 442 } 443 } 444 445 func assertOutputEquals(t *testing.T, want, got entities.Output) { 446 t.Helper() 447 448 // redundant since we're finding the output by name 449 assert.EqualStrings(t, want.Name, got.Name) 450 assert.EqualStrings(t, want.Description, got.Description) 451 452 test.AssertEqualTypes(t, want.Type, got.Type) 453 }