Carry a store's configuration in the registry record
A store's `config.json` lived in a repository the desktop client fetched over HTTP, which made a store's behaviour depend on a second thing existing and staying reachable. The registry already answers what a store *is*; it now answers how it behaves too, in the same shape that file had, so this record is the one source of truth and a store can be configured from the admin alone. `storeRepositoryUrl` stays, demoted to a pointer for a person — where the store's own repository is, when it has one. Clients released before this field still fetch a `config.json` from it, so nothing has to move at once. The column is nullable because a store that configures nothing is still a store: the client falls back to the engine's built-in defaults, which need only a name and a catalog. The admin edits it as JSON text through a pair of accessors, so the column holds real JSON and invalid input comes back with the text kept and a message rather than a 500. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,11 +19,30 @@ RSpec.describe Api::StoresController, type: :request do
|
||||
get "/api/stores"
|
||||
|
||||
store = JSON.parse(response.body).first
|
||||
expect(store).to include("name", "catalogUrl", "storeRepositoryUrl")
|
||||
expect(store).to include("name", "catalogUrl", "storeRepositoryUrl", "config")
|
||||
expect(store["catalogUrl"]).to eq("https://teletypegames.org")
|
||||
expect(store["storeRepositoryUrl"]).to end_with("/stores/ttg-desktop-store")
|
||||
end
|
||||
|
||||
# The config is what the client applies instead of fetching a repository's
|
||||
# config.json, so it has to come back as an object rather than as JSON text.
|
||||
it "carries the store's own config as an object" do
|
||||
create(:store, config: { "paths" => { "subfolder" => "teletypegames" } })
|
||||
|
||||
get "/api/stores"
|
||||
|
||||
config = JSON.parse(response.body).first["config"]
|
||||
expect(config).to eq("paths" => { "subfolder" => "teletypegames" })
|
||||
end
|
||||
|
||||
it "answers null for a store that configures nothing" do
|
||||
create(:store, config: nil)
|
||||
|
||||
get "/api/stores"
|
||||
|
||||
expect(JSON.parse(response.body).first["config"]).to be_nil
|
||||
end
|
||||
|
||||
it "answers null for a store with no repository" do
|
||||
create(:store, store_repository_url: nil)
|
||||
|
||||
|
||||
@@ -21,6 +21,52 @@ RSpec.describe Store, type: :model do
|
||||
expect(build(:store, store_repository_url: "")).to be_valid
|
||||
end
|
||||
|
||||
describe "config" do
|
||||
it "accepts a store that configures nothing" do
|
||||
expect(build(:store, config: nil)).to be_valid
|
||||
end
|
||||
|
||||
it "rejects a config that is not a JSON object" do
|
||||
store = build(:store, config: ["not", "an", "object"])
|
||||
expect(store).not_to be_valid
|
||||
expect(store.errors[:config]).to include("must be a JSON object")
|
||||
end
|
||||
|
||||
# The admin form edits JSON text; the column holds real JSON. These two accessors
|
||||
# are the only bridge between them, so both directions are worth pinning.
|
||||
it "parses config_json into the column" do
|
||||
store = build(:store, config_json: '{"paths": {"subfolder": "teletypegames"}}')
|
||||
|
||||
expect(store).to be_valid
|
||||
expect(store.config).to eq("paths" => { "subfolder" => "teletypegames" })
|
||||
end
|
||||
|
||||
it "renders the column back as pretty JSON text" do
|
||||
store = build(:store, config: { "paths" => { "subfolder" => "teletypegames" } })
|
||||
|
||||
expect(JSON.parse(store.config_json)).to eq(store.config)
|
||||
expect(store.config_json).to include("\n")
|
||||
end
|
||||
|
||||
it "clears the config when the text is emptied" do
|
||||
store = build(:store, config: { "paths" => {} })
|
||||
store.config_json = ""
|
||||
|
||||
expect(store).to be_valid
|
||||
expect(store.config).to be_nil
|
||||
end
|
||||
|
||||
# Refused with the text kept, rather than raising: the form has to come back with
|
||||
# what the person typed and a message they can act on.
|
||||
it "refuses invalid JSON and keeps the text" do
|
||||
store = build(:store, config_json: '{"paths": ')
|
||||
|
||||
expect(store).not_to be_valid
|
||||
expect(store.errors[:config].first).to start_with("is not valid JSON")
|
||||
expect(store.config_json).to eq('{"paths": ')
|
||||
end
|
||||
end
|
||||
|
||||
describe ".ordered" do
|
||||
it "lists stores by name" do
|
||||
later = create(:store, name: "Zed Games")
|
||||
|
||||
Reference in New Issue
Block a user