Files
task/taskfile/taskfile_test.go
Valentin Maerten a41df4a127 fix(remote): report a 401 instead of a missing Taskfile
RemoteExists treated every non-200 as an absent file, so a server
refusing the credentials ended up as "No Taskfile found", sending the
user to check the URL rather than the token. A 401 now stops the search
and reports the status code; the default names need the same credentials,
so trying them would only add rejected requests. A 403 is left alone: it
is also what a server without directory listing answers for a readable
directory.

That message being correct, the expansion no longer needs to refuse an
undefined variable: os.ExpandEnv is inlined and expandEnv is gone. The
`$$` escape goes with it, so a literal value can no longer hold a `$`
followed by a name; a secret carried in an environment variable is
unaffected, as os.Expand never rescans what it substituted.

Header names are validated with httpguts.ValidHeaderFieldName, the table
net/http itself uses, rather than a denylist that let X-Foo(bar) through.
golang.org/x/net was already in the module graph, so tidy only moves it
to the direct block.

Finally, node_http_auth.go becomes http_auth.go: the node_ prefix is for
files defining a Node type, and this one holds the auth concern of
HTTPNode plus hostMatches, which reader.go uses for trusted hosts.
2026-08-23 12:11:55 +02:00

85 lines
2.2 KiB
Go

package taskfile
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/errors"
)
// alwaysStatus answers every request with the given status.
func alwaysStatus(t *testing.T, status int) (*url.URL, *int) {
t.Helper()
var requests int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests++
w.WriteHeader(status)
}))
t.Cleanup(srv.Close)
return mustParse(t, srv.URL), &requests
}
func TestRemoteExistsUnauthorized(t *testing.T) {
t.Parallel()
u, requests := alwaysStatus(t, http.StatusUnauthorized)
_, err := RemoteExists(t.Context(), *u, http.DefaultClient)
var fetchErr errors.TaskfileFetchFailedError
require.ErrorAs(t, err, &fetchErr)
assert.Equal(t, http.StatusUnauthorized, fetchErr.HTTPStatusCode)
assert.Equal(t, 1, *requests)
}
// A 403 is ambiguous, so it keeps the existing behaviour.
func TestRemoteExistsForbiddenEverywhere(t *testing.T) {
t.Parallel()
u, requests := alwaysStatus(t, http.StatusForbidden)
_, err := RemoteExists(t.Context(), *u, http.DefaultClient)
var notFoundErr errors.TaskfileNotFoundError
assert.ErrorAs(t, err, &notFoundErr)
assert.Greater(t, *requests, 1)
}
func TestRemoteExistsForbiddenDirectoryWithReadableTaskfile(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/Taskfile.yml" {
w.WriteHeader(http.StatusForbidden)
return
}
w.Header().Set("Content-Type", "text/yaml")
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
found, err := RemoteExists(t.Context(), *mustParse(t, srv.URL), http.DefaultClient)
require.NoError(t, err)
assert.Equal(t, "/Taskfile.yml", found.Path)
}
func TestRemoteExistsNotFound(t *testing.T) {
t.Parallel()
u, _ := alwaysStatus(t, http.StatusNotFound)
_, err := RemoteExists(t.Context(), *u, http.DefaultClient)
var notFoundErr errors.TaskfileNotFoundError
assert.ErrorAs(t, err, &notFoundErr)
}
func mustParse(t *testing.T, rawURL string) *url.URL {
t.Helper()
parsed, err := url.Parse(rawURL)
require.NoError(t, err)
return parsed
}