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  }