Kérésre: minden magyarázó komment kikerült a forrásfájlokból — 89 Ruby, 16 TypeScript, 14 Vue, plusz a CSS/JS/CJS. Nem soralapú kereséssel: a Ruby-t a Ripper tokenizálta, a JS/TS/CSS-t állapotgép járta végig, hogy az URL-ekben, reguláris kifejezésekben és heredocokban álló // és # jelek helyükön maradjanak. Három komment maradt, mert nélkülük nem indul a kód: az entrypoint.sh shebangja, a vite-env.d.ts hármas perjeles referenciája, és a sanitize teszt @vitest-environment direktívája (ez utóbbi a magyarázó része nélkül). Egy helyen kódot is kellett írni: a CommandBlock másolás-hibaágán a komment volt a catch egyetlen tartalma, és üres blokkot az eslint nem enged — a copied jelző visszaállítása került a helyére. A yaml, Dockerfile, Makefile, erb és markdown fájlokat nem érintettem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,6 @@ ActiveAdmin.register Store do
|
||||
|
||||
menu priority: 5, label: "🛒 Stores"
|
||||
|
||||
# Active first and by default: the registry is what clients actually read, so the
|
||||
# useful question on opening this page is "what is being offered right now".
|
||||
scope("Active", default: true) { |scope| scope.active }
|
||||
scope("Inactive") { |scope| scope.where(active: false) }
|
||||
scope :all
|
||||
@@ -21,8 +19,7 @@ ActiveAdmin.register Store do
|
||||
end
|
||||
column :updated_at
|
||||
actions defaults: true do |store|
|
||||
# One click, because this is the thing an admin comes to this page to do — and
|
||||
# the alternative is Edit, a checkbox and Save for a single boolean.
|
||||
|
||||
link_to store.active? ? "Hide" : "List",
|
||||
toggle_admin_store_path(store),
|
||||
method: :put
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
//= link active_admin.css
|
||||
//= link active_admin.js
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//= require active_admin/base
|
||||
|
||||
(function() {
|
||||
var PATH_INPUT_SELECTOR = 'input[id*="release_assets_attributes"][id$="_path"]:not([data-browse-added])';
|
||||
var _pickerTarget = null;
|
||||
@@ -89,7 +87,6 @@
|
||||
|
||||
var html = '';
|
||||
|
||||
// Breadcrumb
|
||||
var parts = dir ? dir.split('/').filter(function(p){return p;}) : [];
|
||||
html += '<div class="fm-breadcrumbs"><a href="#" data-dir="">softwares/</a>';
|
||||
var acc = '';
|
||||
@@ -99,19 +96,16 @@
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
// Upload bar (also a drop zone)
|
||||
html += '<div class="fm-actions fm-dropzone" id="fm-picker-dropzone">';
|
||||
html += '<input type="file" id="fm-picker-file" />';
|
||||
html += '<button class="fm-btn" id="fm-picker-upload-btn">Upload</button>';
|
||||
html += '<span class="fm-drop-hint">or drag & drop files here</span>';
|
||||
html += '</div>';
|
||||
|
||||
// Table
|
||||
html += '<table class="fm-table"><thead><tr>';
|
||||
html += '<th style="width:50px">Type</th><th>Name</th><th style="width:90px">Size</th>';
|
||||
html += '<th style="width:80px"></th></tr></thead><tbody>';
|
||||
|
||||
// Parent dir
|
||||
if (dir) {
|
||||
var parentDir = dir.indexOf('/') >= 0 ? dir.substring(0, dir.lastIndexOf('/')) : '';
|
||||
html += '<tr><td class="fm-type">DIR</td>';
|
||||
@@ -139,10 +133,8 @@
|
||||
html += '</tbody></table>';
|
||||
body.innerHTML = html;
|
||||
|
||||
// Event delegation
|
||||
body.addEventListener('click', handlePickerClick);
|
||||
|
||||
// Drag & drop
|
||||
var dropzone = document.getElementById('fm-picker-dropzone');
|
||||
if (dropzone) {
|
||||
['dragenter', 'dragover'].forEach(function(evt) {
|
||||
@@ -162,21 +154,18 @@
|
||||
function handlePickerClick(e) {
|
||||
var t = e.target;
|
||||
|
||||
// Navigate directory
|
||||
if (t.tagName === 'A' && t.hasAttribute('data-dir')) {
|
||||
e.preventDefault();
|
||||
loadDir(t.getAttribute('data-dir'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Select file/folder
|
||||
if (t.hasAttribute('data-select')) {
|
||||
e.preventDefault();
|
||||
selectPath(t.getAttribute('data-select'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload button
|
||||
if (t.id === 'fm-picker-upload-btn') {
|
||||
e.preventDefault();
|
||||
var fileInput = document.getElementById('fm-picker-file');
|
||||
@@ -233,7 +222,6 @@
|
||||
return (bytes / 1048576).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
// Live image preview for software_images image_id selects
|
||||
function addImagePreviewListeners() {
|
||||
var selects = document.querySelectorAll('select[id*="image_id"]:not([data-preview-added])');
|
||||
selects.forEach(function(sel) {
|
||||
@@ -241,7 +229,6 @@
|
||||
sel.addEventListener('change', function() {
|
||||
var hint = sel.closest('li').querySelector('.inline-hints');
|
||||
if (!hint) {
|
||||
// Create hint element if it doesn't exist
|
||||
hint = document.createElement('p');
|
||||
hint.className = 'inline-hints';
|
||||
sel.closest('li').appendChild(hint);
|
||||
@@ -255,7 +242,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Standalone Files page drag & drop
|
||||
function initPageDropzone() {
|
||||
var dz = document.getElementById('fm-page-dropzone');
|
||||
if (!dz) return;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@import "active_admin/base";
|
||||
@import "activeadmin_blaze_theme/theme";
|
||||
|
||||
// Force utility navigation visible (blaze theme may hide it)
|
||||
#utility_nav { display: flex !important; visibility: visible !important; }
|
||||
#utility_nav #site_link a {
|
||||
color: #fff !important;
|
||||
@@ -11,7 +10,6 @@
|
||||
&:hover { text-decoration: underline; }
|
||||
}
|
||||
|
||||
// File Manager
|
||||
.fm-breadcrumbs {
|
||||
background: #f5f5f5; padding: 10px 14px; border-radius: 4px; margin-bottom: 16px; font-family: monospace; font-size: 14px;
|
||||
a { color: #5a6268; text-decoration: none; &:hover { text-decoration: underline; } }
|
||||
@@ -37,7 +35,6 @@
|
||||
.fm-icon-danger:hover { filter: brightness(1.3); }
|
||||
.fm-danger { color: #dc3545; }
|
||||
|
||||
// Emoji action buttons in AA index tables
|
||||
.table_actions {
|
||||
a.view_link, a.edit_link, a.delete_link {
|
||||
font-size: 0 !important;
|
||||
@@ -61,14 +58,12 @@
|
||||
.fm-dropzone { transition: background 0.15s, border-color 0.15s; border: 2px dashed transparent; }
|
||||
.fm-dropzone.fm-dragover { background: #e8f4fd !important; border-color: #4a90d9 !important; }
|
||||
|
||||
// Browse button in release forms
|
||||
.fm-browse-btn {
|
||||
display: inline-block; margin-left: 8px; padding: 4px 10px; background: #6c757d; color: #fff !important; border-radius: 3px; font-size: 11px; text-decoration: none !important; vertical-align: middle;
|
||||
&:hover { background: #5a6268; color: #fff !important; }
|
||||
&:visited { color: #fff !important; }
|
||||
}
|
||||
|
||||
// Inline file picker modal
|
||||
.fm-modal-overlay {
|
||||
position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class Api::WikiController < ApiController
|
||||
end
|
||||
property :error, String, desc: "Error message (null on success)"
|
||||
end
|
||||
# GET /api/wiki/pages?tag=blog|howto[&limit=30][&body=1]
|
||||
|
||||
def index
|
||||
render json: WikiService.new.index(
|
||||
tag: params[:tag],
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
# A store a client can install from: a name and a WarpEngine catalog.
|
||||
#
|
||||
# That is the whole record, and deliberately so. A client takes identity from it — the
|
||||
# name, the catalog, and a slug derived from the catalog host — and everything else from
|
||||
# the store engine it carries. How a store behaves is fixed per installed client, which
|
||||
# knows its own machine; a copy of it here would be a second authority over decisions
|
||||
# the client has already made, including which directories it may delete from.
|
||||
#
|
||||
# This is deliberately not part of WarpEngine. The engine serves one catalog and has no
|
||||
# business knowing which stores exist for it; the registry is a property of this site,
|
||||
# which is what the graphical client asks.
|
||||
class Store < ApplicationRecord
|
||||
URL = %r{\Ahttps?://\S+\z}
|
||||
|
||||
@@ -17,10 +6,6 @@ class Store < ApplicationRecord
|
||||
|
||||
default_scope { where(deleted_at: nil) }
|
||||
|
||||
# Two different "not listed". Soft deletion is "this store is gone"; `active` is
|
||||
# "not right now" — a catalog still being set up, or one pulled from the picker for
|
||||
# a while. The client cannot tell the difference and should not have to: it lists
|
||||
# whatever /api/stores hands it.
|
||||
scope :active, -> { where(active: true) }
|
||||
scope :ordered, -> { order(:name) }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ class StoreSerializer < Blueprinter::Base
|
||||
include WarpEngine::TimestampFields
|
||||
|
||||
field :name
|
||||
# camelCase, as the catalog's own payloads use — one convention for a client
|
||||
# that reads both.
|
||||
|
||||
field(:catalogUrl) { |store| store.catalog_url }
|
||||
end
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
class StoreService
|
||||
# Only the active ones. An inactive store is simply absent from the registry — the
|
||||
# client has no state for "there but switched off", and inventing one would mean
|
||||
# every client release having an opinion about it.
|
||||
|
||||
def index
|
||||
StoreSerializer.render_as_hash(Store.active.ordered)
|
||||
end
|
||||
|
||||
@@ -1,23 +1,6 @@
|
||||
require "net/http"
|
||||
require "json"
|
||||
|
||||
# Fetches wiki pages (blog, howto, …) from the Grav backend, filtered by tag.
|
||||
#
|
||||
# Grav exposes: GET {WIKI_GRAV_URL}/custom/pages.json?tag=<tag>[&limit=<n>][&body=1]
|
||||
# and returns:
|
||||
# {
|
||||
# "tag": "howto", "count": 24,
|
||||
# "pages": [
|
||||
# { "id", "path", "title", "description",
|
||||
# "createdAt", "updatedAt", "locale", "route", "tags",
|
||||
# # with body=1 also:
|
||||
# "render" (rendered HTML),
|
||||
# "content" (raw markdown) }
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# This replaces the frontend's direct WikiJS GraphQL calls with a simple GET,
|
||||
# and points at Grav (which is taking over from wiki.teletypegames.org).
|
||||
class WikiService
|
||||
GRAV_URL = ENV.fetch("WIKI_GRAV_URL", "http://localhost:8080").freeze
|
||||
|
||||
@@ -44,6 +27,5 @@ class WikiService
|
||||
{ "tag" => tag, "count" => 0, "pages" => [], "error" => e.message }
|
||||
end
|
||||
|
||||
# Az RSS feedek ezen a néven hívják.
|
||||
alias_method :pages, :index
|
||||
end
|
||||
|
||||
@@ -22,8 +22,6 @@ module Api
|
||||
config.hosts << ENV["WEBAPP_TECHNICAL_DOMAIN"] if ENV["WEBAPP_TECHNICAL_DOMAIN"].present?
|
||||
config.hosts << "teletypegames.org"
|
||||
|
||||
# Traefik terminates SSL so Rails sees HTTP internally; disable the origin
|
||||
# header check (CSRF token itself is still validated)
|
||||
config.action_controller.forgery_protection_origin_check = false
|
||||
|
||||
config.autoload_lib(ignore: %w[assets tasks])
|
||||
|
||||
@@ -10,6 +10,5 @@ Rails.application.configure do
|
||||
|
||||
config.log_level = :warn
|
||||
|
||||
# Rack::Test example.org hostját ne blokkolja a host authorization
|
||||
config.hosts.clear
|
||||
end
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
# Set a JS-readable cookie when an admin signs in/out.
|
||||
# Devise's remember_admin_user_token cookie is HttpOnly,
|
||||
# so the frontend cannot read it. This non-HttpOnly cookie
|
||||
# lets the frontend show/hide the Admin menu link.
|
||||
|
||||
Rails.application.config.to_prepare do
|
||||
ApplicationController.class_eval do
|
||||
after_action :sync_admin_cookie
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
# WarpEngine host configuration. to_prepare: re-runs after reloads, hence
|
||||
# assignment (not <<) to stay idempotent.
|
||||
Rails.application.config.to_prepare do
|
||||
WarpEngine.configure do |c|
|
||||
# Owner of the /build/* DB tokens. Switching to :database mode
|
||||
# (c.application_token_source = :database) must wait until CI uses DB
|
||||
# tokens — the flip invalidates UPDATE_SECRET immediately.
|
||||
|
||||
c.application_token_source = :database
|
||||
c.application_token_owner_class = "AdminUser"
|
||||
|
||||
# Woodpecker configuration extension (/build/config): the served platforms
|
||||
# and their builder images. An image bump is one line here, rolled out to
|
||||
# every repo by the deploy.
|
||||
c.ci_platforms = {
|
||||
"godot" => { builder: "git.teletypegames.org/build/godot-builder:4.7.1" },
|
||||
"phaser" => { builder: "git.teletypegames.org/build/phaser-builder:latest" },
|
||||
@@ -20,16 +13,13 @@ Rails.application.config.to_prepare do
|
||||
"ebitengine" => { builder: "git.teletypegames.org/build/ebitengine-builder:latest" },
|
||||
"tic80" => { builder: "git.teletypegames.org/build/tic80pro:latest" }
|
||||
}
|
||||
# Explicit URL: request.base_url would yield http:// behind the host nginx
|
||||
# (no X-Forwarded-Proto reaches Rails), and the resulting 301 makes the
|
||||
# pipeline's curl steps silently no-op.
|
||||
|
||||
c.ci_update_server = "https://teletypegames.org"
|
||||
c.ci_extension_public_key_url = "https://ci.teletypegames.org/api/signature/public-key"
|
||||
|
||||
# Woodpecker CI management (repo sync, secret provisioning, pipeline control)
|
||||
c.woodpecker_url = ENV["WOODPECKER_URL"] # e.g. "https://ci.teletypegames.org"
|
||||
c.woodpecker_api_token = ENV["WOODPECKER_API_TOKEN"] # Woodpecker PAT with admin access
|
||||
c.woodpecker_repo_owner = ENV["WOODPECKER_REPO_OWNER"] # forge org (e.g. "games")
|
||||
c.woodpecker_url = ENV["WOODPECKER_URL"]
|
||||
c.woodpecker_api_token = ENV["WOODPECKER_API_TOKEN"]
|
||||
c.woodpecker_repo_owner = ENV["WOODPECKER_REPO_OWNER"]
|
||||
|
||||
c.image_owners = [
|
||||
{
|
||||
|
||||
@@ -14,8 +14,5 @@ Rails.application.routes.draw do
|
||||
get "rss/howtos", to: "rss#howtos"
|
||||
end
|
||||
|
||||
# Utolsó sor: a host route-jai nyernek, a katalógus-útvonalakat
|
||||
# (/api/software*, /api/builds*, /api/image, /api/download, /build/*, /file/*)
|
||||
# az engine adja.
|
||||
mount WarpEngine::Engine => "/"
|
||||
end
|
||||
|
||||
@@ -9,7 +9,6 @@ class AddCascadeToSoftwareForeignKeys < ActiveRecord::Migration[8.1]
|
||||
remove_foreign_key :software_images, :softwares
|
||||
add_foreign_key :software_images, :softwares, on_delete: :cascade
|
||||
|
||||
# Keep download stats when a release is removed.
|
||||
remove_foreign_key :downloads, name: "fk_downloads_release"
|
||||
add_foreign_key :downloads, :releases, name: "fk_downloads_release", on_delete: :nullify
|
||||
end
|
||||
|
||||
@@ -20,8 +20,6 @@ class CreateReleaseAssetsAndBackfill < ActiveRecord::Migration[8.1]
|
||||
add_index :release_assets, :path, name: "idx_release_assets_path"
|
||||
add_foreign_key :release_assets, :releases, name: "fk_releases_release_assets", on_delete: :cascade
|
||||
|
||||
# A soft-deletelt release-ek asset sorai a release deleted_at-jét öröklik,
|
||||
# így az unscoped alapú admin/updater logika konzisztens marad.
|
||||
COLUMN_KINDS.each do |column, kind|
|
||||
execute <<~SQL.squish
|
||||
INSERT INTO release_assets (release_id, kind, path, deleted_at, created_at, updated_at)
|
||||
|
||||
@@ -17,7 +17,6 @@ class DropLegacyPathColumnsFromReleases < ActiveRecord::Migration[8.1]
|
||||
add_column :releases, column, :string
|
||||
end
|
||||
|
||||
# rollbacknél az adat a release_assets-ből áll vissza, nem veszik el
|
||||
COLUMN_KINDS.each do |column, kind|
|
||||
execute <<~SQL.squish
|
||||
UPDATE releases r
|
||||
|
||||
@@ -3,9 +3,7 @@ class CreateStores < ActiveRecord::Migration[8.1]
|
||||
create_table :stores, id: { type: :bigint, unsigned: true },
|
||||
charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci",
|
||||
if_not_exists: true do |t|
|
||||
# A store is a WarpEngine catalog plus the repository that configures a
|
||||
# client for it. The registry lives here rather than in the engine: the
|
||||
# engine serves one catalog and knows nothing about who ships stores for it.
|
||||
|
||||
t.string :name, null: false
|
||||
t.string :catalog_url, null: false
|
||||
t.string :store_repository_url, null: false
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
class AllowStoresWithoutARepository < ActiveRecord::Migration[8.1]
|
||||
# A store repository is now optional. The client only ever needed identity from
|
||||
# it — a name, a catalog and a slug — and the engine's own defaults cover
|
||||
# everything else, so a record with a catalog URL is a complete store. A
|
||||
# repository is still honoured when there is one: it stays the authority on how
|
||||
# that store behaves.
|
||||
|
||||
def change
|
||||
change_column_null :stores, :store_repository_url, true
|
||||
end
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
class CarryTheStoreConfigInTheRegistry < ActiveRecord::Migration[8.1]
|
||||
# The store's own configuration moves into this record.
|
||||
#
|
||||
# It used to live as a `config.json` in a repository the 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*; carrying how it behaves
|
||||
# in the same record makes this the one source of truth, and lets a store exist with
|
||||
# no repository at all — which is the ordinary case now that the store engine ships
|
||||
# inside the client.
|
||||
#
|
||||
# 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.
|
||||
|
||||
def change
|
||||
add_column :stores, :config, :json
|
||||
end
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
class AStoreIsANameAndACatalog < ActiveRecord::Migration[8.1]
|
||||
# Both extras go. A store record is a name and a catalog, and nothing else.
|
||||
#
|
||||
# `config` was added earlier today on the idea that the registry should say how each
|
||||
# store behaves. It should not: the configuration is fixed per installed client — the
|
||||
# client carries it and knows its own machine — so a copy on the server was a second
|
||||
# authority over decisions the client had already made correctly, including where it
|
||||
# may delete. Keeping two stores on one machine apart is a subfolder, which the client
|
||||
# derives itself.
|
||||
#
|
||||
# `store_repository_url` goes for the same reason it stopped being read: the store
|
||||
# engines it pointed at do not exist any more, and a URL nobody follows is a URL that
|
||||
# goes stale.
|
||||
|
||||
def change
|
||||
remove_column :stores, :config, :json
|
||||
remove_column :stores, :store_repository_url, :string
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
class AddActiveToStores < ActiveRecord::Migration[8.1]
|
||||
# A store that exists but should not be offered yet.
|
||||
#
|
||||
# Soft deletion already covered "this store is gone"; what was missing is "not yet" —
|
||||
# a catalog being set up, or one taken out of the picker for a while without losing
|
||||
# the row and its history. The client has no notion of either: it lists whatever
|
||||
# /api/stores hands it, so the filtering has to happen here.
|
||||
#
|
||||
# Default true, so every store that exists today keeps being listed. A migration that
|
||||
# silently emptied the registry would be a client with nothing to install from.
|
||||
#
|
||||
# No index: this table holds a handful of rows and is read once per client on first
|
||||
# run. An index on a two-valued column would be ceremony.
|
||||
|
||||
def change
|
||||
add_column :stores, :active, :boolean, null: false, default: true
|
||||
end
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.1].define(version: 2026_08_19_121824) do
|
||||
create_table "admin_users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.datetime "created_at", null: false
|
||||
|
||||
@@ -16,9 +16,6 @@ end
|
||||
end
|
||||
end
|
||||
|
||||
# The store registry the graphical desktop client reads. Our own catalog is the
|
||||
# first record; anyone running this site would add their own the same way, from
|
||||
# the admin panel or here.
|
||||
Store.find_or_create_by!(name: "Teletype Games") do |store|
|
||||
store.catalog_url = ENV.fetch("STORE_CATALOG_URL", "https://teletypegames.org")
|
||||
end
|
||||
|
||||
@@ -19,8 +19,7 @@ RSpec.describe Api::StoresController, type: :request do
|
||||
get "/api/stores"
|
||||
|
||||
store = JSON.parse(response.body).first
|
||||
# Exactly two fields: a name and a catalog are the whole record, and a client
|
||||
# that starts reading a third would be reading something this site no longer says.
|
||||
|
||||
expect(store.keys).to contain_exactly("name", "catalogUrl")
|
||||
expect(store["catalogUrl"]).to eq("https://teletypegames.org")
|
||||
end
|
||||
@@ -33,9 +32,6 @@ RSpec.describe Api::StoresController, type: :request do
|
||||
expect(JSON.parse(response.body)).to be_empty
|
||||
end
|
||||
|
||||
# Two different "not listed", and a client can tell neither apart from the store
|
||||
# never having existed — which is the point. It has no state for "there but
|
||||
# switched off", so an inactive store is simply absent.
|
||||
it "leaves out inactive stores" do
|
||||
create(:store, name: "Listed")
|
||||
create(:store, name: "Not yet", catalog_url: "https://soon.example", active: false)
|
||||
|
||||
@@ -4,6 +4,6 @@ RSpec.describe Member, type: :model do
|
||||
subject { build(:member) }
|
||||
|
||||
it { should validate_presence_of(:nick) }
|
||||
# MySQL utf8mb4_0900_ai_ci collation: az egyediség DB-szinten case-insensitive
|
||||
|
||||
it { should validate_uniqueness_of(:nick).case_insensitive }
|
||||
end
|
||||
|
||||
@@ -11,7 +11,6 @@ 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
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
require "rails_helper"
|
||||
require "warden/test/helpers"
|
||||
|
||||
# The pipeline admin form could not save at all: the resource never declared
|
||||
# `permit_params`, so ActiveAdmin handed unpermitted attributes to the model and Rails
|
||||
# raised ForbiddenAttributesError on every edit. No model spec could have caught that —
|
||||
# the fault was one layer up — so the check belongs here, where the host's ActiveAdmin
|
||||
# instance actually runs.
|
||||
RSpec.describe "Admin pipelines", type: :request do
|
||||
include Warden::Test::Helpers
|
||||
|
||||
@@ -27,9 +22,6 @@ RSpec.describe "Admin pipelines", type: :request do
|
||||
|
||||
after { Warden.test_reset! }
|
||||
|
||||
# This app keeps forgery protection on in the test environment, and a request spec has
|
||||
# no rendered form to take a token from. The token is not what is under test here, so it
|
||||
# is switched off for the duration and put back afterwards.
|
||||
around do |example|
|
||||
protection = ActionController::Base.allow_forgery_protection
|
||||
ActionController::Base.allow_forgery_protection = false
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
require "rails_helper"
|
||||
require "warden/test/helpers"
|
||||
|
||||
# The admin is where the flag is actually used, and none of it is reachable from a model
|
||||
# spec: the scopes, the toggle action and the batch actions are ActiveAdmin plumbing that
|
||||
# only exists once the host's admin is running. The pipelines resource shipped with a
|
||||
# missing `permit_params` and every edit raised — same layer, same lesson.
|
||||
RSpec.describe "Admin stores", type: :request do
|
||||
include Warden::Test::Helpers
|
||||
|
||||
@@ -19,8 +15,6 @@ RSpec.describe "Admin stores", type: :request do
|
||||
|
||||
after { Warden.test_reset! }
|
||||
|
||||
# Same reasoning as the pipelines spec: a request spec has no rendered form to take a
|
||||
# CSRF token from, and the token is not what is under test.
|
||||
around do |example|
|
||||
protection = ActionController::Base.allow_forgery_protection
|
||||
ActionController::Base.allow_forgery_protection = false
|
||||
@@ -48,8 +42,6 @@ RSpec.describe "Admin stores", type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
# The one this page exists for: the form saves the flag rather than dropping it as an
|
||||
# unpermitted attribute, which fails silently — the page redirects and nothing changes.
|
||||
it "saves the flag from the form" do
|
||||
put "/admin/stores/#{listed.id}", params: { store: { active: "0" } }
|
||||
|
||||
@@ -79,8 +71,6 @@ RSpec.describe "Admin stores", type: :request do
|
||||
expect(hidden.reload).not_to be_active
|
||||
end
|
||||
|
||||
# The registry is the whole point of the flag, so the two are checked together: a
|
||||
# change made here has to be what the client sees.
|
||||
it "is what /api/stores answers with" do
|
||||
put "/admin/stores/#{listed.id}/toggle"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user