mirror of
https://github.com/go-task/task.git
synced 2026-09-02 04:02:08 +02:00
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.
85 lines
2.2 KiB
Go
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, ¬FoundErr)
|
|
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, ¬FoundErr)
|
|
}
|
|
|
|
func mustParse(t *testing.T, rawURL string) *url.URL {
|
|
t.Helper()
|
|
parsed, err := url.Parse(rawURL)
|
|
require.NoError(t, err)
|
|
return parsed
|
|
}
|