github.com/mineiros-io/terradoc@v0.0.9-0.20220711062319-018bd4ae81f5/internal/renderers/markdown/writer_test.go (about) 1 package markdown 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "strings" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/madlambda/spells/assert" 12 "github.com/mineiros-io/terradoc/internal/entities" 13 "github.com/mineiros-io/terradoc/internal/types" 14 ) 15 16 const ( 17 lineBreak = "\n" 18 ) 19 20 func TestWriteSection(t *testing.T) { 21 for _, tt := range []struct { 22 desc string 23 section entities.Section 24 want mdSection 25 }{ 26 { 27 desc: "with description and 4 levels", 28 section: entities.Section{ 29 Level: 4, 30 Title: "I AM THE TITLE!", 31 Content: `Basic usage for granting an AWS Account with Account ID ` + "`123456789012`" + ` access to assume a role that grants 32 full access to AWS Simple Storage Service (S3) 33 ` + "```hcl" + ` 34 module "role-s3-full-access" { 35 source = "mineiros-io/iam-role/aws" 36 version = "~> 0.6.0" 37 38 name = "S3FullAccess" 39 40 assume_role_principals = [ 41 { 42 type = "AWS" 43 identifiers = ["arn:aws:iam::123456789012:root"] 44 } 45 ] 46 47 policy_statements = [ 48 { 49 sid = "FullS3Access" 50 51 effect = "Allow" 52 actions = ["s3:*"] 53 resources = ["*"] 54 } 55 ] 56 } 57 ` + "```" + ` 58 `, 59 }, 60 want: mdSection{ 61 heading: "#### I AM THE TITLE!", 62 description: `Basic usage for granting an AWS Account with Account ID ` + "`123456789012`" + ` access to assume a role that grants 63 full access to AWS Simple Storage Service (S3) 64 ` + "```hcl" + ` 65 module "role-s3-full-access" { 66 source = "mineiros-io/iam-role/aws" 67 version = "~> 0.6.0" 68 69 name = "S3FullAccess" 70 71 assume_role_principals = [ 72 { 73 type = "AWS" 74 identifiers = ["arn:aws:iam::123456789012:root"] 75 } 76 ] 77 78 policy_statements = [ 79 { 80 sid = "FullS3Access" 81 82 effect = "Allow" 83 actions = ["s3:*"] 84 resources = ["*"] 85 } 86 ] 87 } 88 ` + "```" + ` 89 `, 90 }, 91 }, 92 { 93 desc: "without description and 1 level", 94 section: entities.Section{ 95 Level: 1, 96 Title: "section title", 97 }, 98 want: mdSection{ 99 heading: "# section title", 100 }, 101 }, 102 } { 103 t.Run(tt.desc, func(t *testing.T) { 104 buf := &bytes.Buffer{} 105 106 writer := newTestWriter(t, buf) 107 108 err := writer.writeSection(tt.section) 109 assert.NoError(t, err) 110 111 assertMarkdownHasSection(t, buf, tt.want) 112 }) 113 } 114 } 115 116 func TestWriteVariable(t *testing.T) { 117 for _, tt := range []struct { 118 desc string 119 variable entities.Variable 120 want mdVariable 121 }{ 122 { 123 desc: "a required string variable with description and default that forces recreation", 124 variable: entities.Variable{ 125 Name: "string_variable", 126 Type: entities.Type{ 127 TFType: types.TerraformString, 128 }, 129 ForcesRecreation: true, 130 Required: true, 131 Description: "i am a variable", 132 Default: []byte(`"default value"`), 133 }, 134 want: mdVariable{ 135 item: "- [**`string_variable`**](#var-string_variable): *(**Required** `string`, Forces new resource)*<a name=\"var-string_variable\"></a>", 136 description: "i am a variable", 137 defaults: "Default is `\"default value\"`.", 138 }, 139 }, 140 { 141 desc: "an optional number variable with defaults that forces recreation", 142 variable: entities.Variable{ 143 Name: "number_variable", 144 Type: entities.Type{ 145 TFType: types.TerraformNumber, 146 }, 147 ForcesRecreation: true, 148 Required: false, 149 Default: []byte("123"), 150 }, 151 want: mdVariable{ 152 item: "- [**`number_variable`**](#var-number_variable): *(Optional `number`, Forces new resource)*<a name=\"var-number_variable\"></a>", 153 defaults: "Default is `123`.", 154 }, 155 }, 156 { 157 desc: "a bool variable", 158 variable: entities.Variable{ 159 Name: "bool_variable", 160 Type: entities.Type{ 161 TFType: types.TerraformBool, 162 }, 163 ForcesRecreation: false, 164 Required: false, 165 }, 166 want: mdVariable{ 167 item: "- [**`bool_variable`**](#var-bool_variable): *(Optional `bool`)*<a name=\"var-bool_variable\"></a>", 168 }, 169 }, 170 { 171 desc: "an object variable with readme example", 172 variable: entities.Variable{ 173 Name: "obj_variable", 174 Type: entities.Type{ 175 TFType: types.TerraformObject, 176 }, 177 ForcesRecreation: true, 178 Required: true, 179 ReadmeExample: `obj_variable = { 180 a = "foo" 181 } 182 `, 183 }, 184 want: mdVariable{ 185 item: "- [**`obj_variable`**](#var-obj_variable): *(**Required** `object`, Forces new resource)*<a name=\"var-obj_variable\"></a>", 186 readmeExample: `obj_variable = { 187 a = "foo" 188 } 189 `, 190 }, 191 }, 192 } { 193 t.Run(tt.desc, func(t *testing.T) { 194 buf := &bytes.Buffer{} 195 196 writer := newTestWriter(t, buf) 197 198 err := writer.writeVariable(tt.variable) 199 assert.NoError(t, err) 200 201 assertMarkdownHasVariable(t, buf, tt.want) 202 }) 203 } 204 } 205 206 func TestWriteAttribute(t *testing.T) { 207 for _, tt := range []struct { 208 desc string 209 attr entities.Attribute 210 want mdAttribute 211 }{ 212 { 213 desc: "a required string attribute with description that forces recreation", 214 attr: entities.Attribute{ 215 Level: 1, 216 Name: "string_attribute", 217 Description: "i am this attribute's description", 218 Type: entities.Type{ 219 TFType: types.TerraformString, 220 }, 221 ForcesRecreation: true, 222 Required: true, 223 }, 224 want: mdAttribute{ 225 item: " - [**`string_attribute`**](#attr-parent_var_name-string_attribute): *(**Required** `string`, Forces new resource)*<a name=\"attr-parent_var_name-string_attribute\"></a>", 226 description: " i am this attribute's description", 227 }, 228 }, 229 { 230 desc: "an optional number attribute that forces recreations", 231 attr: entities.Attribute{ 232 Level: 2, 233 Name: "number_attribute", 234 Type: entities.Type{ 235 TFType: types.TerraformNumber, 236 }, 237 ForcesRecreation: true, 238 Required: false, 239 }, 240 want: mdAttribute{ 241 item: " - [**`number_attribute`**](#attr-parent_var_name-number_attribute): *(Optional `number`, Forces new resource)*<a name=\"attr-parent_var_name-number_attribute\"></a>", 242 }, 243 }, 244 { 245 desc: "a bool attribute", 246 attr: entities.Attribute{ 247 Level: 0, 248 Name: "bool_attribute", 249 Type: entities.Type{ 250 TFType: types.TerraformBool, 251 }, 252 ForcesRecreation: false, 253 Required: false, 254 }, 255 want: mdAttribute{ 256 item: "- [**`bool_attribute`**](#attr-parent_var_name-bool_attribute): *(Optional `bool`)*<a name=\"attr-parent_var_name-bool_attribute\"></a>", 257 }, 258 }, 259 { 260 desc: "an attribute with defautlts", 261 attr: entities.Attribute{ 262 Level: 1, 263 Name: "i_have_a_default", 264 Type: entities.Type{ 265 TFType: types.TerraformNumber, 266 }, 267 Default: []byte("123"), 268 }, 269 want: mdAttribute{ 270 item: " - [**`i_have_a_default`**](#attr-parent_var_name-i_have_a_default): *(Optional `number`)*<a name=\"attr-parent_var_name-i_have_a_default\"></a>", 271 description: " Default is `123`.", 272 }, 273 }, 274 } { 275 t.Run(tt.desc, func(t *testing.T) { 276 buf := &bytes.Buffer{} 277 278 writer := newTestWriter(t, buf) 279 err := writer.writeAttribute(tt.attr, "parent_var_name") 280 assert.NoError(t, err) 281 282 assertMarkdownHasAttribute(t, buf, tt.want) 283 }) 284 } 285 } 286 287 func TestWriteAttributeWithNested(t *testing.T) { 288 t.Skip("write rendering tests for nested attributes once we know more about it") 289 } 290 291 func TestWriteOutput(t *testing.T) { 292 for _, tt := range []struct { 293 desc string 294 output entities.Output 295 want mdOutput 296 }{ 297 { 298 desc: "", 299 output: entities.Output{ 300 Name: "string_output", 301 Description: "i am an output", 302 Type: entities.Type{ 303 TFType: types.TerraformString, 304 }, 305 }, 306 want: mdOutput{ 307 item: "- [**`string_output`**](#output-string_output): *(`string`)*<a name=\"output-string_output\"></a>", 308 description: "i am an output", 309 }, 310 }, 311 { 312 desc: "an optional number output with defaults that forces recreation", 313 output: entities.Output{ 314 Name: "number_output", 315 Type: entities.Type{ 316 TFType: types.TerraformNumber, 317 }, 318 }, 319 want: mdOutput{ 320 item: "- [**`number_output`**](#output-number_output): *(`number`)*<a name=\"output-number_output\"></a>", 321 }, 322 }, 323 { 324 desc: "a bool output", 325 output: entities.Output{ 326 Name: "bool_output", 327 Type: entities.Type{ 328 TFType: types.TerraformBool, 329 }, 330 }, 331 want: mdOutput{ 332 item: "- [**`bool_output`**](#output-bool_output): *(`bool`)*<a name=\"output-bool_output\"></a>", 333 }, 334 }, 335 { 336 desc: "an object output with readme example", 337 output: entities.Output{ 338 Name: "obj_output", 339 Type: entities.Type{ 340 TFType: types.TerraformObject, 341 Label: "some_object", 342 }, 343 }, 344 want: mdOutput{ 345 item: "- [**`obj_output`**](#output-obj_output): *(`object(some_object)`)*<a name=\"output-obj_output\"></a>", 346 }, 347 }, 348 } { 349 t.Run(tt.desc, func(t *testing.T) { 350 buf := &bytes.Buffer{} 351 352 writer := newTestWriter(t, buf) 353 354 err := writer.writeOutput(tt.output) 355 assert.NoError(t, err) 356 357 assertMarkdownHasOutput(t, buf, tt.want) 358 }) 359 } 360 } 361 362 // TODO: rewrite all? :D 363 364 type mdSection struct { 365 heading string 366 description string 367 } 368 369 type mdVariable struct { 370 item string 371 description string 372 defaults string 373 readmeExample string 374 } 375 376 type mdAttribute struct { 377 item string 378 description string 379 defaults string 380 readmeExample string 381 } 382 383 type mdOutput struct { 384 item string 385 description string 386 } 387 388 func assertMarkdownHasSection(t *testing.T, buf *bytes.Buffer, md mdSection) { 389 t.Helper() 390 391 want := md.heading + lineBreak 392 393 if md.description != "" { 394 want += lineBreak + md.description + lineBreak 395 } 396 397 want += lineBreak 398 399 if diff := cmp.Diff(want, buf.String()); diff != "" { 400 t.Errorf("Expected section markdown to match (-want +got):\n%s", diff) 401 } 402 } 403 404 func assertMarkdownHasVariable(t *testing.T, buf *bytes.Buffer, md mdVariable) { 405 t.Helper() 406 407 want := md.item + lineBreak 408 409 if md.description != "" { 410 want += fmt.Sprintf("\n %s\n", md.description) 411 } 412 413 if md.defaults != "" { 414 want += fmt.Sprintf("\n %s\n", md.defaults) 415 } 416 417 if md.readmeExample != "" { 418 // TODO: what's a better way of checking that indentation is right? 419 want += fmt.Sprintf("\n Example:\n\n ```hcl\n %s\n ```\n", md.readmeExample) 420 } 421 422 want += "\n" 423 424 if diff := cmp.Diff(want, buf.String()); diff != "" { 425 t.Errorf("Expected variable markdown to match (-want +got):\n%s", diff) 426 } 427 } 428 429 func assertMarkdownHasAttribute(t *testing.T, buf *bytes.Buffer, md mdAttribute) { 430 t.Helper() 431 432 want := md.item + lineBreak 433 434 if md.description != "" { 435 want += fmt.Sprintf("\n %s\n", md.description) 436 } 437 438 if md.defaults != "" { 439 want += fmt.Sprintf("\n %s\n", md.defaults) 440 } 441 442 if md.readmeExample != "" { 443 want += fmt.Sprintf("\n Example:\n\n ```hcl\n %s\n \n ```\n", md.readmeExample) 444 } 445 446 want += lineBreak 447 448 got := strings.TrimLeft(buf.String(), "\n") 449 450 if diff := cmp.Diff(want, got); diff != "" { 451 t.Errorf("Expected variable markdown to match (-want +got):\n%s", diff) 452 } 453 } 454 455 func assertMarkdownHasOutput(t *testing.T, buf *bytes.Buffer, md mdOutput) { 456 t.Helper() 457 458 want := md.item + lineBreak 459 460 if md.description != "" { 461 want += fmt.Sprintf("\n %s\n", md.description) 462 } 463 464 want += "\n" 465 466 if diff := cmp.Diff(want, buf.String()); diff != "" { 467 t.Errorf("Expected output markdown to match (-want +got):\n%s", diff) 468 } 469 } 470 471 func newTestWriter(t *testing.T, buf io.Writer) *markdownWriter { 472 writer, err := newMarkdownWriter(buf) 473 assert.NoError(t, err) 474 475 return writer 476 }