| @@ -0,0 +1 @@ | |||||
| --require spec_helper | |||||
| @@ -65,3 +65,5 @@ gem 'dotenv-rails' | |||||
| gem 'whenever', require: false | gem 'whenever', require: false | ||||
| gem 'discard' | gem 'discard' | ||||
| gem "rspec-rails", "~> 8.0", :groups => [:development, :test] | |||||
| @@ -298,6 +298,23 @@ GEM | |||||
| io-console (~> 0.5) | io-console (~> 0.5) | ||||
| rexml (3.4.1) | rexml (3.4.1) | ||||
| rouge (3.30.0) | rouge (3.30.0) | ||||
| rspec-core (3.13.6) | |||||
| rspec-support (~> 3.13.0) | |||||
| rspec-expectations (3.13.5) | |||||
| diff-lcs (>= 1.2.0, < 2.0) | |||||
| rspec-support (~> 3.13.0) | |||||
| rspec-mocks (3.13.7) | |||||
| diff-lcs (>= 1.2.0, < 2.0) | |||||
| rspec-support (~> 3.13.0) | |||||
| rspec-rails (8.0.2) | |||||
| actionpack (>= 7.2) | |||||
| activesupport (>= 7.2) | |||||
| railties (>= 7.2) | |||||
| rspec-core (~> 3.13) | |||||
| rspec-expectations (~> 3.13) | |||||
| rspec-mocks (~> 3.13) | |||||
| rspec-support (~> 3.13) | |||||
| rspec-support (3.13.6) | |||||
| rss (0.3.1) | rss (0.3.1) | ||||
| rexml | rexml | ||||
| rubocop (1.75.6) | rubocop (1.75.6) | ||||
| @@ -433,6 +450,7 @@ DEPENDENCIES | |||||
| puma (>= 5.0) | puma (>= 5.0) | ||||
| rack-cors | rack-cors | ||||
| rails (~> 8.0.2) | rails (~> 8.0.2) | ||||
| rspec-rails (~> 8.0) | |||||
| rubocop-rails-omakase | rubocop-rails-omakase | ||||
| sprockets-rails | sprockets-rails | ||||
| sqlite3 (>= 2.1) | sqlite3 (>= 2.1) | ||||
| @@ -1,20 +0,0 @@ | |||||
| class IpAddressesController < ApplicationController | |||||
| def index | |||||
| @ip_addresses = IpAddress.all | |||||
| render json: @ip_addresses | |||||
| end | |||||
| def show | |||||
| render json: @ip_address | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -1,16 +0,0 @@ | |||||
| class NicoTagRelationController < ApplicationController | |||||
| def index | |||||
| end | |||||
| def show | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -1,16 +0,0 @@ | |||||
| class PostTagsController < ApplicationController | |||||
| def index | |||||
| end | |||||
| def show | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -81,7 +81,7 @@ class PostsController < ApplicationController | |||||
| # TODO: URL が正規のものがチェック,不正ならエラー | # TODO: URL が正規のものがチェック,不正ならエラー | ||||
| # TODO: URL は必須にする(タイトルは省略可). | # TODO: URL は必須にする(タイトルは省略可). | ||||
| # TODO: サイトに応じて thumbnail_base 設定 | # TODO: サイトに応じて thumbnail_base 設定 | ||||
| title = params[:title] | |||||
| title = params[:title].presence | |||||
| url = params[:url] | url = params[:url] | ||||
| thumbnail = params[:thumbnail] | thumbnail = params[:thumbnail] | ||||
| tag_names = params[:tags].to_s.split(' ') | tag_names = params[:tags].to_s.split(' ') | ||||
| @@ -122,7 +122,7 @@ class PostsController < ApplicationController | |||||
| return head :unauthorized unless current_user | return head :unauthorized unless current_user | ||||
| return head :forbidden unless current_user.member? | return head :forbidden unless current_user.member? | ||||
| title = params[:title] | |||||
| title = params[:title].presence | |||||
| tag_names = params[:tags].to_s.split(' ') | tag_names = params[:tags].to_s.split(' ') | ||||
| original_created_from = params[:original_created_from] | original_created_from = params[:original_created_from] | ||||
| original_created_before = params[:original_created_before] | original_created_before = params[:original_created_before] | ||||
| @@ -141,10 +141,6 @@ class PostsController < ApplicationController | |||||
| end | end | ||||
| end | end | ||||
| # DELETE /posts/1 | |||||
| def destroy | |||||
| end | |||||
| def changes | def changes | ||||
| id = params[:id] | id = params[:id] | ||||
| page = (params[:page].presence || 1).to_i | page = (params[:page].presence || 1).to_i | ||||
| @@ -1,16 +0,0 @@ | |||||
| class SettingsController < ApplicationController | |||||
| def index | |||||
| end | |||||
| def show | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -1,16 +0,0 @@ | |||||
| class TagAliasesController < ApplicationController | |||||
| def index | |||||
| end | |||||
| def show | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -35,9 +35,6 @@ class TagsController < ApplicationController | |||||
| end | end | ||||
| end | end | ||||
| def create | |||||
| end | |||||
| def update | def update | ||||
| return head :unauthorized unless current_user | return head :unauthorized unless current_user | ||||
| return head :forbidden unless current_user.member? | return head :forbidden unless current_user.member? | ||||
| @@ -51,7 +48,4 @@ class TagsController < ApplicationController | |||||
| render json: tag | render json: tag | ||||
| end | end | ||||
| def destroy | |||||
| end | |||||
| end | end | ||||
| @@ -1,16 +0,0 @@ | |||||
| class UserIpsController < ApplicationController | |||||
| def index | |||||
| end | |||||
| def show | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -1,16 +0,0 @@ | |||||
| class UserPostViewsController < ApplicationController | |||||
| def index | |||||
| end | |||||
| def show | |||||
| end | |||||
| def create | |||||
| end | |||||
| def update | |||||
| end | |||||
| def destroy | |||||
| end | |||||
| end | |||||
| @@ -6,7 +6,7 @@ Rails.application.routes.draw do | |||||
| delete ':child_id', action: :destroy | delete ':child_id', action: :destroy | ||||
| end | end | ||||
| resources :tags do | |||||
| resources :tags, only: [:index, :show, :update] do | |||||
| collection do | collection do | ||||
| get :autocomplete | get :autocomplete | ||||
| get 'name/:name', action: :show_by_name | get 'name/:name', action: :show_by_name | ||||
| @@ -35,7 +35,7 @@ Rails.application.routes.draw do | |||||
| end | end | ||||
| end | end | ||||
| resources :posts do | |||||
| resources :posts, only: [:index, :show, :create, :update] do | |||||
| collection do | collection do | ||||
| get :random | get :random | ||||
| get :changes | get :changes | ||||
| @@ -54,12 +54,4 @@ Rails.application.routes.draw do | |||||
| post 'code/renew', action: :renew | post 'code/renew', action: :renew | ||||
| end | end | ||||
| end | end | ||||
| resources :ip_addresses | |||||
| resources :nico_tag_relations | |||||
| resources :post_tags | |||||
| resources :settings | |||||
| resources :tag_aliases | |||||
| resources :user_ips | |||||
| resources :user_post_views | |||||
| end | end | ||||
| @@ -0,0 +1,28 @@ | |||||
| class MakeTitleNullableInPosts < ActiveRecord::Migration[7.0] | |||||
| def up | |||||
| change_column_null :posts, :title, true | |||||
| execute <<~SQL | |||||
| UPDATE | |||||
| posts | |||||
| SET | |||||
| title = NULL | |||||
| WHERE | |||||
| title = '' | |||||
| SQL | |||||
| end | |||||
| def down | |||||
| execute <<~SQL | |||||
| UPDATE | |||||
| posts | |||||
| SET | |||||
| title = '' | |||||
| WHERE | |||||
| title IS NULL | |||||
| SQL | |||||
| change_column_null :posts, :title, false | |||||
| end | |||||
| end | |||||
| @@ -10,7 +10,7 @@ | |||||
| # | # | ||||
| # It's strongly recommended that you check this file into your version control system. | # It's strongly recommended that you check this file into your version control system. | ||||
| ActiveRecord::Schema[8.0].define(version: 2025_12_30_143400) do | |||||
| ActiveRecord::Schema[8.0].define(version: 2026_01_07_034300) do | |||||
| create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | ||||
| t.string "name", null: false | t.string "name", null: false | ||||
| t.string "record_type", null: false | t.string "record_type", null: false | ||||
| @@ -84,7 +84,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_30_143400) do | |||||
| end | end | ||||
| create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | create_table "posts", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| | ||||
| t.string "title", null: false | |||||
| t.string "title" | |||||
| t.string "url", limit: 2000, null: false | t.string "url", limit: 2000, null: false | ||||
| t.string "thumbnail_base", limit: 2000 | t.string "thumbnail_base", limit: 2000 | ||||
| t.bigint "parent_id" | t.bigint "parent_id" | ||||
| @@ -0,0 +1,77 @@ | |||||
| # This file is copied to spec/ when you run 'rails generate rspec:install' | |||||
| require 'spec_helper' | |||||
| ENV['RAILS_ENV'] ||= 'test' | |||||
| require_relative '../config/environment' | |||||
| # Prevent database truncation if the environment is production | |||||
| abort("The Rails environment is running in production mode!") if Rails.env.production? | |||||
| # Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file | |||||
| # that will avoid rails generators crashing because migrations haven't been run yet | |||||
| # return unless Rails.env.test? | |||||
| require 'rspec/rails' | |||||
| # Add additional requires below this line. Rails is not loaded until this point! | |||||
| # Requires supporting ruby files with custom matchers and macros, etc, in | |||||
| # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are | |||||
| # run as spec files by default. This means that files in spec/support that end | |||||
| # in _spec.rb will both be required and run as specs, causing the specs to be | |||||
| # run twice. It is recommended that you do not name files matching this glob to | |||||
| # end with _spec.rb. You can configure this pattern with the --pattern | |||||
| # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. | |||||
| # | |||||
| # The following line is provided for convenience purposes. It has the downside | |||||
| # of increasing the boot-up time by auto-requiring all files in the support | |||||
| # directory. Alternatively, in the individual `*_spec.rb` files, manually | |||||
| # require only the support files necessary. | |||||
| # | |||||
| # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } | |||||
| # Ensures that the test database schema matches the current schema file. | |||||
| # If there are pending migrations it will invoke `db:test:prepare` to | |||||
| # recreate the test database by loading the schema. | |||||
| # If you are not using ActiveRecord, you can remove these lines. | |||||
| begin | |||||
| ActiveRecord::Migration.maintain_test_schema! | |||||
| rescue ActiveRecord::PendingMigrationError => e | |||||
| abort e.to_s.strip | |||||
| end | |||||
| Dir[Rails.root.join('spec/support/**/*.rb')].each do |f| | |||||
| require f | |||||
| end | |||||
| RSpec.configure do |config| | |||||
| config.include TestRecords | |||||
| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures | |||||
| config.fixture_paths = [ | |||||
| Rails.root.join('spec/fixtures') | |||||
| ] | |||||
| # If you're not using ActiveRecord, or you'd prefer not to run each of your | |||||
| # examples within a transaction, remove the following line or assign false | |||||
| # instead of true. | |||||
| config.use_transactional_fixtures = true | |||||
| # You can uncomment this line to turn off ActiveRecord support entirely. | |||||
| # config.use_active_record = false | |||||
| # RSpec Rails uses metadata to mix in different behaviours to your tests, | |||||
| # for example enabling you to call `get` and `post` in request specs. e.g.: | |||||
| # | |||||
| # RSpec.describe UsersController, type: :request do | |||||
| # # ... | |||||
| # end | |||||
| # | |||||
| # The different available types are documented in the features, such as in | |||||
| # https://rspec.info/features/8-0/rspec-rails | |||||
| # | |||||
| # You can also this infer these behaviours automatically by location, e.g. | |||||
| # /spec/models would pull in the same behaviour as `type: :model` but this | |||||
| # behaviour is considered legacy and will be removed in a future version. | |||||
| # | |||||
| # To enable this behaviour uncomment the line below. | |||||
| # config.infer_spec_type_from_file_location! | |||||
| # Filter lines from Rails gems in backtraces. | |||||
| config.filter_rails_from_backtrace! | |||||
| # arbitrary gems may also be filtered via: | |||||
| # config.filter_gems_from_backtrace("gem name") | |||||
| end | |||||
| @@ -0,0 +1,30 @@ | |||||
| require 'rails_helper' | |||||
| RSpec.describe 'Posts API', type: :request do | |||||
| describe 'GET /posts' do | |||||
| it 'returns tags with name in JSON' do | |||||
| tn = TagName.create!(name: 'gen:spec_tag') | |||||
| tag = Tag.create!(tag_name: tn, category: 'general') | |||||
| post = Post.create!(title: 'spec post', url: 'https://example.com/spec') | |||||
| PostTag.create!(post: post, tag: tag) | |||||
| get '/posts' | |||||
| expect(response).to have_http_status(:ok) | |||||
| json = JSON.parse(response.body) | |||||
| expect(json).to have_key('posts') | |||||
| expect(json['posts']).to be_a(Array) | |||||
| expect(json['posts']).not_to be_empty | |||||
| tags = json['posts'][0]['tags'] | |||||
| expect(tags).to be_a(Array) | |||||
| expect(tags).not_to be_empty | |||||
| expect(tags[0]).to have_key('name') | |||||
| expect(tags[0]['name']).to eq('gen:spec_tag') | |||||
| end | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,48 @@ | |||||
| require 'cgi' | |||||
| require 'rails_helper' | |||||
| RSpec.describe 'Tags API', type: :request do | |||||
| let!(:tn) { TagName.create!(name: 'spec_tag') } | |||||
| let!(:tag) { Tag.create!(tag_name: tn, category: 'general') } | |||||
| describe 'GET /tags' do | |||||
| it 'returns tags with name' do | |||||
| get '/tags' | |||||
| expect(response).to have_http_status(:ok) | |||||
| json = JSON.parse(response.body) | |||||
| expect(json).to be_a(Array) | |||||
| expect(json).not_to be_empty | |||||
| expect(json[0]).to have_key('name') | |||||
| expect(json.map { |t| t['name'] }).to include('spec_tag') | |||||
| end | |||||
| end | |||||
| describe 'GET /tags/autocomplete' do | |||||
| it 'returns matching tags by q' do | |||||
| get '/tags/autocomplete', params: { q: 'spec' } | |||||
| expect(response).to have_http_status(:ok) | |||||
| json = JSON.parse(response.body) | |||||
| expect(json).to be_a(Array) | |||||
| expect(json.map { |t| t['name'] }).to include('spec_tag') | |||||
| end | |||||
| end | |||||
| describe 'GET /tags/name/:name' do | |||||
| it 'returns tag by name' do | |||||
| get "/tags/name/#{ CGI.escape('spec_tag') }" | |||||
| expect(response).to have_http_status(:ok) | |||||
| json = JSON.parse(response.body) | |||||
| expect(json).to have_key('id') | |||||
| expect(json).to have_key('name') | |||||
| expect(json['id']).to eq(tag.id) | |||||
| expect(json['name']).to eq('spec_tag') | |||||
| end | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,42 @@ | |||||
| require 'cgi' | |||||
| require 'rails_helper' | |||||
| require 'securerandom' | |||||
| RSpec.describe 'Wiki API', type: :request do | |||||
| let!(:user) { create_user_for_wiki! } | |||||
| let!(:tn) { TagName.create!(name: 'spec_wiki_title') } | |||||
| let!(:page) do | |||||
| WikiPage.create!(tag_name: tn, created_user: user, updated_user: user) | |||||
| end | |||||
| describe 'GET /wiki' do | |||||
| it 'returns wiki pages with title' do | |||||
| get '/wiki' | |||||
| expect(response).to have_http_status(:ok) | |||||
| json = JSON.parse(response.body) | |||||
| expect(json).to be_a(Array) | |||||
| expect(json).not_to be_empty | |||||
| expect(json[0]).to have_key('title') | |||||
| expect(json.map { |p| p['title'] }).to include('spec_wiki_title') | |||||
| end | |||||
| end | |||||
| describe 'GET /wiki/title/:title' do | |||||
| it 'returns wiki page by title' do | |||||
| get "/wiki/title/#{CGI.escape('spec_wiki_title')}" | |||||
| expect(response).to have_http_status(:ok) | |||||
| json = JSON.parse(response.body) | |||||
| expect(json).to have_key('id') | |||||
| expect(json).to have_key('title') | |||||
| expect(json['id']).to eq(page.id) | |||||
| expect(json['title']).to eq('spec_wiki_title') | |||||
| end | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,94 @@ | |||||
| # This file was generated by the `rails generate rspec:install` command. Conventionally, all | |||||
| # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. | |||||
| # The generated `.rspec` file contains `--require spec_helper` which will cause | |||||
| # this file to always be loaded, without a need to explicitly require it in any | |||||
| # files. | |||||
| # | |||||
| # Given that it is always loaded, you are encouraged to keep this file as | |||||
| # light-weight as possible. Requiring heavyweight dependencies from this file | |||||
| # will add to the boot time of your test suite on EVERY test run, even for an | |||||
| # individual file that may not need all of that loaded. Instead, consider making | |||||
| # a separate helper file that requires the additional dependencies and performs | |||||
| # the additional setup, and require it from the spec files that actually need | |||||
| # it. | |||||
| # | |||||
| # See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration | |||||
| RSpec.configure do |config| | |||||
| # rspec-expectations config goes here. You can use an alternate | |||||
| # assertion/expectation library such as wrong or the stdlib/minitest | |||||
| # assertions if you prefer. | |||||
| config.expect_with :rspec do |expectations| | |||||
| # This option will default to `true` in RSpec 4. It makes the `description` | |||||
| # and `failure_message` of custom matchers include text for helper methods | |||||
| # defined using `chain`, e.g.: | |||||
| # be_bigger_than(2).and_smaller_than(4).description | |||||
| # # => "be bigger than 2 and smaller than 4" | |||||
| # ...rather than: | |||||
| # # => "be bigger than 2" | |||||
| expectations.include_chain_clauses_in_custom_matcher_descriptions = true | |||||
| end | |||||
| # rspec-mocks config goes here. You can use an alternate test double | |||||
| # library (such as bogus or mocha) by changing the `mock_with` option here. | |||||
| config.mock_with :rspec do |mocks| | |||||
| # Prevents you from mocking or stubbing a method that does not exist on | |||||
| # a real object. This is generally recommended, and will default to | |||||
| # `true` in RSpec 4. | |||||
| mocks.verify_partial_doubles = true | |||||
| end | |||||
| # This option will default to `:apply_to_host_groups` in RSpec 4 (and will | |||||
| # have no way to turn it off -- the option exists only for backwards | |||||
| # compatibility in RSpec 3). It causes shared context metadata to be | |||||
| # inherited by the metadata hash of host groups and examples, rather than | |||||
| # triggering implicit auto-inclusion in groups with matching metadata. | |||||
| config.shared_context_metadata_behavior = :apply_to_host_groups | |||||
| # The settings below are suggested to provide a good initial experience | |||||
| # with RSpec, but feel free to customize to your heart's content. | |||||
| =begin | |||||
| # This allows you to limit a spec run to individual examples or groups | |||||
| # you care about by tagging them with `:focus` metadata. When nothing | |||||
| # is tagged with `:focus`, all examples get run. RSpec also provides | |||||
| # aliases for `it`, `describe`, and `context` that include `:focus` | |||||
| # metadata: `fit`, `fdescribe` and `fcontext`, respectively. | |||||
| config.filter_run_when_matching :focus | |||||
| # Allows RSpec to persist some state between runs in order to support | |||||
| # the `--only-failures` and `--next-failure` CLI options. We recommend | |||||
| # you configure your source control system to ignore this file. | |||||
| config.example_status_persistence_file_path = "spec/examples.txt" | |||||
| # Limits the available syntax to the non-monkey patched syntax that is | |||||
| # recommended. For more details, see: | |||||
| # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ | |||||
| config.disable_monkey_patching! | |||||
| # Many RSpec users commonly either run the entire suite or an individual | |||||
| # file, and it's useful to allow more verbose output when running an | |||||
| # individual spec file. | |||||
| if config.files_to_run.one? | |||||
| # Use the documentation formatter for detailed output, | |||||
| # unless a formatter has already been configured | |||||
| # (e.g. via a command-line flag). | |||||
| config.default_formatter = "doc" | |||||
| end | |||||
| # Print the 10 slowest examples and example groups at the | |||||
| # end of the spec run, to help surface which specs are running | |||||
| # particularly slow. | |||||
| config.profile_examples = 10 | |||||
| # Run specs in random order to surface order dependencies. If you find an | |||||
| # order dependency and want to debug it, you can fix the order by providing | |||||
| # the seed, which is printed after each run. | |||||
| # --seed 1234 | |||||
| config.order = :random | |||||
| # Seed global randomization in this process using the `--seed` CLI option. | |||||
| # Setting this allows you to use `--seed` to deterministically reproduce | |||||
| # test failures related to randomization by passing the same `--seed` value | |||||
| # as the one that triggered the failure. | |||||
| Kernel.srand config.seed | |||||
| =end | |||||
| end | |||||
| @@ -0,0 +1,8 @@ | |||||
| module TestRecords | |||||
| def create_member_user! | |||||
| User.create!(name: 'spec user', | |||||
| inheritance_code: SecureRandom.hex(16), | |||||
| role: 'member', | |||||
| banned: false) | |||||
| end | |||||
| end | |||||