Skip to content

Commit c43468b

Browse files
committed
adds tests
1 parent 75d125d commit c43468b

4 files changed

Lines changed: 289 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (C) 2026 l3montree GmbH
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as
5+
// published by the Free Software Foundation, either version 3 of the
6+
// License, or (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU Affero General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Affero General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package dependencyfirewall
17+
18+
import "testing"
19+
20+
func TestGoEcosystem(t *testing.T) {
21+
t.Run("trimPrefix with and without secret", func(t *testing.T) {
22+
cases := []struct {
23+
name string
24+
path string
25+
expected string
26+
}{
27+
{
28+
name: "without secret",
29+
path: "/api/v1/dependency-proxy/go/github.com/foo/bar",
30+
expected: "github.com/foo/bar",
31+
},
32+
{
33+
name: "with secret",
34+
path: "/api/v1/dependency-proxy/550e8400-e29b-41d4-a716-446655440000/go/github.com/foo/bar",
35+
expected: "github.com/foo/bar",
36+
},
37+
}
38+
39+
for _, tc := range cases {
40+
t.Run(tc.name, func(t *testing.T) {
41+
if got := golang.trimPrefix(tc.path); got != tc.expected {
42+
t.Fatalf("expected %q, got %q", tc.expected, got)
43+
}
44+
})
45+
}
46+
})
47+
48+
t.Run("parse explicit version info path", func(t *testing.T) {
49+
pkg, version := golang.parsePackage("/github.com/foo/bar@v/v1.2.3.info")
50+
if pkg != "github.com/foo/bar" || version != "v1.2.3" {
51+
t.Fatalf("expected github.com/foo/bar@v1.2.3, got %q@%q", pkg, version)
52+
}
53+
})
54+
55+
t.Run("parse list path returns empty version", func(t *testing.T) {
56+
pkg, version := golang.parsePackage("/github.com/foo/bar@v/list")
57+
if pkg != "github.com/foo/bar" || version != "" {
58+
t.Fatalf("expected github.com/foo/bar with empty version, got %q@%q", pkg, version)
59+
}
60+
})
61+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (C) 2026 l3montree GmbH
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as
5+
// published by the Free Software Foundation, either version 3 of the
6+
// License, or (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU Affero General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Affero General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package dependencyfirewall
17+
18+
import "testing"
19+
20+
func TestNPMEcosystem(t *testing.T) {
21+
t.Run("trimPrefix with and without secret", func(t *testing.T) {
22+
cases := []struct {
23+
name string
24+
path string
25+
expected string
26+
}{
27+
{
28+
name: "without secret",
29+
path: "/api/v1/dependency-proxy/npm/lodash",
30+
expected: "lodash",
31+
},
32+
{
33+
name: "with secret",
34+
path: "/api/v1/dependency-proxy/550e8400-e29b-41d4-a716-446655440000/npm/@babel/core",
35+
expected: "@babel/core",
36+
},
37+
}
38+
39+
for _, tc := range cases {
40+
t.Run(tc.name, func(t *testing.T) {
41+
if got := npm.trimPrefix(tc.path); got != tc.expected {
42+
t.Fatalf("expected %q, got %q", tc.expected, got)
43+
}
44+
})
45+
}
46+
})
47+
48+
t.Run("parse metadata path", func(t *testing.T) {
49+
pkg, version := npm.parsePackage("/lodash")
50+
if pkg != "lodash" || version != "" {
51+
t.Fatalf("expected lodash with empty version, got %q and %q", pkg, version)
52+
}
53+
})
54+
55+
t.Run("parse tarball path", func(t *testing.T) {
56+
pkg, version := npm.parsePackage("/lodash/-/lodash-4.17.21.tgz")
57+
if pkg != "lodash" || version != "4.17.21" {
58+
t.Fatalf("expected lodash@4.17.21, got %q@%q", pkg, version)
59+
}
60+
})
61+
62+
t.Run("parse scoped tarball path", func(t *testing.T) {
63+
pkg, version := npm.parsePackage("/@babel/core/-/core-7.23.0.tgz")
64+
if pkg != "@babel/core" || version != "7.23.0" {
65+
t.Fatalf("expected @babel/core@7.23.0, got %q@%q", pkg, version)
66+
}
67+
})
68+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (C) 2026 l3montree GmbH
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as
5+
// published by the Free Software Foundation, either version 3 of the
6+
// License, or (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU Affero General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Affero General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package dependencyfirewall
17+
18+
import "testing"
19+
20+
func TestPyPIParsePackage(t *testing.T) {
21+
t.Run("parses simple index path", func(t *testing.T) {
22+
pkg, version := pypi.parsePackage("/simple/requests/")
23+
if pkg != "requests" || version != "" {
24+
t.Fatalf("expected requests with empty version, got %q and %q", pkg, version)
25+
}
26+
})
27+
28+
t.Run("parses package artifact path with leading slash", func(t *testing.T) {
29+
pkg, version := pypi.parsePackage("/packages/ab/cd/requests-2.31.0-py3-none-any.whl")
30+
if pkg != "requests" || version != "2.31.0" {
31+
t.Fatalf("expected requests@2.31.0, got %q@%q", pkg, version)
32+
}
33+
})
34+
}
35+
36+
func TestPyPIEcosystemTrimPrefix(t *testing.T) {
37+
cases := []struct {
38+
name string
39+
path string
40+
expected string
41+
}{
42+
{
43+
name: "without secret",
44+
path: "/api/v1/dependency-proxy/pypi/simple/requests/",
45+
expected: "simple/requests/",
46+
},
47+
{
48+
name: "with secret",
49+
path: "/api/v1/dependency-proxy/550e8400-e29b-41d4-a716-446655440000/pypi/simple/requests/",
50+
expected: "simple/requests/",
51+
},
52+
}
53+
54+
for _, tc := range cases {
55+
t.Run(tc.name, func(t *testing.T) {
56+
if got := pypi.trimPrefix(tc.path); got != tc.expected {
57+
t.Fatalf("expected %q, got %q", tc.expected, got)
58+
}
59+
})
60+
}
61+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (C) 2026 l3montree GmbH
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU Affero General Public License as
5+
// published by the Free Software Foundation, either version 3 of the
6+
// License, or (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU Affero General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Affero General Public License
14+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package dependencyfirewall
17+
18+
import (
19+
"net/http"
20+
"net/http/httptest"
21+
"testing"
22+
23+
"github.com/labstack/echo/v4"
24+
)
25+
26+
func TestEnsureReadMethod(t *testing.T) {
27+
t.Run("allows GET", func(t *testing.T) {
28+
e := echo.New()
29+
req := httptest.NewRequest(http.MethodGet, "/", nil)
30+
rec := httptest.NewRecorder()
31+
c := e.NewContext(req, rec)
32+
33+
if err := ensureReadMethod(c); err != nil {
34+
t.Fatalf("expected GET to be allowed, got error: %v", err)
35+
}
36+
})
37+
38+
t.Run("allows HEAD", func(t *testing.T) {
39+
e := echo.New()
40+
req := httptest.NewRequest(http.MethodHead, "/", nil)
41+
rec := httptest.NewRecorder()
42+
c := e.NewContext(req, rec)
43+
44+
if err := ensureReadMethod(c); err != nil {
45+
t.Fatalf("expected HEAD to be allowed, got error: %v", err)
46+
}
47+
})
48+
49+
t.Run("rejects POST", func(t *testing.T) {
50+
e := echo.New()
51+
req := httptest.NewRequest(http.MethodPost, "/", nil)
52+
rec := httptest.NewRecorder()
53+
c := e.NewContext(req, rec)
54+
55+
err := ensureReadMethod(c)
56+
if err == nil {
57+
t.Fatal("expected POST to be rejected")
58+
}
59+
60+
httpErr, ok := err.(*echo.HTTPError)
61+
if !ok {
62+
t.Fatalf("expected *echo.HTTPError, got %T", err)
63+
}
64+
if httpErr.Code != http.StatusMethodNotAllowed {
65+
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, httpErr.Code)
66+
}
67+
})
68+
}
69+
70+
func TestPassthroughUpstreamResponse(t *testing.T) {
71+
e := echo.New()
72+
req := httptest.NewRequest(http.MethodGet, "/", nil)
73+
rec := httptest.NewRecorder()
74+
c := e.NewContext(req, rec)
75+
76+
d := &DependencyProxyController{}
77+
headers := http.Header{}
78+
headers.Add("X-Test", "a")
79+
headers.Add("X-Test", "b")
80+
headers.Set("Content-Type", "application/json")
81+
body := []byte(`{"ok":true}`)
82+
83+
if err := d.passthroughUpstreamResponse(c, headers, http.StatusAccepted, body); err != nil {
84+
t.Fatalf("expected no error, got %v", err)
85+
}
86+
87+
if rec.Code != http.StatusAccepted {
88+
t.Fatalf("expected status %d, got %d", http.StatusAccepted, rec.Code)
89+
}
90+
if got := rec.Body.String(); got != string(body) {
91+
t.Fatalf("expected body %q, got %q", string(body), got)
92+
}
93+
if got := rec.Header().Values("X-Test"); len(got) != 2 || got[0] != "a" || got[1] != "b" {
94+
t.Fatalf("expected X-Test headers [a b], got %v", got)
95+
}
96+
if got := rec.Header().Get("Content-Type"); got != "application/json" {
97+
t.Fatalf("expected Content-Type application/json, got %q", got)
98+
}
99+
}

0 commit comments

Comments
 (0)