Four small duplications, each of which had already drifted or was one edit away from it. **The JSON error body.** The host's `ApiController` was a line-by-line copy of the engine's four `rescue_from` blocks plus `resolve_mime`. It is a `WarpEngine::ApiErrorRendering` concern now, included by both, so "what a failure looks like on the wire" is decided once. **Mime resolution.** The same three lines lived in the engine's API controller and the host's. `WarpEngine::Storage.mime_for` owns it — next to the adapter that hands out the files. **Finding the asset behind a path.** `DownloadService` and `FileService` each escaped `%` and `_` by hand and ran their own `LIKE` — the second one without the exact-match attempt the first one had. `ReleaseAsset.for_relative_path` is the one lookup: the absolute path first, then a suffix match anchored at `/` rather than the old `%path%`, which could match a different file whose name merely contained this one. Two now-unreachable helpers (`DownloadService.base_path`, `FileService.base_path`) go with it. **Ordering.** `PlatformLink` and `SoftwareImage` ordered inside their `default_scope`, which leaks into every association and aggregate — the proof was already in the tree: `admin/images.rb` had to write `SoftwareImage.unscope(:order).distinct.pluck(:image_id)`, because MySQL will not order a DISTINCT by a column it does not select. Ordering is a scope you ask for now (`ordered`), the two places that need it ask (`for_platform`, the `software_images` association, so the API's image order is unchanged), and the `unscope` is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
63 lines
2.0 KiB
Ruby
63 lines
2.0 KiB
Ruby
require "rails_helper"
|
|
|
|
RSpec.describe WarpEngine::PlatformLink, type: :model do
|
|
describe "validations" do
|
|
it { should validate_presence_of(:name) }
|
|
it { should validate_presence_of(:url) }
|
|
it { should validate_presence_of(:platform) }
|
|
|
|
it "rejects invalid platform" do
|
|
link = build(:platform_link, platform: "invalid")
|
|
expect(link).not_to be_valid
|
|
expect(link.errors[:platform]).to be_present
|
|
end
|
|
|
|
it "accepts valid platforms" do
|
|
WarpEngine::PlatformLink::SUPPORTED_PLATFORMS.each do |p|
|
|
link = build(:platform_link, platform: p)
|
|
expect(link).to be_valid
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "default scope" do
|
|
it "excludes soft-deleted records" do
|
|
active = create(:platform_link)
|
|
create(:platform_link, deleted_at: Time.current)
|
|
|
|
expect(WarpEngine::PlatformLink.all).to eq([active])
|
|
end
|
|
|
|
it "does not order: ordering is asked for, not inherited" do
|
|
second = create(:platform_link, position: 2)
|
|
first = create(:platform_link, position: 1)
|
|
|
|
expect(WarpEngine::PlatformLink.ordered).to eq([ first, second ])
|
|
expect(WarpEngine::PlatformLink.all.to_sql).not_to include("ORDER BY")
|
|
end
|
|
end
|
|
|
|
describe ".ordered" do
|
|
it "is what .for_platform uses, so the API keeps its link order" do
|
|
second = create(:platform_link, platform: "tic80", position: 2)
|
|
first = create(:platform_link, platform: "tic80", position: 1)
|
|
|
|
expect(WarpEngine::PlatformLink.for_platform("tic80")).to eq([ first, second ])
|
|
end
|
|
end
|
|
|
|
describe ".for_platform" do
|
|
it "returns links for given platform only" do
|
|
tic80_link = create(:platform_link, platform: "tic80")
|
|
create(:platform_link, platform: "love")
|
|
|
|
result = WarpEngine::PlatformLink.for_platform("tic80")
|
|
expect(result).to eq([tic80_link])
|
|
end
|
|
|
|
it "returns empty array for platform without links" do
|
|
expect(WarpEngine::PlatformLink.for_platform("godot")).to eq([])
|
|
end
|
|
end
|
|
end
|