Something went wrong. Try again.
A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103const { test } = require("node:test");const assert = require("node:assert/strict");const fs = require("node:fs");const path = require("node:path");const { execFileSync } = require("node:child_process");const root = path.resolve(__dirname, "../../..");const spec = JSON.parse( fs.readFileSync(path.join(root, "docs/api-reference/openapi.json")),);const operation = (name, method = "get") => spec.paths["/app.rocksky." + name][method];const schema = (name) => spec.components.schemas["app.rocksky." + name];
test("generated OpenAPI is current", () => { execFileSync(process.execPath, [ path.join(root, "apps/api/scripts/generate-openapi.cjs"), "--check", ]);});test("documents only registered endpoints, including the uploaded library", () => { assert.equal(Object.keys(spec.paths).length, 146); for (const name of [ "library.getAlbum", "notification.listNotifications", "stats.getWrapped", "song.matchSong", "equalizer.listPresets", ]) assert.ok(operation(name)); for (const name of [ "feed.describeFeedGenerator", "feed.getFeedSkeleton", "player.addDirectoryToQueue", ]) assert.equal(spec.paths["/app.rocksky." + name], undefined);});test("distinguishes public, optional and authenticated operations", () => { assert.deepEqual(operation("song.getSongs").security, []); assert.deepEqual(operation("song.getSong").security, [{}, { Bearer: [] }]); assert.deepEqual(operation("library.getAlbum").security, [{ Bearer: [] }]); assert.deepEqual(operation("scrobble.createScrobble", "post").security, [ { Bearer: [] }, ]);});test("preserves procedure query parameters and JSON bodies", () => { assert.ok( operation("player.play", "post").parameters.some( (p) => p.name === "playerId" && p.in === "query", ), ); assert.ok( operation("scrobble.createScrobble", "post").requestBody.content[ "application/json" ], ); assert.ok( operation("song.getSong").parameters.some((p) => p.name === "spotifyId"), );});test("includes identifiers, nullable values and fractional wire fields", () => { const song = schema("song.defs__songViewDetailed"); assert.ok(song.properties.mbId); assert.ok(song.properties.isrc); assert.deepEqual(song.properties.bpm.type, ["number", "null"]); assert.ok( Object.values(song.properties).some((p) => p.anyOf?.some((t) => t.type === "null"), ), ); assert.equal( schema("actor.defs__compatibilityViewBasic").properties .compatibilityPercentage.type, "number", );});test("does not promise successful downloads from unimplemented handlers", () => { assert.equal(spec.paths["/app.rocksky.dropbox.downloadFile"], undefined); assert.equal(spec.paths["/app.rocksky.googledrive.downloadFile"], undefined); assert.equal( operation("player.next", "post")["x-implementation-status"], "placeholder", ); assert.equal( operation("spotify.next", "post")["x-implementation-status"], undefined, );});test("schema refs resolve and nullable properties stay optional unless required", () => { const walk = (value) => { if (!value || typeof value !== "object") return; if (value.$ref) assert.ok( spec.components.schemas[value.$ref.split("/").at(-1)], value.$ref, ); if (value.required && value.properties) for (const name of value.required) assert.ok(name in value.properties, name); Object.values(value).forEach(walk); }; walk(spec);});