From 5655480e00b545f4fa6f957e7cbe1e3412619736 Mon Sep 17 00:00:00 2001 From: Pierig Le Saux Date: Wed, 8 Apr 2026 22:36:00 -0400 Subject: [PATCH] add codecoverage --- api/gpg_test.go | 201 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 156 insertions(+), 45 deletions(-) diff --git a/api/gpg_test.go b/api/gpg_test.go index aad15025..40b15c7c 100644 --- a/api/gpg_test.go +++ b/api/gpg_test.go @@ -3,6 +3,9 @@ package api import ( "bytes" "encoding/json" + "os" + "path/filepath" + "strings" . "gopkg.in/check.v1" ) @@ -13,6 +16,46 @@ type GPGSuite struct { var _ = Suite(&GPGSuite{}) +func (s *GPGSuite) withFakeGPG(c *C, scriptBody string, test func(scriptPath string)) { + tempDir, err := os.MkdirTemp("", "aptly-fake-gpg") + c.Assert(err, IsNil) + defer func() { _ = os.RemoveAll(tempDir) }() + + scriptPath := filepath.Join(tempDir, "gpg") + err = os.WriteFile(scriptPath, []byte(scriptBody), 0o755) + c.Assert(err, IsNil) + + oldPath := os.Getenv("PATH") + err = os.Setenv("PATH", tempDir+string(os.PathListSeparator)+oldPath) + c.Assert(err, IsNil) + defer func() { _ = os.Setenv("PATH", oldPath) }() + + test(scriptPath) +} + +func (s *GPGSuite) fakeGPGScript(c *C, listOutput string, deleteOutput string, deleteError string) string { + return "#!/bin/sh\n" + + "if [ \"$1\" = \"--version\" ]; then\n" + + " echo 'gpg (GnuPG) 2.2.27'\n" + + " exit 0\n" + + "fi\n" + + "args=\"$*\"\n" + + "if printf '%s' \"$args\" | grep -q -- '--list-keys'; then\n" + + " cat <<'EOF'\n" + listOutput + "\nEOF\n" + + " exit 0\n" + + "fi\n" + + "if printf '%s' \"$args\" | grep -q -- '--delete-keys'; then\n" + + " if [ -n \"" + strings.ReplaceAll(deleteError, "\n", "") + "\" ]; then\n" + + " echo '" + strings.ReplaceAll(deleteError, "'", "'\\''") + "'\n" + + " exit 1\n" + + " fi\n" + + " cat <<'EOF'\n" + deleteOutput + "\nEOF\n" + + " exit 0\n" + + "fi\n" + + "echo 'unexpected invocation' >&2\n" + + "exit 1\n" +} + // TestParseGPGOutputEmpty tests parsing of empty GPG output func (s *GPGSuite) TestParseGPGOutputEmpty(c *C) { output := "" @@ -173,50 +216,68 @@ fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:` // TestAPIGPGListKeysDefaultKeyring tests the HTTP endpoint with default keyring func (s *GPGSuite) TestAPIGPGListKeysDefaultKeyring(c *C) { - c.Skip("Requires GPG binary and test key installation. Run manually if needed.") + s.withFakeGPG(c, s.fakeGPGScript(c, `pub:u:4096:1:8B48AD6246925553:1611864000::::: +uid:u::::1611864000::1234567890::John Doe ::::::::::0: +fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`, "", ""), func(_ string) { + response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) - response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil) - c.Assert(err, IsNil) - c.Check(response.Code, Equals, 200) - - // Verify response is valid JSON - var result gpgKeyListResponse - err = json.NewDecoder(response.Body).Decode(&result) - c.Assert(err, IsNil) - c.Check(result.Keys, NotNil) + var result gpgKeyListResponse + err = json.NewDecoder(response.Body).Decode(&result) + c.Assert(err, IsNil) + c.Check(result.Keys, HasLen, 1) + c.Check(result.Keys[0].KeyID, Equals, "8B48AD6246925553") + }) } // TestAPIGPGListKeysWithKeyringParam tests the HTTP endpoint with custom keyring parameter func (s *GPGSuite) TestAPIGPGListKeysWithKeyringParam(c *C) { - c.Skip("Requires GPG binary and test key installation. Run manually if needed.") - - response, err := s.HTTPRequest("GET", "/api/gpg/keys?keyring=custom.gpg", nil) + argFile, err := os.CreateTemp("", "aptly-gpg-args") c.Assert(err, IsNil) - // May fail if custom.gpg doesn't exist, but endpoint should handle gracefully - // Accept either 200 (success) or 400 (file not found) - code := response.Code - c.Check(code == 200 || code == 400, Equals, true) + _ = argFile.Close() + defer func() { _ = os.Remove(argFile.Name()) }() + + script := "#!/bin/sh\n" + + "if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" + + "printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" + + "if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" + + "cat <<'EOF'\n" + + "pub:u:4096:1:8B48AD6246925553:1611864000:::::\n" + + "fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:\n" + + "EOF\n" + + "exit 0\n" + + "fi\n" + + "exit 1\n" + + s.withFakeGPG(c, script, func(_ string) { + response, reqErr := s.HTTPRequest("GET", "/api/gpg/keys?keyring=/custom.gpg", nil) + c.Assert(reqErr, IsNil) + c.Check(response.Code, Equals, 200) + + argBytes, readErr := os.ReadFile(argFile.Name()) + c.Assert(readErr, IsNil) + c.Check(string(argBytes), Matches, `(?s).*--keyring\ncustom\.gpg\n.*`) + }) } // TestAPIGPGListKeysResponseFormat tests that the response has the correct structure func (s *GPGSuite) TestAPIGPGListKeysResponseFormat(c *C) { - c.Skip("Requires GPG binary and test key installation. Run manually if needed.") + s.withFakeGPG(c, s.fakeGPGScript(c, `pub:f:4096:1:A1B2C3D4E5F67890:1611864000::::: +uid:f::::1611864000::1234567890::Jane Smith ::::::::::0: +fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`, "", ""), func(_ string) { + response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 200) - response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil) - c.Assert(err, IsNil) - - if response.Code == 200 { var result gpgKeyListResponse err = json.NewDecoder(response.Body).Decode(&result) c.Assert(err, IsNil) - - // If there are keys, verify their structure - for _, key := range result.Keys { - c.Check(key.KeyID, Not(Equals), "") - c.Check(key.Validity, Not(Equals), "") - c.Check(key.CreatedAt, Not(Equals), "") - } - } + c.Assert(result.Keys, HasLen, 1) + c.Check(result.Keys[0].KeyID, Equals, "A1B2C3D4E5F67890") + c.Check(result.Keys[0].Validity, Equals, "f") + c.Check(result.Keys[0].CreatedAt, Equals, "1611864000") + }) } // TestParseGPGOutputEdgeCaseUIDWithoutFields tests UID record with missing fields @@ -300,8 +361,6 @@ func (s *GPGSuite) TestGPGDeleteKeyParamsValidation(c *C) { // TestAPIGPGDeleteKeyMissingKeyID tests delete with missing key ID parameter func (s *GPGSuite) TestAPIGPGDeleteKeyMissingKeyID(c *C) { - c.Skip("Requires GPG binary. Run manually if needed.") - body, err := json.Marshal(map[string]string{ "Keyring": "trustedkeys.gpg", // GpgKeyID is missing @@ -315,8 +374,6 @@ func (s *GPGSuite) TestAPIGPGDeleteKeyMissingKeyID(c *C) { // TestAPIGPGDeleteKeyInvalidJSON tests delete with invalid JSON request func (s *GPGSuite) TestAPIGPGDeleteKeyInvalidJSON(c *C) { - c.Skip("Requires GPG binary. Run manually if needed.") - response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader([]byte("invalid json"))) c.Assert(err, IsNil) c.Check(response.Code, Equals, 400) @@ -324,17 +381,71 @@ func (s *GPGSuite) TestAPIGPGDeleteKeyInvalidJSON(c *C) { // TestAPIGPGDeleteKeySuccess tests successful key deletion func (s *GPGSuite) TestAPIGPGDeleteKeySuccess(c *C) { - c.Skip("Requires GPG binary and test key installation. Run manually if needed.") + argFile, err := os.CreateTemp("", "aptly-gpg-delete-args") + c.Assert(err, IsNil) + _ = argFile.Close() + defer func() { _ = os.Remove(argFile.Name()) }() - body, err := json.Marshal(gpgDeleteKeyParams{ - Keyring: "trustedkeys.gpg", - GpgKeyID: "8B48AD6246925553", + script := "#!/bin/sh\n" + + "if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" + + "printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" + + "if printf '%s' \"$*\" | grep -q -- '--delete-keys'; then\n" + + "echo 'deleted'\n" + + "exit 0\n" + + "fi\n" + + "exit 1\n" + + s.withFakeGPG(c, script, func(_ string) { + body, marshalErr := json.Marshal(gpgDeleteKeyParams{ + Keyring: "/trustedkeys.gpg", + GpgKeyID: "8B48AD6246925553", + }) + c.Assert(marshalErr, IsNil) + + response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body)) + c.Assert(reqErr, IsNil) + c.Check(response.Code, Equals, 200) + c.Check(response.Body.String(), Matches, `"deleted\\n"`) + + argBytes, readErr := os.ReadFile(argFile.Name()) + c.Assert(readErr, IsNil) + argText := string(argBytes) + c.Check(argText, Matches, `(?s).*--batch\n--yes\n.*`) + c.Check(argText, Matches, `(?s).*--keyring\n/trustedkeys\.gpg\n.*`) + c.Check(argText, Matches, `(?s).*--delete-keys\n8B48AD6246925553\n.*`) + }) +} + +// TestAPIGPGListKeysCommandFailure tests list error propagation from gpg +func (s *GPGSuite) TestAPIGPGListKeysCommandFailure(c *C) { + script := "#!/bin/sh\n" + + "if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" + + "if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" + + "echo 'keyring missing'\n" + + "exit 1\n" + + "fi\n" + + "exit 1\n" + + s.withFakeGPG(c, script, func(_ string) { + response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil) + c.Assert(err, IsNil) + c.Check(response.Code, Equals, 400) + c.Check(response.Body.String(), Matches, `(?s).*failed to list keys: keyring missing.*`) + }) +} + +// TestAPIGPGDeleteKeyCommandFailure tests delete error propagation from gpg +func (s *GPGSuite) TestAPIGPGDeleteKeyCommandFailure(c *C) { + s.withFakeGPG(c, s.fakeGPGScript(c, "", "", "delete failed"), func(_ string) { + body, err := json.Marshal(gpgDeleteKeyParams{ + Keyring: "trustedkeys.gpg", + GpgKeyID: "8B48AD6246925553", + }) + c.Assert(err, IsNil) + + response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body)) + c.Assert(reqErr, IsNil) + c.Check(response.Code, Equals, 400) + c.Check(response.Body.String(), Matches, `(?s).*failed to delete key: delete failed.*`) }) - c.Assert(err, IsNil) - - response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body)) - c.Assert(err, IsNil) - // Should succeed (200) or fail gracefully (400) if key doesn't exist - code := response.Code - c.Check(code == 200 || code == 400, Equals, true) }