github.com/twelho/conform@v0.0.0-20231016230407-c25e9238598a/internal/policy/commit/commit_test.go (about)

     1  // This Source Code Form is subject to the terms of the Mozilla Public
     2  // License, v. 2.0. If a copy of the MPL was not distributed with this
     3  // file, You can obtain one at http://mozilla.org/MPL/2.0/.
     4  
     5  //nolint:testpackage
     6  package commit
     7  
     8  import (
     9  	"fmt"
    10  	"os"
    11  	"os/exec"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/twelho/conform/internal/policy"
    16  )
    17  
    18  //nolint:gocognit
    19  func TestConventionalCommitPolicy(t *testing.T) {
    20  	//nolint:govet
    21  	type testDesc struct {
    22  		Name         string
    23  		CreateCommit func() error
    24  		ExpectValid  bool
    25  	}
    26  
    27  	for _, test := range []testDesc{
    28  		{
    29  			Name:         "Valid",
    30  			CreateCommit: createValidScopedCommit,
    31  			ExpectValid:  true,
    32  		},
    33  		{
    34  			Name:         "ValidBreaking",
    35  			CreateCommit: createValidBreakingCommit,
    36  			ExpectValid:  true,
    37  		},
    38  		{
    39  			Name:         "InvalidBreakingSymbol",
    40  			CreateCommit: createInvalidBreakingSymbolCommit,
    41  			ExpectValid:  false,
    42  		},
    43  		{
    44  			Name:         "ValidScopedBreaking",
    45  			CreateCommit: createValidScopedBreakingCommit,
    46  			ExpectValid:  true,
    47  		},
    48  		{
    49  			Name:         "InvalidScopedBreaking",
    50  			CreateCommit: createInvalidScopedBreakingCommit,
    51  			ExpectValid:  false,
    52  		},
    53  		{
    54  			Name:         "Invalid",
    55  			CreateCommit: createInvalidCommit,
    56  			ExpectValid:  false,
    57  		},
    58  		{
    59  			Name:         "InvalidEmpty",
    60  			CreateCommit: createInvalidEmptyCommit,
    61  			ExpectValid:  false,
    62  		},
    63  	} {
    64  		func(test testDesc) {
    65  			t.Run(test.Name, func(tt *testing.T) {
    66  				dir := t.TempDir()
    67  
    68  				err := os.Chdir(dir)
    69  				if err != nil {
    70  					tt.Error(err)
    71  				}
    72  				err = initRepo()
    73  				if err != nil {
    74  					tt.Error(err)
    75  				}
    76  
    77  				err = test.CreateCommit()
    78  				if err != nil {
    79  					tt.Error(err)
    80  				}
    81  				report, err := runCompliance()
    82  				if err != nil {
    83  					t.Error(err)
    84  				}
    85  
    86  				if test.ExpectValid {
    87  					if !report.Valid() {
    88  						tt.Error("Report is invalid with valid conventional commit")
    89  					}
    90  				} else {
    91  					if report.Valid() {
    92  						tt.Error("Report is valid with invalid conventional commit")
    93  					}
    94  				}
    95  			})
    96  		}(test)
    97  	}
    98  }
    99  
   100  func TestValidateDCO(t *testing.T) {
   101  	type testDesc struct {
   102  		Name          string
   103  		CommitMessage string
   104  		ExpectValid   bool
   105  	}
   106  
   107  	for _, test := range []testDesc{
   108  		{
   109  			Name:          "Valid DCO",
   110  			CommitMessage: "something nice\n\nSigned-off-by: Foo Bar <foobar@example.org>\n\n",
   111  			ExpectValid:   true,
   112  		},
   113  		{
   114  			Name:          "Valid DCO with CRLF",
   115  			CommitMessage: "something nice\r\n\r\nSigned-off-by: Foo Bar <foobar@example.org>\r\n\r\n",
   116  			ExpectValid:   true,
   117  		},
   118  		{
   119  			Name:          "No DCO",
   120  			CommitMessage: "something nice\n\nnot signed\n",
   121  			ExpectValid:   false,
   122  		},
   123  	} {
   124  		// Fixes scopelint error.
   125  		test := test
   126  		t.Run(test.Name, func(tt *testing.T) {
   127  			var report policy.Report
   128  			c := Commit{msg: test.CommitMessage}
   129  			report.AddCheck(c.ValidateDCO())
   130  
   131  			if test.ExpectValid {
   132  				if !report.Valid() {
   133  					tt.Error("Report is invalid with valid DCP")
   134  				}
   135  			} else {
   136  				if report.Valid() {
   137  					tt.Error("Report is valid with invalid DCO")
   138  				}
   139  			}
   140  		})
   141  	}
   142  }
   143  
   144  func TestValidConventionalCommitPolicy(t *testing.T) {
   145  	dir := t.TempDir()
   146  
   147  	err := os.Chdir(dir)
   148  	if err != nil {
   149  		t.Error(err)
   150  	}
   151  
   152  	err = initRepo()
   153  	if err != nil {
   154  		t.Error(err)
   155  	}
   156  
   157  	err = createValidScopedCommit()
   158  	if err != nil {
   159  		t.Error(err)
   160  	}
   161  
   162  	report, err := runCompliance()
   163  	if err != nil {
   164  		t.Error(err)
   165  	}
   166  
   167  	if !report.Valid() {
   168  		t.Errorf("Report is invalid with valid conventional commit")
   169  	}
   170  }
   171  
   172  func TestInvalidConventionalCommitPolicy(t *testing.T) {
   173  	dir := t.TempDir()
   174  
   175  	err := os.Chdir(dir)
   176  	if err != nil {
   177  		t.Error(err)
   178  	}
   179  
   180  	err = initRepo()
   181  	if err != nil {
   182  		t.Error(err)
   183  	}
   184  
   185  	err = createInvalidCommit()
   186  	if err != nil {
   187  		t.Error(err)
   188  	}
   189  
   190  	report, err := runCompliance()
   191  	if err != nil {
   192  		t.Error(err)
   193  	}
   194  
   195  	if report.Valid() {
   196  		t.Errorf("Report is valid with invalid conventional commit")
   197  	}
   198  }
   199  
   200  func TestEmptyConventionalCommitPolicy(t *testing.T) {
   201  	dir := t.TempDir()
   202  
   203  	err := os.Chdir(dir)
   204  	if err != nil {
   205  		t.Error(err)
   206  	}
   207  
   208  	err = initRepo()
   209  	if err != nil {
   210  		t.Error(err)
   211  	}
   212  
   213  	err = createInvalidEmptyCommit()
   214  	if err != nil {
   215  		t.Error(err)
   216  	}
   217  
   218  	report, err := runCompliance()
   219  	if err != nil {
   220  		t.Error(err)
   221  	}
   222  
   223  	if report.Valid() {
   224  		t.Error("Report is valid with invalid conventional commit")
   225  	}
   226  }
   227  
   228  func TestValidConventionalCommitPolicyRegex(t *testing.T) {
   229  	dir := t.TempDir()
   230  
   231  	err := os.Chdir(dir)
   232  	if err != nil {
   233  		t.Error(err)
   234  	}
   235  
   236  	err = initRepo()
   237  	if err != nil {
   238  		t.Error(err)
   239  	}
   240  
   241  	err = createValidCommitRegex()
   242  	if err != nil {
   243  		t.Error(err)
   244  	}
   245  
   246  	report, err := runCompliance()
   247  	if err != nil {
   248  		t.Error(err)
   249  	}
   250  
   251  	if !report.Valid() {
   252  		t.Error("Report is invalid with valid conventional commit")
   253  	}
   254  }
   255  
   256  func TestInvalidConventionalCommitPolicyRegex(t *testing.T) {
   257  	dir := t.TempDir()
   258  
   259  	err := os.Chdir(dir)
   260  	if err != nil {
   261  		t.Error(err)
   262  	}
   263  
   264  	err = initRepo()
   265  	if err != nil {
   266  		t.Error(err)
   267  	}
   268  
   269  	err = createInvalidCommitRegex()
   270  	if err != nil {
   271  		t.Error(err)
   272  	}
   273  
   274  	report, err := runCompliance()
   275  	if err != nil {
   276  		t.Error(err)
   277  	}
   278  
   279  	if report.Valid() {
   280  		t.Error("Report is valid with invalid conventional commit")
   281  	}
   282  }
   283  
   284  func TestValidRevisionRange(t *testing.T) {
   285  	dir := t.TempDir()
   286  
   287  	err := os.Chdir(dir)
   288  	if err != nil {
   289  		t.Error(err)
   290  	}
   291  
   292  	err = initRepo()
   293  	if err != nil {
   294  		t.Error(err)
   295  	}
   296  
   297  	revs, err := createValidCommitRange()
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  
   302  	// Test with a valid revision range
   303  	report, err := runComplianceRange(revs[0], revs[len(revs)-1])
   304  	if err != nil {
   305  		t.Error(err)
   306  	}
   307  
   308  	if !report.Valid() {
   309  		t.Error("Report is invalid with valid conventional commits")
   310  	}
   311  
   312  	// Test with HEAD as end of revision range
   313  	report, err = runComplianceRange(revs[0], "HEAD")
   314  	if err != nil {
   315  		t.Error(err)
   316  	}
   317  
   318  	if !report.Valid() {
   319  		t.Error("Report is invalid with valid conventional commits")
   320  	}
   321  
   322  	// Test with empty end of revision range (should fail)
   323  	_, err = runComplianceRange(revs[0], "")
   324  	if err == nil {
   325  		t.Error("Invalid end of revision, got success, expecting failure")
   326  	}
   327  
   328  	// Test with empty start of revision (should fail)
   329  	_, err = runComplianceRange("", "HEAD")
   330  	if err == nil {
   331  		t.Error("Invalid end of revision, got success, expecting failure")
   332  	}
   333  
   334  	// Test with start of revision not an ancestor of end of range (should fail)
   335  	_, err = runComplianceRange(revs[1], revs[0])
   336  	if err == nil {
   337  		t.Error("Invalid end of revision, got success, expecting failure")
   338  	}
   339  }
   340  
   341  func createValidCommitRange() ([]string, error) {
   342  	var revs []string
   343  
   344  	for i := 0; i < 4; i++ {
   345  		err := os.WriteFile("test", []byte(fmt.Sprint(i)), 0o644)
   346  		if err != nil {
   347  			return nil, fmt.Errorf("writing test file failed: %w", err)
   348  		}
   349  
   350  		_, err = exec.Command("git", "add", "test").Output()
   351  		if err != nil {
   352  			return nil, fmt.Errorf("git add failed: %w", err)
   353  		}
   354  
   355  		_, err = exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", fmt.Sprintf("type(scope): description %d", i)).Output()
   356  		if err != nil {
   357  			return nil, fmt.Errorf("git commit failed: %w", err)
   358  		}
   359  
   360  		id, err := exec.Command("git", "rev-parse", "HEAD").Output()
   361  		if err != nil {
   362  			return nil, fmt.Errorf("rev-parse failed: %w", err)
   363  		}
   364  
   365  		revs = append(revs, strings.TrimSpace(string(id)))
   366  	}
   367  
   368  	return revs, nil
   369  }
   370  
   371  func runComplianceRange(id1, id2 string) (*policy.Report, error) {
   372  	c := &Commit{
   373  		Conventional: &Conventional{
   374  			Types:  []string{"type"},
   375  			Scopes: []string{"scope", "^valid"},
   376  		},
   377  	}
   378  
   379  	return c.Compliance(&policy.Options{
   380  		RevisionRange: fmt.Sprintf("%s..%s", id1, id2),
   381  	})
   382  }
   383  
   384  func runCompliance() (*policy.Report, error) {
   385  	c := &Commit{
   386  		Conventional: &Conventional{
   387  			Types:  []string{"type"},
   388  			Scopes: []string{"scope", "^valid"},
   389  		},
   390  	}
   391  
   392  	return c.Compliance(&policy.Options{})
   393  }
   394  
   395  func initRepo() error {
   396  	_, err := exec.Command("git", "init").Output()
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	_, err = exec.Command("touch", "test").Output()
   402  	if err != nil {
   403  		return err
   404  	}
   405  
   406  	_, err = exec.Command("git", "add", "test").Output()
   407  
   408  	return err
   409  }
   410  
   411  func createValidScopedCommit() error {
   412  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "type(scope): description").Output()
   413  
   414  	return err
   415  }
   416  
   417  func createValidBreakingCommit() error {
   418  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat!: description").Output()
   419  
   420  	return err
   421  }
   422  
   423  func createInvalidBreakingSymbolCommit() error {
   424  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat$: description").Output()
   425  
   426  	return err
   427  }
   428  
   429  func createValidScopedBreakingCommit() error {
   430  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat(scope)!: description").Output()
   431  
   432  	return err
   433  }
   434  
   435  func createInvalidScopedBreakingCommit() error {
   436  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "feat!(scope): description").Output()
   437  
   438  	return err
   439  }
   440  
   441  func createInvalidCommit() error {
   442  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "invalid commit").Output()
   443  
   444  	return err
   445  }
   446  
   447  func createInvalidEmptyCommit() error {
   448  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "--allow-empty-message", "-m", "").Output()
   449  
   450  	return err
   451  }
   452  
   453  func createValidCommitRegex() error {
   454  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "type(valid-1): description").Output()
   455  
   456  	return err
   457  }
   458  
   459  func createInvalidCommitRegex() error {
   460  	_, err := exec.Command("git", "-c", "user.name='test'", "-c", "user.email='test@siderolabs.io'", "commit", "-m", "type(invalid-1): description").Output()
   461  
   462  	return err
   463  }