api/v2: send empty route lists under fields=all

This commit is contained in:
Kristoffer Dalby committed 2026-10-30 14:55:37 +00:00
1 parent bde86a8c26
commit b17306c9b9
3 files changed
+19 -12

No files matched your search

+3 -3
View File
@@ -46,9 +46,9 @@ type Device struct {
// does not record, so it is left out.
OAuthClientID string `json:"oauthClientId,omitempty"`
// Populated only when fields=all is requested.
AdvertisedRoutes []string `json:"advertisedRoutes,omitempty"`
EnabledRoutes []string `json:"enabledRoutes,omitempty"`
// Populated only when fields=all is requested, then as [] when empty.
AdvertisedRoutes []string `json:"advertisedRoutes,omitzero"`
EnabledRoutes []string `json:"enabledRoutes,omitzero"`
}
// DeviceRoutes is the GET /device/{id}/routes response: what the node announces
-9
View File
@@ -99,14 +99,6 @@ var apiv2CompatSkipReasons = map[string]string{
"oauth/device:before/all/device-oauth-client": deviceOAuthClientMaker,
"oauth/device:joiner/all/device-oauth-client": deviceOAuthClientMaker,
"oauth/device:before/empty-route-lists": emptyRouteListsOmitted,
"oauth/device:ci-runner/empty-route-lists": emptyRouteListsOmitted,
"oauth/device:joiner/empty-route-lists": emptyRouteListsOmitted,
"oauth/device:laptop/empty-route-lists": emptyRouteListsOmitted,
"oauth/device:managed/empty-route-lists": emptyRouteListsOmitted,
"oauth/device:mixed/empty-route-lists": emptyRouteListsOmitted,
"oauth/device:prod-db/empty-route-lists": emptyRouteListsOmitted,
"policy-api/P7/0/body": hujsonFormatted,
"policy-api/P7/1/body": hujsonFormatted,
}
@@ -154,7 +146,6 @@ const (
deletedClientGone = "DELETED_CLIENT_GONE: SaaS keeps a deleted OAuth client readable as invalid and revoked; headscale removes it"
deletedClientCredentialText = "DELETED_CLIENT_CREDENTIAL_TEXT: SaaS refuses a deleted client's secret and tokens with API token invalid; headscale says invalid client credentials and unauthorized"
missingScopeText = "MISSING_SCOPE_TEXT: SaaS refuses a delete outside the token's scopes with calling actor does not have enough permissions; headscale names the missing scope"
emptyRouteListsOmitted = "EMPTY_ROUTE_LISTS_OMITTED: SaaS sends advertisedRoutes and enabledRoutes as [] for a device without routes under fields=all; headscale leaves both out"
hujsonFormatted = "HUJSON_FORMATTED: SaaS stores a HuJSON policy formatted, comments kept; headscale stores the bytes as sent"
)
+16
View File
@@ -151,6 +151,22 @@ func TestAPIv2Device_Get(t *testing.T) {
assert.Equal(t, n.IPsAsString(), dev.Addresses)
}
// TestAPIv2Device_EmptyRouteLists: fields=all sends a device's route lists
// even when they are empty; the default read leaves them out.
func TestAPIv2Device_EmptyRouteLists(t *testing.T) {
e := newDeviceTestEnv(t)
all := e.api.Get("/api/v2/device/" + e.deviceID + "?fields=all")
require.Equalf(t, http.StatusOK, all.Code, "body: %s", all.Body)
assert.Contains(t, all.Body.String(), `"advertisedRoutes":[]`)
assert.Contains(t, all.Body.String(), `"enabledRoutes":[]`)
def := e.api.Get("/api/v2/device/" + e.deviceID)
require.Equalf(t, http.StatusOK, def.Code, "body: %s", def.Body)
assert.NotContains(t, def.Body.String(), "advertisedRoutes")
assert.NotContains(t, def.Body.String(), "enabledRoutes")
}
// TestAPIv2Device_NameIsFQDN: with a base domain, a device's name is its
// MagicDNS FQDN, in a read and in the list.
func TestAPIv2Device_NameIsFQDN(t *testing.T) {