Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test config #71

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Test config #71

wants to merge 3 commits into from

Conversation

lippserd
Copy link
Member

@lippserd lippserd commented Sep 18, 2024

This PR introduces tests for the ParseFlags(), FromYAMLFile() and TLS.MakeConfig() functions:

ParseFlags

  • Verifies that command-line flags are correctly parsed into a struct.
  • Error propagation from defaults.Set().
  • Nil pointer argument: Tests calling FromYAMLFile with a nil pointer argument.
  • Nil argument: Tests calling FromYAMLFile with a nil argument.

FromYAMLFile

  • Simple YAML: Tests parsing a simple YAML file.
  • Inlined YAML: Tests parsing a YAML file with inlined fields.
  • Embedded YAML: Tests parsing a YAML file with embedded fields.
  • Defaults: Tests parsing a YAML file with default values.
  • Overriding defaults: Tests parsing a YAML file overriding default values.
  • Empty YAML: Tests parsing an empty YAML file.
  • Empty YAML with directive separator: Tests parsing an empty YAML file with a directive separator.
  • Faulty YAML: Tests parsing a faulty YAML file.
  • Invalid YAML: Tests parsing a YAML file that is expected to fail validation.
  • Nil pointer argument: Tests calling FromYAMLFile with a nil pointer argument.
  • Nil argument: Tests calling FromYAMLFile with a nil argument.
  • Non-existent file: Tests calling FromYAMLFile with a non-existent file.
  • Permission denied: Tests calling FromYAMLFile with a file that has no read permissions.

TLS.MakeConfig()

  1. Basic Configuration

    • Verifies that the function returns nil when TLS is disabled.
    • Ensures the server name is correctly set in the TLS configuration.
    • Checks that InsecureSkipVerify is set to true when specified.
    • Ensures an error is returned when the client certificate is missing.
    • Ensures an error is returned when the private key is missing.
  2. x509 Certificate Handling

    • Valid certificate and key: Verifies successful configuration with valid certificate and key.
    • Mismatched certificate and key: Ensures an error is returned for mismatched certificate and key.
    • Invalid certificate path: Ensures an error is returned for a non-existent certificate file.
    • Invalid certificate permissions: Ensures an error is returned for a certificate file with no read permissions.
    • Corrupt certificate: Ensures an error is returned for a corrupt certificate file.
    • Invalid key path: Ensures an error is returned for a non-existent key file.
    • Invalid key permissions: Ensures an error is returned for a key file with no read permissions.
    • Corrupt key: Ensures an error is returned for a corrupt key file.
    • Valid CA: Verifies successful configuration with a valid CA certificate.
    • Invalid CA path: Ensures an error is returned for a non-existent CA file.
    • Invalid CA permissions: Ensures an error is returned for a CA file with no read permissions.
    • Corrupt CA: Ensures an error is returned for a corrupt CA file.

@cla-bot cla-bot bot added the cla/signed CLA is signed by all contributors of a PR label Sep 18, 2024
@lippserd lippserd requested a review from oxzi September 18, 2024 11:13
Comment on lines 31 to 93
t.Run("Nil pointer argument", func(t *testing.T) {
var flags interface{}

err := ParseFlags(flags)
require.ErrorIs(t, err, ErrInvalidArgument)
})

t.Run("Nil argument", func(t *testing.T) {
err := ParseFlags(nil)
require.ErrorIs(t, err, ErrInvalidArgument)
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two test cases seem to be identical. Furthermore, the first one does not check a nil pointer, but nil.

If, however, one changes it to use a real nil pointer by setting ParseFlags(&flags), it panics.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I forgot to add *, i.e. var flags *any.

})
}

// simpleConfig is an always valid test configuration struct with only one key.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just my personal taste, but I would expect the used types to be on the top of the file. When reading the tests top to bottom, I first asked myself where the used types are being declared.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just always put the exported stuff first. But you're right that it's better for tests to have them first.

Comment on lines +130 to +183
defer func(name string) {
_ = os.Remove(name)
}(yamlFile.Name())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to introduce the name variable for the deferred function? yamlFile.Name() is also used on all other places.

Suggested change
defer func(name string) {
_ = os.Remove(name)
}(yamlFile.Name())
defer func() { _ = os.Remove(yamlFile.Name()) }()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In such scenarios, I would always capture the argument values at the time the defer is set up, as opposed to relying on the value not having changed by the time the defer is executed.

Comment on lines 158 to 236
t.Run("Nil pointer argument", func(t *testing.T) {
var config Validator

err := FromYAMLFile("", config)
require.ErrorIs(t, err, ErrInvalidArgument)
})

t.Run("Nil argument", func(t *testing.T) {
err := FromYAMLFile("", nil)
require.ErrorIs(t, err, ErrInvalidArgument)
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, both these tests boil down to perform the same FromYAMLFile call.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, and it's a bit more difficult here. But I think something like var config *struct{ Validator } could work.

var config struct{ validateValid }
var pathError *fs.PathError

err := FromYAMLFile("nonexistent.yaml", &config)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One should enforce that nonexistent.yaml does not suddenly exists. Maybe create another new temporary file, delete it afterwards and use its file path? I mean, technically this could be raced, but it should be fine for tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add require.ErrorIs(t, pathError.Err, fs.ErrNotExist). If nonexistent.yaml really gets created, the test fails.

Comment on lines +186 to +257
defer func(name string) {
_ = os.Remove(name)
}(yamlFile.Name())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why are you wrapping the file name?

Btw, I have verified that the os.Remove works. First, I was a bit uncertain due to the 000 permissions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I have verified that the os.Remove works. First, I was a bit uncertain due to the 000 permissions.

Yes, the ability to delete a file is determined by the permissions of the directory containing the file.

Key: "value",
},
invalid: true,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test case for failed defaults is missing. Maybe create some test type with faulty default struct types.

if err := defaults.Set(v); err != nil {
return errors.Wrap(err, "can't set config defaults")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

err := ParseFlags(nil)
require.ErrorIs(t, err, ErrInvalidArgument)
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no tests for a parser.Parse() error. However, this may require some code changes as otherwise one has to recover from os.Exit().

if _, err := parser.Parse(); err != nil {
var flagErr *flags.Error
if errors.As(err, &flagErr) && flagErr.Type == flags.ErrHelp {
fmt.Fprintln(os.Stdout, flagErr)
os.Exit(0)
}
return errors.Wrap(err, "can't parse CLI flags")
}

However, covering this is not completely necessary, imo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recover from os.Exit() is impossible using the stdlib, at least to my knowledge. But we could test that using a subprocess. I'll give it a try.

var issuerKey crypto.PrivateKey
if options.issuer != nil {
if options.issuerKey == nil {
panic("issuerKey required if issuer set")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper function returns an error in most cases, but panics here. When generateCert is called, the error must always be empty. Thus, to have a nicer test output, maybe return an error here as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, why not.

issuerKey = privateKey
}

derBytes, err := x509.CreateCertificate(rand.Reader, template, issuer, privateKey.Public(), issuerKey)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

der Bytes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, x509.CreateCertificate() returns the DER encoded certificate as bytes -> derBytes 😆.

Copy link
Member Author

@lippserd lippserd Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename it to der as x509.ParseCertificate() uses that as argument name.

@lippserd
Copy link
Member Author

@oxzi I have adjusted the code based on your preview. Please take another look at it. I have already moved code unfortunately, so the comments are no longer aligned correctly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla/signed CLA is signed by all contributors of a PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants