Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions api/dbv1/get_developer_apps.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 14 additions & 11 deletions api/dbv1/queries/get_developer_apps.sql
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
-- name: GetDeveloperApps :many
SELECT
address,
user_id,
name,
description,
image_url
FROM developer_apps
WHERE
(user_id = @user_id OR address = @address)
AND is_current = true
AND is_delete = false
ORDER BY created_at DESC;
da.address,
da.user_id,
da.name,
da.description,
da.image_url,
COALESCE(ARRAY_AGG(oau.redirect_uri ORDER BY oau.id) FILTER (WHERE oau.redirect_uri IS NOT NULL), ARRAY[]::text[])::text[] AS redirect_uris
FROM developer_apps da
LEFT JOIN oauth_redirect_uris oau ON oau.client_id = da.address
Comment on lines +6 to +10
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New redirect_uris aggregation is now part of the GetDeveloperApps result set, but there’s no test coverage exercising this behavior (e.g., when URIs exist and when none exist). Please extend the existing developer app query/API tests to assert redirect_uris is returned (and is an empty array rather than null) and that ordering is deterministic (by oauth_redirect_uris.id).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test coverage in e70017c:

  • TestGetDeveloperAppsQueries: asserts RedirectUris equals []string{} (not nil) at the query layer when no URIs are registered
  • TestGetDeveloperApp: asserts the deserialized RedirectUris field is an empty slice (not nil) from the /v1/developer_apps/:address endpoint
  • TestV1UsersDeveloperApps: uses gjson.IsArray() to assert redirect_uris is a JSON array (not null) with length 0 for each app without registered URIs
  • TestV1UsersDeveloperAppsRedirectUrisOrdering (new): inserts URIs with z before a alphabetically but in ascending id order, then asserts the response matches insertion order — confirming ordering by oauth_redirect_uris.id, not alphabetically. Also asserts the second app (no URIs) returns [] not null.

WHERE
(da.user_id = @user_id OR da.address = @address)
AND da.is_current = true
AND da.is_delete = false
GROUP BY da.address, da.user_id, da.name, da.description, da.image_url, da.created_at
ORDER BY da.created_at DESC;

-- name: GetDeveloperAppsWithGrants :many
SELECT
Expand Down
4 changes: 4 additions & 0 deletions api/v1_developer_apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ func TestGetDeveloperAppsQueries(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, developerApps, 1)
assert.Equal(t, "0x7d7b6b7a97d1deefe3a1ccc5a13c48e8f055e0b6", developerApps[0].Address)
// redirect_uris must be an empty slice (not nil) when no URIs are registered
assert.Equal(t, []string{}, developerApps[0].RedirectUris)
}

func TestGetDeveloperApp(t *testing.T) {
Expand All @@ -29,4 +31,6 @@ func TestGetDeveloperApp(t *testing.T) {
assert.True(t, strings.Contains(string(body), `"user_id":"7eP5n"`))
assert.True(t, strings.Contains(string(body), `"name":"cool app"`))
assert.Equal(t, "cool app", resp.Data.Name)
// redirect_uris must be an empty slice (not nil) when no URIs are registered
assert.Equal(t, []string{}, resp.Data.RedirectUris)
}
1 change: 0 additions & 1 deletion api/v1_oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,6 @@ func (app *ApiServer) v1OAuthRevoke(c *fiber.Ctx) error {
return c.JSON(fiber.Map{})
}


// --- Helper methods ---

// validateJWTIat extracts and validates the iat (issued-at) claim from a JWT.
Expand Down
60 changes: 60 additions & 0 deletions api/v1_users_developer_apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"api.audius.co/database"
"api.audius.co/trashid"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
)

func TestV1UsersDeveloperApps(t *testing.T) {
Expand Down Expand Up @@ -68,6 +69,11 @@ func TestV1UsersDeveloperApps(t *testing.T) {
"data.1.name": "app_name_2",
"data.1.description": "app_description_2",
})
// redirect_uris must be an empty array (not null) when no URIs are registered
assert.True(t, gjson.GetBytes(body, "data.0.redirect_uris").IsArray())
assert.Equal(t, 0, len(gjson.GetBytes(body, "data.0.redirect_uris").Array()))
assert.True(t, gjson.GetBytes(body, "data.1.redirect_uris").IsArray())
assert.Equal(t, 0, len(gjson.GetBytes(body, "data.1.redirect_uris").Array()))
}

{
Expand Down Expand Up @@ -125,3 +131,57 @@ func TestV1UsersDeveloperAppsIncludeMetrics(t *testing.T) {
"data.0.api_access_keys.#": 0,
})
}

// TestV1UsersDeveloperAppsRedirectUrisOrdering verifies that redirect_uris are returned
// in deterministic order (by oauth_redirect_uris.id) rather than alphabetically,
// and that apps without registered URIs return an empty array rather than null.
func TestV1UsersDeveloperAppsRedirectUrisOrdering(t *testing.T) {
app := emptyTestApp(t)

fixtures := database.FixtureMap{
"users": []map[string]any{
{"user_id": 1, "handle": "user1"},
},
"developer_apps": []map[string]any{
{
"address": "app_address_1",
"user_id": 1,
"name": "app_name_1",
"is_current": true,
"is_delete": false,
"created_at": time.Now(),
},
{
"address": "app_address_2",
"user_id": 1,
"name": "app_name_2",
"is_current": true,
"is_delete": false,
"created_at": time.Now().Add(-time.Second),
},
},
// Insert z before a: if ordering were alphabetical, a would come first.
// With id ordering, z comes first because it was inserted with a lower id.
"oauth_redirect_uris": []map[string]any{
{"client_id": "app_address_1", "redirect_uri": "https://z.example.com/callback"},
{"client_id": "app_address_1", "redirect_uri": "https://a.example.com/callback"},
},
}

database.Seed(app.pool.Replicas[0], fixtures)

status, body := testGet(t, app, "/v1/users/"+trashid.MustEncodeHashID(1)+"/developer-apps")
assert.Equal(t, 200, status)
// app_address_1 has 2 redirect URIs; z was inserted first (lower id) so it must come first
jsonAssert(t, body, map[string]any{
"data.#": 2,
"data.0.address": "app_address_1",
"data.0.redirect_uris.#": 2,
"data.0.redirect_uris.0": "https://z.example.com/callback",
"data.0.redirect_uris.1": "https://a.example.com/callback",
"data.1.address": "app_address_2",
})
// app_address_2 has no redirect URIs; must be an empty array, not null
assert.True(t, gjson.GetBytes(body, "data.1.redirect_uris").IsArray())
assert.Equal(t, 0, len(gjson.GetBytes(body, "data.1.redirect_uris").Array()))
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/gofiber/contrib/fiberzap/v2 v2.1.5
github.com/gofiber/contrib/websocket v1.3.4
github.com/gofiber/fiber/v2 v2.52.9
github.com/google/uuid v1.6.0
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
github.com/jackc/pgx/v5 v5.7.4
github.com/jackc/pgxlisten v0.0.0-20241106001234-1d6f6656415c
Expand Down Expand Up @@ -122,7 +123,6 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/rpc v1.2.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gowebpki/jcs v1.0.0 // indirect
Expand Down
Loading