Phase 3: move service layer, serializers and DTOs into WarpEngine + dummy-app test suite
- all catalog services (update/software/highlighted/builds/file/file-manager/ download/image + SoftwareResponseBuilder), the SoftwareUpdater platform services and their concerns, Blueprinter serializers (incl. TimestampFields) and the 4 DTOs now live in the engine under WarpEngine:: - constantize dispatch strings use absolute names (WarpEngine::SoftwareUpdater::<Platform>Service) - container paths read from WarpEngine.config everywhere (FileService, DownloadService, FileManagerService, ArchiveExtraction, ReleaseSerializer path rewriting); FileManagerService base path is now lazy - engine requires blueprinter itself; gemspec declares blueprinter + rubyzip - engine test suite: spec/dummy app (mysql warp_engine_test, catalog-only schema), rails_helper with engine-local factories; catalog model/service specs and factories moved from the host - host suite keeps TTG specs and loads catalog factories from the engine Verified: engine suite 40 green, host suite 14 green, /api/software and /api/builds byte-identical to baselines, admin OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
FactoryBot.define do
|
||||
factory :download, class: "WarpEngine::Download" do
|
||||
file_path { "/test/file.tic" }
|
||||
ip_address { "127.0.0.1" }
|
||||
end
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
FactoryBot.define do
|
||||
factory :platform_link, class: "WarpEngine::PlatformLink" do
|
||||
sequence(:name) { |n| "Link #{n}" }
|
||||
platform { "tic80" }
|
||||
url { "https://example.com/link" }
|
||||
position { 0 }
|
||||
end
|
||||
end
|
||||
@@ -1,6 +0,0 @@
|
||||
FactoryBot.define do
|
||||
factory :release, class: "WarpEngine::Release" do
|
||||
software
|
||||
sequence(:version) { |n| "1.0.#{n}" }
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
FactoryBot.define do
|
||||
factory :software, class: "WarpEngine::Software" do
|
||||
sequence(:name) { |n| "game-#{n}" }
|
||||
title { "Test Game" }
|
||||
author { "dev" }
|
||||
platform { "tic80" }
|
||||
status { "development" }
|
||||
end
|
||||
end
|
||||
@@ -1,6 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe WarpEngine::Download, type: :model do
|
||||
it { should validate_presence_of(:file_path) }
|
||||
it { should belong_to(:release).optional }
|
||||
end
|
||||
@@ -1,52 +0,0 @@
|
||||
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 "orders by position" do
|
||||
second = create(:platform_link, position: 2)
|
||||
first = create(:platform_link, position: 1)
|
||||
|
||||
expect(WarpEngine::PlatformLink.all).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
|
||||
@@ -1,23 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe WarpEngine::Release, type: :model do
|
||||
it { should belong_to(:software) }
|
||||
it { should have_many(:downloads) }
|
||||
|
||||
describe "version uniqueness" do
|
||||
let(:software) { create(:software) }
|
||||
|
||||
it "prevents duplicate versions for same software" do
|
||||
create(:release, software: software, version: "1.0.0")
|
||||
dup = build(:release, software: software, version: "1.0.0")
|
||||
expect(dup).not_to be_valid
|
||||
end
|
||||
|
||||
it "allows same version across different software" do
|
||||
other_sw = create(:software)
|
||||
create(:release, software: software, version: "1.0.0")
|
||||
other = build(:release, software: other_sw, version: "1.0.0")
|
||||
expect(other).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,25 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe WarpEngine::Software, type: :model do
|
||||
subject { build(:software) }
|
||||
|
||||
it { should validate_presence_of(:name) }
|
||||
it { should validate_presence_of(:title) }
|
||||
it { should validate_presence_of(:platform) }
|
||||
# MySQL utf8mb4_0900_ai_ci collation: az egyediség DB-szinten case-insensitive
|
||||
it { should validate_uniqueness_of(:name).case_insensitive }
|
||||
|
||||
# a törlést a DB-szintű ON DELETE CASCADE végzi, a modellen nincs dependent opció
|
||||
it { should have_many(:releases) }
|
||||
it { should have_many(:external_links) }
|
||||
it { should have_many(:software_images).dependent(:destroy) }
|
||||
|
||||
describe "default scope" do
|
||||
it "excludes soft-deleted records" do
|
||||
active = create(:software)
|
||||
create(:software, deleted_at: Time.current)
|
||||
|
||||
expect(WarpEngine::Software.all).to eq([active])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,6 +11,10 @@ rescue ActiveRecord::PendingMigrationError => e
|
||||
abort e.to_s.strip
|
||||
end
|
||||
|
||||
# A katalógus-factory-k (software, release, download, platform_link) az engine-ben élnek
|
||||
FactoryBot.definition_file_paths << WarpEngine::Engine.root.join("spec/factories")
|
||||
FactoryBot.reload
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.fixture_paths = [ Rails.root.join("spec/fixtures") ]
|
||||
config.use_transactional_fixtures = true
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe BuildsService do
|
||||
describe "#index" do
|
||||
subject(:result) { described_class.new.index }
|
||||
|
||||
it "returns all supported platforms with expected kinds" do
|
||||
expect(result[:platforms]).to have_key("tic80")
|
||||
expect(result[:platforms]["tic80"][:label]).to eq("TIC-80")
|
||||
expect(result[:platforms]["tic80"][:kinds]).to include("cartridge", "html")
|
||||
end
|
||||
|
||||
it "returns c64 with only cartridge" do
|
||||
expect(result[:platforms]["c64"][:kinds]).to eq(["cartridge"])
|
||||
end
|
||||
|
||||
it "returns allKinds matching WarpEngine::ReleaseAsset::KINDS" do
|
||||
expect(result[:allKinds]).to eq(WarpEngine::ReleaseAsset::KINDS)
|
||||
end
|
||||
|
||||
it "includes all supported platforms" do
|
||||
WarpEngine::PlatformLink::SUPPORTED_PLATFORMS.each do |platform|
|
||||
expect(result[:platforms]).to have_key(platform)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#show" do
|
||||
let!(:software) { create(:software, name: "test-game", platform: "love") }
|
||||
let!(:release) { create(:release, software: software, version: "1.0.0") }
|
||||
|
||||
before do
|
||||
WarpEngine::ReleaseAsset.create!(release: release, kind: "html", path: "/test/html")
|
||||
WarpEngine::ReleaseAsset.create!(release: release, kind: "win_x64", path: "/test/win")
|
||||
end
|
||||
|
||||
it "returns expected and actual kinds per release" do
|
||||
result = described_class.new.show("test-game")
|
||||
|
||||
expect(result[:platform]).to eq("love")
|
||||
expect(result[:expected]).to match_array(%w[html win_x64 linux_x64 mac_universal])
|
||||
expect(result[:releases]["1.0.0"][:actual]).to match_array(%w[html win_x64])
|
||||
expect(result[:releases]["1.0.0"][:missing]).to match_array(%w[linux_x64 mac_universal])
|
||||
end
|
||||
|
||||
it "raises RecordNotFound for unknown software" do
|
||||
expect { described_class.new.show("nonexistent") }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe SoftwareHighlightedService do
|
||||
describe "#index" do
|
||||
it "returns nil when no highlighted software" do
|
||||
create(:software, highlighted: false)
|
||||
|
||||
result = described_class.new.index
|
||||
expect(result).to be_nil
|
||||
end
|
||||
|
||||
it "returns highlighted software" do
|
||||
sw = create(:software, highlighted: true, title: "Featured")
|
||||
|
||||
result = described_class.new.index
|
||||
expect(result).not_to be_nil
|
||||
expect(result[:software][:title]).to eq("Featured")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,79 +0,0 @@
|
||||
require "rails_helper"
|
||||
require "zip"
|
||||
|
||||
RSpec.describe SoftwareUpdater::Tic80Service do
|
||||
let(:tmpdir) { Dir.mktmpdir }
|
||||
let(:name) { "spectic" }
|
||||
let(:version) { "9.9" }
|
||||
let(:versioned) { "#{name}-#{version}" }
|
||||
|
||||
before do
|
||||
allow(ENV).to receive(:fetch).and_call_original
|
||||
allow(ENV).to receive(:fetch).with("FILE_CONTAINER_PATH", "/softwares").and_return(tmpdir)
|
||||
|
||||
write_zip("#{versioned}.html.zip", "index.html" => "<html></html>")
|
||||
File.write(File.join(tmpdir, "#{versioned}.tic"), "TIC!")
|
||||
File.write(File.join(tmpdir, "#{versioned}.lua"), <<~LUA)
|
||||
-- title: Spec TIC
|
||||
-- name: #{name}
|
||||
-- author: RSpec
|
||||
-- desc: spec fixture
|
||||
-- version: #{version}
|
||||
function TIC() end
|
||||
LUA
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.remove_entry(tmpdir)
|
||||
WarpEngine::Software.unscoped.where(name: name).each do |sw|
|
||||
WarpEngine::Release.unscoped.where(software_id: sw.id).each do |r|
|
||||
WarpEngine::ReleaseAsset.unscoped.where(release_id: r.id).delete_all
|
||||
r.delete
|
||||
end
|
||||
WarpEngine::ExternalLink.unscoped.where(software_id: sw.id).delete_all
|
||||
sw.delete
|
||||
end
|
||||
end
|
||||
|
||||
def write_zip(filename, entries)
|
||||
Zip::File.open(File.join(tmpdir, filename), create: true) do |zip|
|
||||
entries.each do |entry_name, content|
|
||||
zip.get_output_stream(entry_name) { |io| io.write(content) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def asset_kinds(release)
|
||||
WarpEngine::ReleaseAsset.unscoped.where(release_id: release.id).pluck(:kind)
|
||||
end
|
||||
|
||||
it "registers the release without a docs zip" do
|
||||
release = described_class.new.update(name, version)
|
||||
|
||||
expect(asset_kinds(release)).to match_array(%w[cartridge source html])
|
||||
end
|
||||
|
||||
it "registers docs when the docs zip is present" do
|
||||
write_zip("#{versioned}-docs.zip", "index.html" => "docs")
|
||||
|
||||
release = described_class.new.update(name, version)
|
||||
|
||||
expect(asset_kinds(release)).to include("docs")
|
||||
end
|
||||
|
||||
it "registers binary assets when slug zips are present" do
|
||||
write_zip("#{versioned}-win-x64.zip", "game.exe" => "MZ")
|
||||
write_zip("#{versioned}-mac-x64.zip", "game" => "bin")
|
||||
|
||||
release = described_class.new.update(name, version)
|
||||
|
||||
expect(asset_kinds(release)).to include("win_x64", "mac_x64")
|
||||
expect(asset_kinds(release)).not_to include("linux_x64")
|
||||
end
|
||||
|
||||
it "exposes expected_kinds from included Build concerns" do
|
||||
expect(described_class.expected_kinds).to match_array(
|
||||
%w[cartridge source html docs win_x64 linux_x64 mac_x64]
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1,59 +0,0 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe UpdateService do
|
||||
describe "#update" do
|
||||
it "raises ArgumentError for unsupported platform" do
|
||||
input = UpdateInputDto.new(platform: "unknown", name: "game", version: "1.0")
|
||||
|
||||
expect { described_class.new.update(input) }.to raise_error(ArgumentError, /Unsupported platform/)
|
||||
end
|
||||
|
||||
it "routes to correct platform service" do
|
||||
input = UpdateInputDto.new(platform: "tic80", name: "game", version: "1.0")
|
||||
mock_service = instance_double(SoftwareUpdater::Tic80Service)
|
||||
|
||||
allow(SoftwareUpdater::Tic80Service).to receive(:new).and_return(mock_service)
|
||||
allow(mock_service).to receive(:update)
|
||||
|
||||
described_class.new.update(input)
|
||||
|
||||
expect(mock_service).to have_received(:update).with("game", "1.0")
|
||||
end
|
||||
|
||||
it "routes godot platform to GodotService" do
|
||||
input = UpdateInputDto.new(platform: "godot", name: "game", version: "1.0")
|
||||
mock_service = instance_double(SoftwareUpdater::GodotService)
|
||||
|
||||
allow(SoftwareUpdater::GodotService).to receive(:new).and_return(mock_service)
|
||||
allow(mock_service).to receive(:update)
|
||||
|
||||
described_class.new.update(input)
|
||||
|
||||
expect(mock_service).to have_received(:update).with("game", "1.0")
|
||||
end
|
||||
|
||||
it "routes bevy platform to BevyService" do
|
||||
input = UpdateInputDto.new(platform: "bevy", name: "game", version: "1.0")
|
||||
mock_service = instance_double(SoftwareUpdater::BevyService)
|
||||
|
||||
allow(SoftwareUpdater::BevyService).to receive(:new).and_return(mock_service)
|
||||
allow(mock_service).to receive(:update)
|
||||
|
||||
described_class.new.update(input)
|
||||
|
||||
expect(mock_service).to have_received(:update).with("game", "1.0")
|
||||
end
|
||||
|
||||
it "routes phaser platform to PhaserService" do
|
||||
input = UpdateInputDto.new(platform: "phaser", name: "game", version: "1.0")
|
||||
mock_service = instance_double(SoftwareUpdater::PhaserService)
|
||||
|
||||
allow(SoftwareUpdater::PhaserService).to receive(:new).and_return(mock_service)
|
||||
allow(mock_service).to receive(:update)
|
||||
|
||||
described_class.new.update(input)
|
||||
|
||||
expect(mock_service).to have_received(:update).with("game", "1.0")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user