Two seams the hosts needed, both backward compatible.
Storage: artifacts are served through WarpEngine::Storage.adapter instead of
raw filesystem calls. The default :local adapter keeps the previous behaviour
byte for byte, including the path traversal guard. A host can now set
config.storage_adapter to any object answering file?/directory?/locate and
serve builds from an object store - FileService and /api/download both honour
a Location.redirect, so a signing adapter turns them into redirects.
DownloadService#create still returns an absolute path (nil when missing) for
existing callers; #locate is the new entry point that can also return a
redirect. Ingestion (upload, extraction, file manager) stays local for now.
Publish: PublishService emits ActiveSupport::Notifications
("warp_engine.publish") with platform/name/version/software/release, so hosts
can react to a new build without hanging callbacks on the models.
WarpEngine.instruments_publish? lets a host feature-detect and keep its
fallback for older engine versions.
99 lines
3.4 KiB
Ruby
99 lines
3.4 KiB
Ruby
require "rails_helper"
|
|
require "tmpdir"
|
|
|
|
# The serving side (FileService, DownloadService) goes through the storage
|
|
# adapter. With :local nothing changes; with a custom adapter the same call
|
|
# can hand back a redirect instead of a file.
|
|
RSpec.describe "Serving through the storage adapter" do
|
|
let(:tmpdir) { Dir.mktmpdir }
|
|
|
|
# Minimal adapter answering the documented contract.
|
|
let(:signing_adapter) do
|
|
Class.new do
|
|
def file?(_relative) = true
|
|
def directory?(_relative) = false
|
|
|
|
def locate(relative, filename: nil, expires_in: nil)
|
|
WarpEngine::Storage::Location.redirect("https://cdn.example/#{relative}?signed=1")
|
|
end
|
|
end.new
|
|
end
|
|
|
|
before do
|
|
allow(WarpEngine.config).to receive(:file_container_path).and_return(tmpdir)
|
|
WarpEngine::Storage.reset!
|
|
File.write(File.join(tmpdir, "game-1.0.zip"), "zip")
|
|
end
|
|
|
|
after { FileUtils.rm_rf(tmpdir) }
|
|
|
|
describe WarpEngine::FileService do
|
|
it "serves a local file (default adapter)" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(:local)
|
|
|
|
result = described_class.new.show(WarpEngine::FileShowInputDto.new(path: "game-1.0.zip"))
|
|
|
|
expect(result.type).to eq(:file)
|
|
expect(result.path).to eq(File.join(tmpdir, "game-1.0.zip"))
|
|
end
|
|
|
|
it "redirects a directory to its index.html" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(:local)
|
|
FileUtils.mkdir_p(File.join(tmpdir, "game-1.0"))
|
|
File.write(File.join(tmpdir, "game-1.0", "index.html"), "<h1>hi</h1>")
|
|
|
|
result = described_class.new.show(WarpEngine::FileShowInputDto.new(path: "game-1.0"))
|
|
|
|
expect(result.type).to eq(:redirect)
|
|
expect(result.url).to eq("/file/game-1.0/index.html")
|
|
end
|
|
|
|
it "hands back the adapter's redirect when storage is remote" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(signing_adapter)
|
|
|
|
result = described_class.new.show(WarpEngine::FileShowInputDto.new(path: "game-1.0.zip"))
|
|
|
|
expect(result.type).to eq(:redirect)
|
|
expect(result.url).to eq("https://cdn.example/game-1.0.zip?signed=1")
|
|
end
|
|
|
|
it "reports a missing file" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(:local)
|
|
|
|
result = described_class.new.show(WarpEngine::FileShowInputDto.new(path: "nope.zip"))
|
|
|
|
expect(result.type).to eq(:not_found)
|
|
end
|
|
end
|
|
|
|
describe WarpEngine::DownloadService do
|
|
let(:args) { { ip: "127.0.0.1", user_agent: "rspec", referer: nil } }
|
|
|
|
it "keeps returning an absolute path from #create (backward compatible)" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(:local)
|
|
|
|
path = described_class.new.create(path: "game-1.0.zip", **args)
|
|
|
|
expect(path).to eq(File.join(tmpdir, "game-1.0.zip"))
|
|
expect(WarpEngine::Download.count).to eq(1)
|
|
end
|
|
|
|
it "returns nil from #create for a missing file" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(:local)
|
|
|
|
expect(described_class.new.create(path: "nope.zip", **args)).to be_nil
|
|
expect(WarpEngine::Download.count).to eq(0)
|
|
end
|
|
|
|
it "returns a location from #locate and logs the download" do
|
|
allow(WarpEngine.config).to receive(:storage_adapter).and_return(signing_adapter)
|
|
|
|
location = described_class.new.locate(path: "game-1.0.zip", **args)
|
|
|
|
expect(location).to be_redirect
|
|
expect(location.url).to include("signed=1")
|
|
expect(WarpEngine::Download.count).to eq(1)
|
|
end
|
|
end
|
|
end
|