[
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: lint\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    name: Linting\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: '4.0'\n    - name: Install dependencies\n      run: bundle install\n    - name: Run tests\n      run: bundle exec rubocop --config=./.rubocop.yml --parallel\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    name: Tests\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    strategy:\n      fail-fast: false\n      matrix:\n        database: [mysql, postgres, sqlite]\n        ruby-version:\n          - 3.2\n          - 3.3\n          - 3.4\n          - 4.0\n    services:\n      mysql:\n        image: mysql:8.0.31\n        env:\n          MYSQL_ALLOW_EMPTY_PASSWORD: \"yes\"\n        ports:\n          - 33060:3306\n        options: --health-cmd \"mysql -h localhost -e \\\"select now()\\\"\" --health-interval 1s --health-timeout 5s --health-retries 30\n      postgres:\n        image: postgres:15.1\n        env:\n          POSTGRES_HOST_AUTH_METHOD: \"trust\"\n        ports:\n          - 55432:5432\n    env:\n      TARGET_DB: ${{ matrix.database }}\n    steps:\n    - uses: actions/checkout@v6\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{ matrix.ruby-version }}\n    - name: Install dependencies\n      run: bundle install\n    - name: Setup db\n      run: |\n        bin/rails db:setup\n    - name: Run tests\n      run: bin/test\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/doc/\n/log/*.log\n/pkg/\n/tmp/\n/test/dummy/db/*.sqlite3\n/test/dummy/db/*.sqlite3-*\n/test/dummy/log/*.log\n/test/dummy/storage/\n/test/dummy/tmp/\nGemfile.lock\n/bench/db/*.sqlite3\n/bench/db/*.sqlite3-*\n/bench/log/*.log\n/bench/storage/\n/bench/tmp/\n/bench/app/assets/builds/*\n!/bench/app/assets/builds/.keep\n/bench/config/master.key\n/bench/storage/*\n!/bench/storage/.keep\n/bench/tmp/storage/*\n!/bench/tmp/storage/\n!/bench/tmp/storage/.keep\n/bench/public/assets\n/bench/.env*\n!/bench/.env*.erb\n/bench/k6\ntest/dummy/solid_cable_test\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "inherit_gem:\n  rubocop-rails-omakase: rubocop.yml\n\nAllCops:\n  NewCops: enable\n  SuggestExtensions: false\n  Exclude:\n    - vendor/**/*\n    - Gemfile.lock\n    - db/*_schema.rb\n    - db/schema.rb\n    - tmp/**/*\n    - bin/**/*\n    - test/dummy/**/*\n    - bench/**/*\n    - test/lib/action_cable/subscription_adapter/solid_cable_test.rb\n    - lib/generators/**/*_schema.rb\n  TargetRubyVersion: 3.3\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\ngit_source(:github) { |repo| \"https://github.com/#{repo}.git\" }\n\n# Specify your gem's dependencies in solid_cable.gemspec.\ngemspec\n\ngem \"puma\"\n\ngem \"pg\"\ngem \"sqlite3\"\ngem \"trilogy\"\n\ngem \"rubocop-rails-omakase\"\n"
  },
  {
    "path": "MIT-LICENSE",
    "content": "Copyright Nick Pezza\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Solid Cable\n\nSolid Cable is a database-backed Action Cable adapter that keeps messages in a table and continuously polls for updates. This makes it possible to drop the common dependency on Redis, if it isn't needed for any other purpose. Despite polling, the performance of Solid Cable is comparable to Redis in most situations. And in all circumstances, it makes it easier to deploy Rails when Redis is no longer a required dependency for Action Cable functionality.\n\n> [!NOTE]\n> Solid Cable is tested to work with MySQL, SQLite, and PostgreSQL.\n>\n> Action Cable already has a [dedicated PostgreSQL adapter](https://guides.rubyonrails.org/action_cable_overview.html#postgresql-adapter),\n> which utilizes the builtin `NOTIFY` command for better performance. However, that\n> adapter has an 8kb limit on its payload. Solid Cable is a great alternative if you find yourself\n> broadcasting large payloads, or prefer not to use the `NOTIFY` command.\n\n## Installation\n\nSolid Cable is configured by default in new Rails 8 applications. But if you're running an earlier version, you can add it manually following these steps:\n\n1. `bundle add solid_cable`\n2. `bin/rails solid_cable:install`\n\nThis will configure Solid Cable as the production cable adapter by overwritting `config/cable.yml` and create `db/cable_schema.rb`.\n\nYou will then have to add the configuration for the cable database in `config/database.yml`. If you're using SQLite, it'll look like this:\n\n```yaml\nproduction:\n  primary:\n    <<: *default\n    database: storage/production.sqlite3\n  cable:\n    <<: *default\n    database: storage/production_cable.sqlite3\n    migrations_paths: db/cable_migrate\n```\n\n...or if you're using MySQL/PostgreSQL/Trilogy:\n\n```yaml\nproduction:\n  primary: &primary_production\n    <<: *default\n    database: app_production\n    username: app\n    password: <%= ENV[\"APP_DATABASE_PASSWORD\"] %>\n  cable:\n    <<: *primary_production\n    database: app_production_cable\n    migrations_paths: db/cable_migrate\n```\n\n> [!NOTE]\n> Calling `bin/rails solid_cable:install` will automatically setup `config/cable.yml`, so no additional configuration is needed there (although you must make sure that you use the `cable` name in `database.yml` for this to match!). But if you want to use Solid Cable in a different environment (like staging or even development), you'll have to manually add that `connects_to` block to the respective environment in the `config/cable.yml` file. And, as always, make sure that the name you're using for the database in `config/cable.yml` matches the name you define in `config/database.yml`.\n\nThen run `db:prepare` in production to ensure the database is created and the schema is loaded.\n\n### Single database configuration\n\nRunning Solid Cable in a separate database is recommended, but it's also possible to use a single database for both the app and Action Cable.\n\n1. Copy the contents of `db/cable_schema.rb` into a normal migration and delete `db/cable_schema.rb`\n2. Remove `connects_to` from `config/cable.yml`\n3. `bin/rails db:migrate`\n\nYou won't have multiple databases, so `database.yml` doesn't need to have primary and cable database.\n\n## Configuration\n\nAll configuration is managed via the `config/cable.yml` file. By default, it'll be configured like this:\n\n```yaml\nproduction:\n  adapter: solid_cable\n  connects_to:\n    database:\n      writing: cable\n  polling_interval: 0.1.seconds\n  message_retention: 1.day\n```\n\nThe options are:\n\n- `connects_to` - set the Active Record database configuration for the Solid Cable models. All options available in Active Record can be used here.\n- `polling_interval` - sets the frequency of the polling interval. (Defaults to\n  0.1.seconds)\n- `message_retention` - sets the retention time for messages kept in the database. Used as the cut-off when trimming is performed. (Defaults to 1.day)\n- `autotrim` - sets wether you want Solid Cable to handle autotrimming messages. (Defaults to true)\n- `silence_polling` - whether to silence Active Record logs emitted when polling (Defaults to true)\n- `use_skip_locked` - whether to use `FOR UPDATE SKIP LOCKED` when performing trimming. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential. (Defaults to true)\n- `trim_batch_size` - the batch size to use when deleting old records (default: `100`)\n- `reconnect_attempts` - Supports a number of connection attempts or an array of\n  durations to wait between attempts. (Defaults to 1 retry attempt)\n\n\n## Trimming\n\nMessages are autotrimmed based upon the `message_retention` setting to determine how long messages are to be kept around. If no `message_retention` is given or parsing fails, it defaults to `1.day`. Messages are trimmed when a messsage is broadcast.\n\nAutotrimming can negatively impact performance slightly depending on your workload because it is potentially doing a delete on broadcast. If\nyou would prefer, you can disable autotrimming by setting `autotrim: false` and you can manually enqueue the job later, `SolidCable::TrimJob.perform_later`, or run it on a recurring interval out of band.\n\n\n## Upgrading\n\nIf you have already installed Solid Cable < 3 and are upgrading to version 3,\nrun `solid_cable:update` to install a new migration.\n\n\n## Benchmarks\n\nInside the `bench` directory there is a minimal Rails app that is used to benchmark.\nYou are welcome to update the config/deploy.yml file to point to your own server\nif you want to deploy the app to your own server and run benchmarks.\n\nTo benchmark we use [k6](https://k6.io). Most of the setup was gotten from this\n[article](https://evilmartians.com/chronicles/real-time-stress-anycable-k6-websockets-and-yabeda).\n1. Install k6\n1. Install xk6-cable by running `xk6 build --with\n   github.com/anycable/xk6-cable`. This will output a custom k6 binary.\n1. Run the load test with `./k6 run loadtest.js`\n    - This script takes a variety of ENV variables:\n        - WS_URL: The url to send websocket connections\n        - MAX: The number of virtual users to hit the server with\n        - TIME: The duration of the load test\n        - MESSAGES_NUM: The number of messages each VU will send to the server\n\n\n#### Results\n\nOur loadtest is run on a Hetzner CCX13, with a MESSAGES_NUM of 5, and a TIME of 90.\n\n##### SQLite\n\nWith a polling interval of 0.1 seconds and autotrimming enabled.\n\n100 VUs\n```\nrtt..................: avg=135.82ms min=50ms     med=138ms    max=357ms    p(90)=174ms    p(95)=195ms\nws_connecting........: avg=205.81ms min=149.35ms med=199.01ms max=509.48ms p(90)=254.04ms p(95)=261.77ms\n```\n250 VUs\n```\nrtt..................: avg=146.24ms min=50ms     med=144ms    max=435ms p(90)=209ms   p(95)=234.04ms\nws_connecting........: avg=222.15ms min=146.47ms med=208.57ms max=1.3s  p(90)=263.6ms p(95)=284.18ms\n```\n500 VUs\n```\nrtt..................: avg=271.79ms min=48ms     med=205ms    max=1.15s p(90)=558ms    p(95)=660ms\nws_connecting........: avg=248.81ms min=145.89ms med=221.89ms max=1.38s p(90)=290.41ms p(95)=322.2ms\n```\n750 VUs\n```\nrtt..................: avg=548.27ms min=51ms     med=438ms    max=5.19s  p(90)=1.18s  p(95)=1.29s\nws_connecting........: avg=266.37ms min=144.06ms med=224.93ms max=2.33s  p(90)=298ms  p(95)=342.87ms\n```\n\nWith trimming disabled\n\n250 VUs\n```\nrtt..................: avg=139.47ms min=48ms     med=142ms    max=807ms p(90)=189ms    p(95)=214ms\nws_connecting........: avg=212.58ms min=146.19ms med=196.25ms max=1.25s p(90)=255.74ms p(95)=272.44ms\n```\n\nWith a polling interval of 0.01 seconds it becomes comparable to Redis\n\n250 VUs\n```\nrtt..................: avg=84.22ms  min=43ms     med=69ms     max=416ms p(90)=137ms    p(95)=150ms\nws_connecting........: avg=219.37ms min=144.71ms med=200.77ms max=2.17s p(90)=265.23ms p(95)=290.83ms\n```\n\n##### Redis\n\nThis instance was hosted on the same machine.\n\n100 VUs\n```\nrtt..................: avg=68.95ms  min=41ms     med=56ms     max=6.23s  p(90)=114ms   p(95)=129ms\nws_connecting........: avg=211.09ms min=153.23ms med=195.69ms max=1.44s  p(90)=258.1ms p(95)=272.23ms\n```\n250 VUs\n```\nrtt..................: avg=69.32ms  min=40ms     med=56ms     max=645ms p(90)=119ms    p(95)=135ms\nws_connecting........: avg=212.95ms min=142.92ms med=196.31ms max=1.25s p(90)=260.25ms p(95)=273.49ms\n```\n500 VUs\n```\nrtt..................: avg=87.5ms   min=40ms     med=67ms     max=839ms p(90)=149ms    p(95)=176ms\nws_connecting........: avg=242.62ms min=142.03ms med=213.76ms max=2.34s p(90)=291.25ms p(95)=324.04ms\n```\n750 VUs\n```\nrtt..................: avg=162.54ms min=39ms  med=123ms    max=2.26s p(90)=343.1ms  p(95)=438ms\nws_connecting........: avg=353.08ms min=143ms med=264.15ms max=2.73s p(90)=541.36ms p(95)=1.15s\n```\n\n\n##### MySQL\n\nWith a polling interval of 0.1 seconds and autotrimming enabled. This instance\nwas also hosted on the same machine.\n\n100 VUs\n```\nrtt..................: avg=136.02ms min=51ms     med=137ms    max=877ms p(90)=168.1ms  p(95)=198ms\nws_connecting........: avg=207.76ms min=151.93ms med=196.74ms max=1.21s p(90)=249.91ms p(95)=260.37ms\n```\n250 VUs\n```\nrtt..................: avg=159.33ms min=51ms    med=149ms    max=559ms p(90)=236ms    p(95)=263ms\nws_connecting........: avg=232.38ms min=151.6ms med=218.09ms max=1.38s p(90)=287.99ms p(95)=324.6ms\n```\n500 VUs\n```\nrtt..................: avg=441.07ms min=51ms     med=312ms    max=2.29s  p(90)=931ms    p(95)=1.07s\nws_connecting........: avg=256.73ms min=152.23ms med=231.02ms max=2.31s  p(90)=305.69ms p(95)=340.83ms\n```\n750 VUs\n```\nrtt..................: avg=822.08ms min=51ms     med=732ms    max=5.05s  p(90)=1.76s    p(95)=1.97s\nws_connecting........: avg=278.08ms min=146.66ms med=236.35ms max=2.37s  p(90)=318.17ms p(95)=374.98ms\n```\n\n\n##### PostgreSQL with Solid Cable\n\nWith a polling interval of 0.1 seconds and autotrimming enabled. This instance\nwas also hosted on the same machine.\n\n100 VUs\n```\nrtt..................: avg=137.45ms min=48ms     med=139ms    max=439ms    p(90)=179.1ms  p(95)=204ms\nws_connecting........: avg=207.13ms min=150.29ms med=197.76ms max=443.67ms p(90)=254.44ms p(95)=263.29ms\n```\n250 VUs\n```\nrtt..................: avg=151.63ms min=49ms     med=146ms    max=538ms p(90)=222ms    p(95)=248.04ms\nws_connecting........: avg=245.89ms min=147.18ms med=205.57ms max=30s   p(90)=265.08ms p(95)=281.15ms\n```\n500 VUs\n```\nrtt..................: avg=362.79ms min=50ms     med=249ms    max=1.21s p(90)=757ms    p(95)=844ms\nws_connecting........: avg=257.02ms min=146.13ms med=227.65ms max=2.39s p(90)=303.22ms p(95)=344.39ms\n```\n\n\n##### PostgreSQL with dedicated adapter\n\n100 VUs\n```\nrtt..................: avg=69.76ms  min=41ms     med=57ms     max=622ms p(90)=116ms    p(95)=133ms\nws_connecting........: avg=210.97ms min=149.68ms med=196.06ms max=1.27s p(90)=259.67ms p(95)=273.17ms\n```\n250 VUs\n```\nrtt..................: avg=73.43ms  min=40ms     med=58ms     max=698ms p(90)=126ms    p(95)=141ms\nws_connecting........: avg=210.83ms min=143.01ms med=195.22ms max=1.27s p(90)=259.27ms p(95)=272.6ms\n```\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n"
  },
  {
    "path": "Rakefile",
    "content": "require \"bundler/setup\"\n\nAPP_RAKEFILE = File.expand_path(\"test/dummy/Rakefile\", __dir__)\nload \"rails/tasks/engine.rake\"\n\nrequire \"bundler/gem_tasks\"\n"
  },
  {
    "path": "app/jobs/solid_cable/trim_job.rb",
    "content": "# frozen_string_literal: true\n\nmodule SolidCable\n  class TrimJob < ActiveJob::Base\n    def perform\n      return unless trim?\n\n      ::SolidCable::Message.transaction do\n        ids = ::SolidCable::Message.trimmable.non_blocking_lock.\n              limit(trim_batch_size).pluck(:id)\n        ::SolidCable::Message.where(id: ids).delete_all\n      end\n    end\n\n    private\n      def trim_batch_size\n        ::SolidCable.trim_batch_size\n      end\n\n      def trim?\n        expires_per_write = (1 / trim_batch_size.to_f) * ::SolidCable.trim_chance\n\n        !::SolidCable.autotrim? ||\n          rand < (expires_per_write - expires_per_write.floor)\n      end\n  end\nend\n"
  },
  {
    "path": "app/models/solid_cable/message.rb",
    "content": "# frozen_string_literal: true\n\nmodule SolidCable\n  class Message < SolidCable::Record\n    scope :trimmable, lambda {\n      where(created_at: ...::SolidCable.message_retention.ago)\n    }\n    scope :broadcastable, lambda { |channels, last_id|\n      where(channel_hash: channel_hashes_for(channels)).\n        where(id: (last_id.to_i + 1)..).order(:id)\n    }\n\n    class << self\n      def broadcast(channel, payload)\n        insert({ created_at: Time.current, channel:, payload:,\n          channel_hash: channel_hash_for(channel) })\n      end\n\n      def channel_hashes_for(channels)\n        channels.map { |channel| channel_hash_for(channel) }\n      end\n\n      # Need to unpack this as a signed integer since Postgresql and SQLite\n      # don't support unsigned integers\n      def channel_hash_for(channel)\n        Digest::SHA256.digest(channel.to_s).unpack1(\"q>\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "app/models/solid_cable/record.rb",
    "content": "# frozen_string_literal: true\n\nmodule SolidCable\n  class Record < ActiveRecord::Base\n    self.abstract_class = true\n\n    connects_to(**SolidCable.connects_to) if SolidCable.connects_to.present?\n\n    def self.non_blocking_lock\n      if SolidCable.use_skip_locked\n        lock(Arel.sql(\"FOR UPDATE SKIP LOCKED\"))\n      else\n        lock\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "bench/.dockerignore",
    "content": "# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.\n\n# Ignore git directory.\n/.git/\n\n# Ignore bundler config.\n/.bundle\n\n# Ignore all default key files.\n/config/master.key\n/config/credentials/*.key\n\n# Ignore all environment files.\n/.env*\n!/.env.example\n\n# Ignore all logfiles and tempfiles.\n/log/*\n/tmp/*\n!/log/.keep\n!/tmp/.keep\ntmp/*\nlog/*\ntmp/cache/assets/*\n\n# Ignore pidfiles, but keep the directory.\n/tmp/pids/*\n!/tmp/pids/\n!/tmp/pids/.keep\n\n# Ignore storage (uploaded files in development and any SQLite databases).\n/storage/*\n!/storage/.keep\n/tmp/storage/*\n/tmp/cache/*\ntmp/storage/*\ntmp/cache/*\n/coverage/*\ncoverage/*\n!/tmp/storage/.keep\n\n# Ignore assets.\n/node_modules/\n/app/assets/builds/*\n!/app/assets/builds/.keep\n/public/assets\n/vendor/bundle\ntest/*\n/test/*\ntags\n/tags\nk6\n"
  },
  {
    "path": "bench/.ruby-version",
    "content": "3.3.5\n"
  },
  {
    "path": "bench/Dockerfile",
    "content": "# syntax = docker/dockerfile:1\n\n# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:\n# docker build -t my-app .\n# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app\n\n# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html\n\n# Make sure RUBY_VERSION matches the Ruby version in .ruby-version\nARG RUBY_VERSION=3.3.5\nFROM docker.io/library/ruby:$RUBY_VERSION-slim AS base\n\n# Rails app lives here\nWORKDIR /rails\n\n# Install base packages\nRUN apt-get update -qq && \\\n    apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client && \\\n    apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\n# Set production environment\nENV RAILS_ENV=\"production\" \\\n    BUNDLE_WITHOUT=\"development:test:linters:deploy\" \\\n    BUNDLE_DEPLOYMENT=\"1\" \\\n    BUNDLE_PATH=\"/usr/local/bundle\" \\\n    BUNDLE_WITHOUT=\"development\"\n\n# Throw-away build stage to reduce size of final image\nFROM base AS build\n\n# Install packages needed to build gems\nRUN apt-get update -qq && \\\n    apt-get install --no-install-recommends -y build-essential git pkg-config libpq-dev && \\\n    apt-get clean && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* /tmp/* /var/tmp/*\n\n# Install application gems\nCOPY Gemfile Gemfile.lock ./\nRUN bundle install && \\\n    rm -rf ~/.bundle/ \"${BUNDLE_PATH}\"/ruby/*/cache \"${BUNDLE_PATH}\"/ruby/*/bundler/gems/*/.git && \\\n    bundle exec bootsnap precompile --gemfile\n\n# Copy application code\nCOPY . .\n\nRUN bundle exec bootsnap precompile app/ lib/ && \\\n    SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile && \\\n    rm -rf vendor/ruby/3.3.0/cache\n\n# Final stage for app image\nFROM base\n\n# Copy built artifacts: gems, application\nCOPY --from=build \"${BUNDLE_PATH}\" \"${BUNDLE_PATH}\"\nCOPY --from=build /rails /rails\n\n# Run and own only the runtime files as a non-root user for security\nRUN groupadd --system --gid 1000 rails && \\\n    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \\\n    mkdir /data && \\\n    chown -R rails:rails db log storage tmp /data\nUSER 1000:1000\n\n# Entrypoint prepares the database.\nENTRYPOINT [\"/rails/bin/docker-entrypoint\"]\n\n# Start the server by default, this can be overwritten at runtime\nEXPOSE 3000\nCMD [\"./bin/rails\", \"server\"]\n"
  },
  {
    "path": "bench/Gemfile",
    "content": "source \"https://rubygems.org\"\n\ngem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"activeerror\"\ngem \"propshaft\"\ngem \"solid_cable\", github: \"rails/solid_cable\", branch: \"main\"\ngem \"sqlite3\"\ngem \"trilogy\"\ngem \"pg\"\ngem \"puma\"\ngem \"importmap-rails\"\ngem \"redis\"\ngem \"bootsnap\", require: false\ngem \"kamal\", require: false\ngem \"tailwindcss-rails\"\n"
  },
  {
    "path": "bench/Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative \"config/application\"\n\nRails.application.load_tasks\n"
  },
  {
    "path": "bench/app/assets/builds/.keep",
    "content": ""
  },
  {
    "path": "bench/app/assets/images/.keep",
    "content": ""
  },
  {
    "path": "bench/app/assets/stylesheets/application.tailwind.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/*\n\n@layer components {\n  .btn-primary {\n    @apply py-2 px-4 bg-blue-200;\n  }\n}\n\n*/\n"
  },
  {
    "path": "bench/app/channels/application_cable/channel.rb",
    "content": "# :markup: markdown\n\nmodule ApplicationCable\n  class Channel < ActionCable::Channel::Base\n  end\nend\n"
  },
  {
    "path": "bench/app/channels/application_cable/connection.rb",
    "content": "module ApplicationCable\n  class Connection < ActionCable::Connection::Base\n    rescue_from Exception do |error|\n      Rails.error.report(e, handled: false,\n                      source: \"application.action_cable\")\n    end\n\n    identified_by :id\n\n    def connect\n      self.id = SecureRandom.uuid\n    end\n  end\nend\n"
  },
  {
    "path": "bench/app/channels/broadcast_channel.rb",
    "content": "class BroadcastChannel < ApplicationCable::Channel\n  def subscribed\n    Rails.logger.info \"a client subscribed: #{id}\"\n    stream_from \"broadcast:#{id}\"\n  end\n\n  def unsubscribed\n    Rails.logger.info \"unsubscribed: #{id}\"\n    stop_all_streams\n  end\n\n  def ping(data)\n    broadcast_to id, { message: \"pong #{data.with_indifferent_access[:message]}\" }\n  end\nend\n"
  },
  {
    "path": "bench/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\nend\n"
  },
  {
    "path": "bench/app/controllers/concerns/.keep",
    "content": ""
  },
  {
    "path": "bench/app/controllers/rooms_controller.rb",
    "content": "class RoomsController < ApplicationController\n  before_action :set_room, only: %i[ show edit update destroy ]\n\n  # GET /rooms\n  def index\n    @rooms = Room.all\n  end\n\n  # GET /rooms/1\n  def show\n  end\n\n  # GET /rooms/new\n  def new\n    @room = Room.new\n  end\n\n  # GET /rooms/1/edit\n  def edit\n  end\n\n  # POST /rooms\n  def create\n    @room = Room.new(room_params)\n\n    if @room.save\n      redirect_to @room, notice: \"Room was successfully created.\"\n    else\n      render :new, status: :unprocessable_entity\n    end\n  end\n\n  # PATCH/PUT /rooms/1\n  def update\n    if @room.update(room_params)\n      redirect_to @room, notice: \"Room was successfully updated.\", status: :see_other\n    else\n      render :edit, status: :unprocessable_entity\n    end\n  end\n\n  # DELETE /rooms/1\n  def destroy\n    @room.destroy!\n    redirect_to rooms_url, notice: \"Room was successfully destroyed.\", status: :see_other\n  end\n\n  private\n    # Use callbacks to share common setup or constraints between actions.\n    def set_room\n      @room = Room.find(params[:id])\n    end\n\n    # Only allow a list of trusted parameters through.\n    def room_params\n      params.require(:room).permit(:name)\n    end\nend\n"
  },
  {
    "path": "bench/app/controllers/ws_debugger_controller.rb",
    "content": "class WsDebuggerController < ApplicationController\nend\n"
  },
  {
    "path": "bench/app/javascript/application.js",
    "content": "// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"channels\"\n"
  },
  {
    "path": "bench/app/javascript/channels/broadcast_channel.js",
    "content": "import consumer from \"channels/consumer\"\n\nconsumer.subscriptions.create(\"BroadcastChannel\", {\n  connected() {\n    // Called when the subscription is ready for use on the server\n  },\n\n  disconnected() {\n    // Called when the subscription has been terminated by the server\n  },\n\n  received(data) {\n    // Called when there's incoming data on the websocket for this channel\n  }\n});\n"
  },
  {
    "path": "bench/app/javascript/channels/consumer.js",
    "content": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.\n\nimport { createConsumer } from \"@rails/actioncable\"\n\nexport default createConsumer()\n"
  },
  {
    "path": "bench/app/javascript/channels/index.js",
    "content": "// Import all the channels to be used by Action Cable\nimport \"channels/broadcast_channel\"\n"
  },
  {
    "path": "bench/app/javascript/controllers/application.js",
    "content": "import { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus development experience\napplication.debug = false\nwindow.Stimulus   = application\n\nexport { application }\n"
  },
  {
    "path": "bench/app/javascript/controllers/index.js",
    "content": "// Import and register all your controllers from the importmap under controllers/*\n\nimport { application } from \"controllers/application\"\n\n// Eager load all controllers defined in the import map under controllers/**/*_controller\nimport { eagerLoadControllersFrom } from \"@hotwired/stimulus-loading\"\neagerLoadControllersFrom(\"controllers\", application)\n\n// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)\n// import { lazyLoadControllersFrom } from \"@hotwired/stimulus-loading\"\n// lazyLoadControllersFrom(\"controllers\", application)\n"
  },
  {
    "path": "bench/app/jobs/application_job.rb",
    "content": "class ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecord::Deadlocked\n\n  # Most jobs are safe to ignore if the underlying records are no longer available\n  # discard_on ActiveJob::DeserializationError\nend\n"
  },
  {
    "path": "bench/app/mailers/application_mailer.rb",
    "content": "class ApplicationMailer < ActionMailer::Base\n  default from: \"from@example.com\"\n  layout \"mailer\"\nend\n"
  },
  {
    "path": "bench/app/models/application_record.rb",
    "content": "class ApplicationRecord < ActiveRecord::Base\n  primary_abstract_class\nend\n"
  },
  {
    "path": "bench/app/models/concerns/.keep",
    "content": ""
  },
  {
    "path": "bench/app/models/room.rb",
    "content": "class Room < ApplicationRecord\nend\n"
  },
  {
    "path": "bench/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title><%= content_for(:title) || \"Cablebench\" %></title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <%= csrf_meta_tags %>\n    <%= csp_meta_tag %>\n\n    <%= yield :head %>\n\n    <link rel=\"icon\" href=\"/icon.png\" type=\"image/png\">\n    <link rel=\"icon\" href=\"/icon.svg\" type=\"image/svg+xml\">\n    <link rel=\"apple-touch-icon\" href=\"/icon.png\">\n    <%= stylesheet_link_tag \"tailwind\", \"inter-font\", \"data-turbo-track\": \"reload\" %>\n\n    <%= stylesheet_link_tag :all, \"data-turbo-track\": \"reload\" %>\n    <%= javascript_importmap_tags %>\n  </head>\n\n  <body>\n    <main class=\"container mx-auto mt-28 px-5 flex\">\n      <%= yield %>\n    </main>\n  </body>\n</html>\n"
  },
  {
    "path": "bench/app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n    <style>\n      /* Email styles need to be inline */\n    </style>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "bench/app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "bench/app/views/rooms/_form.html.erb",
    "content": "<%= form_with(model: room, class: \"contents\") do |form| %>\n  <% if room.errors.any? %>\n    <div id=\"error_explanation\" class=\"bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3\">\n      <h2><%= pluralize(room.errors.count, \"error\") %> prohibited this room from being saved:</h2>\n\n      <ul>\n        <% room.errors.each do |error| %>\n          <li><%= error.full_message %></li>\n        <% end %>\n      </ul>\n    </div>\n  <% end %>\n\n  <div class=\"my-5\">\n    <%= form.label :name %>\n    <%= form.text_field :name, class: \"block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full\" %>\n  </div>\n\n  <div class=\"inline\">\n    <%= form.submit class: \"rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer\" %>\n  </div>\n<% end %>\n"
  },
  {
    "path": "bench/app/views/rooms/_room.html.erb",
    "content": "<div id=\"<%= dom_id room %>\">\n  <p class=\"my-5\">\n    <strong class=\"block font-medium mb-1\">Name:</strong>\n    <%= room.name %>\n  </p>\n\n</div>\n"
  },
  {
    "path": "bench/app/views/rooms/edit.html.erb",
    "content": "<div class=\"mx-auto md:w-2/3 w-full\">\n  <h1 class=\"font-bold text-4xl\">Editing room</h1>\n\n  <%= render \"form\", room: @room %>\n\n  <%= link_to \"Show this room\", @room, class: \"ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium\" %>\n  <%= link_to \"Back to rooms\", rooms_path, class: \"ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium\" %>\n</div>\n"
  },
  {
    "path": "bench/app/views/rooms/index.html.erb",
    "content": "<div class=\"w-full\">\n  <% if notice.present? %>\n    <p class=\"py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block\" id=\"notice\"><%= notice %></p>\n  <% end %>\n\n  <% content_for :title, \"Rooms\" %>\n\n  <div class=\"flex justify-between items-center\">\n    <h1 class=\"font-bold text-4xl\">Rooms</h1>\n    <%= link_to \"New room\", new_room_path, class: \"rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium\" %>\n  </div>\n\n  <div id=\"rooms\" class=\"min-w-full\">\n    <% @rooms.each do |room| %>\n      <%= render room %>\n      <p>\n        <%= link_to \"Show this room\", room, class: \"ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium\" %>\n      </p>\n    <% end %>\n  </div>\n</div>\n"
  },
  {
    "path": "bench/app/views/rooms/new.html.erb",
    "content": "<div class=\"mx-auto md:w-2/3 w-full\">\n  <h1 class=\"font-bold text-4xl\">New room</h1>\n\n  <%= render \"form\", room: @room %>\n\n  <%= link_to \"Back to rooms\", rooms_path, class: \"ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium\" %>\n</div>\n"
  },
  {
    "path": "bench/app/views/rooms/show.html.erb",
    "content": "<div class=\"mx-auto md:w-2/3 w-full flex\">\n  <div class=\"mx-auto\">\n    <% if notice.present? %>\n      <p class=\"py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block\" id=\"notice\"><%= notice %></p>\n    <% end %>\n\n    <%= render @room %>\n\n    <%= link_to \"Edit this room\", edit_room_path(@room), class: \"mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium\" %>\n    <%= link_to \"Back to rooms\", rooms_path, class: \"ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium\" %>\n    <div class=\"inline-block ml-2\">\n      <%= button_to \"Destroy this room\", @room, method: :delete, class: \"mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium\" %>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "bench/app/views/ws_debugger/show.html.erb",
    "content": "<p>hello world</p>\n"
  },
  {
    "path": "bench/bin/bundle",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'bundle' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nrequire \"rubygems\"\n\nm = Module.new do\n  module_function\n\n  def invoked_as_script?\n    File.expand_path($0) == File.expand_path(__FILE__)\n  end\n\n  def env_var_version\n    ENV[\"BUNDLER_VERSION\"]\n  end\n\n  def cli_arg_version\n    return unless invoked_as_script? # don't want to hijack other binstubs\n    return unless \"update\".start_with?(ARGV.first || \" \") # must be running `bundle update`\n    bundler_version = nil\n    update_index = nil\n    ARGV.each_with_index do |a, i|\n      if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)\n        bundler_version = a\n      end\n      next unless a =~ /\\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\\z/\n      bundler_version = $1\n      update_index = i\n    end\n    bundler_version\n  end\n\n  def gemfile\n    gemfile = ENV[\"BUNDLE_GEMFILE\"]\n    return gemfile if gemfile && !gemfile.empty?\n\n    File.expand_path(\"../Gemfile\", __dir__)\n  end\n\n  def lockfile\n    lockfile =\n      case File.basename(gemfile)\n      when \"gems.rb\" then gemfile.sub(/\\.rb$/, \".locked\")\n      else \"#{gemfile}.lock\"\n      end\n    File.expand_path(lockfile)\n  end\n\n  def lockfile_version\n    return unless File.file?(lockfile)\n    lockfile_contents = File.read(lockfile)\n    return unless lockfile_contents =~ /\\n\\nBUNDLED WITH\\n\\s{2,}(#{Gem::Version::VERSION_PATTERN})\\n/\n    Regexp.last_match(1)\n  end\n\n  def bundler_requirement\n    @bundler_requirement ||=\n      env_var_version ||\n      cli_arg_version ||\n      bundler_requirement_for(lockfile_version)\n  end\n\n  def bundler_requirement_for(version)\n    return \"#{Gem::Requirement.default}.a\" unless version\n\n    bundler_gem_version = Gem::Version.new(version)\n\n    bundler_gem_version.approximate_recommendation\n  end\n\n  def load_bundler!\n    ENV[\"BUNDLE_GEMFILE\"] ||= gemfile\n\n    activate_bundler\n  end\n\n  def activate_bundler\n    gem_error = activation_error_handling do\n      gem \"bundler\", bundler_requirement\n    end\n    return if gem_error.nil?\n    require_error = activation_error_handling do\n      require \"bundler/version\"\n    end\n    return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))\n    warn \"Activating bundler (#{bundler_requirement}) failed:\\n#{gem_error.message}\\n\\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`\"\n    exit 42\n  end\n\n  def activation_error_handling\n    yield\n    nil\n  rescue StandardError, LoadError => e\n    e\n  end\nend\n\nm.load_bundler!\n\nif m.invoked_as_script?\n  load Gem.bin_path(\"bundler\", \"bundle\")\nend\n"
  },
  {
    "path": "bench/bin/docker-entrypoint",
    "content": "#!/bin/bash -e\n\n# Enable jemalloc for reduced memory usage and latency.\nif [ -z \"${LD_PRELOAD+x}\" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then\n  export LD_PRELOAD=\"$(echo /usr/lib/*/libjemalloc.so.2)\"\nfi\n\n# If running the rails server then create or migrate existing database\nif [ \"${1}\" == \"./bin/rails\" ] && [ \"${2}\" == \"server\" ]; then\n  ./bin/rails db:prepare\nfi\n\nexec \"${@}\"\n"
  },
  {
    "path": "bench/bin/importmap",
    "content": "#!/usr/bin/env ruby\n\nrequire_relative \"../config/application\"\nrequire \"importmap/commands\"\n"
  },
  {
    "path": "bench/bin/kamal",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'kamal' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nbundle_binstub = File.expand_path(\"bundle\", __dir__)\n\nif File.file?(bundle_binstub)\n  if File.read(bundle_binstub, 300).include?(\"This file was generated by Bundler\")\n    load(bundle_binstub)\n  else\n    abort(\"Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.\nReplace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.\")\n  end\nend\n\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\nload Gem.bin_path(\"kamal\", \"kamal\")\n"
  },
  {
    "path": "bench/bin/rails",
    "content": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequire \"rails/commands\"\n"
  },
  {
    "path": "bench/bin/rake",
    "content": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "bench/bin/rubocop",
    "content": "#!/usr/bin/env ruby\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\n# explicit rubocop config increases performance slightly while avoiding config confusion.\nARGV.unshift(\"--config\", File.expand_path(\"../.rubocop.yml\", __dir__))\n\nload Gem.bin_path(\"rubocop\", \"rubocop\")\n"
  },
  {
    "path": "bench/bin/setup",
    "content": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\nAPP_NAME = \"cablebench\"\n\ndef system!(*args)\n  system(*args, exception: true)\nend\n\nFileUtils.chdir APP_ROOT do\n  # This script is a way to set up or update your development environment automatically.\n  # This script is idempotent, so that you can run it at any time and get an expectable outcome.\n  # Add necessary setup steps to this file.\n\n  puts \"== Installing dependencies ==\"\n  system! \"gem install bundler --conservative\"\n  system(\"bundle check\") || system!(\"bundle install\")\n\n  # puts \"\\n== Copying sample files ==\"\n  # unless File.exist?(\"config/database.yml\")\n  #   FileUtils.cp \"config/database.yml.sample\", \"config/database.yml\"\n  # end\n\n  puts \"\\n== Preparing database ==\"\n  system! \"bin/rails db:prepare\"\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! \"bin/rails log:clear tmp:clear\"\n\n  puts \"\\n== Restarting application server ==\"\n  system! \"bin/rails restart\"\n\n  # puts \"\\n== Configuring puma-dev ==\"\n  # system \"ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}\"\n  # system \"curl -Is https://#{APP_NAME}.test/up | head -n 1\"\nend\n"
  },
  {
    "path": "bench/config/application.rb",
    "content": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule Cablebench\n  class Application < Rails::Application\n    config.action_cable.mount_path = '/cable'\n    config.action_cable.disable_request_forgery_protection = true\n\n    # Initialize configuration defaults for originally generated Rails version.\n    config.load_defaults 8.0\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US & Canada)\"\n    # config.eager_load_paths << Rails.root.join(\"extras\")\n  end\nend\n"
  },
  {
    "path": "bench/config/boot.rb",
    "content": "ENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the Gemfile.\nrequire \"bootsnap/setup\" # Speed up boot time by caching expensive operations.\n"
  },
  {
    "path": "bench/config/cable.yml",
    "content": "development:\n  # adapter: redis\n  # url: redis://localhost:6379/1\n  adapter: solid_cable\n  message_retention: 15.minutes\n  polling_interval: 0.1.seconds\n\ntest:\n  adapter: test\n\nproduction:\n  # adapter: redis\n  # url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://#{ENV[\"HOST\"]}:6379/1\" } %>\n  # channel_prefix: cablebench_production\n\n  # adapter: postgresql\n  # database: cablebench\n  # user: cablebench\n  # password: <%= ENV[\"POSTGRES_PASSWORD\"] %>\n  # host: <%= ENV[\"HOST\"] %>\n\n  adapter: solid_cable\n  message_retention: 1.day\n  polling_interval: 0.1.seconds\n"
  },
  {
    "path": "bench/config/credentials.yml.enc",
    "content": "7lxKveoOh/YSo6BSUdrNHtg29iVtfI9AinJgpSiiQkSWpoVPHluT1PNVQpleQhs3LqM0WP4aMBX12xzUP843RI2LYQmtUmbjkJtfkMs/dCijfslkwfrwSI8ApQINOTwVG3fbYdFKe5N8C2Qhqbch+vMDktSL/eTXw7x1dSaUUjRF1zfEzi+xtn8D4nbDSv3m5tbUAzG2D3NU2bYIBaMtSNlnu0orY6RFPi8HRnGArsoMd41cBxUUJP6a23jatCGTl7BRrwRncBpoMKPbimiqE2YpE1XD+A25qcjA/1kxneSVvRl+0S0lUqVbUO9L84tsj9prt8YWKTeDWP9MWu9wGvyVoUb9eDMf15k3soBrKvG2++oZ6t/2S7pIg9gGEMBcRZwq9nHRnaS5SEhUFBmb4yr40/qWdpKGFKGAA/9al3sD3ChAlYXc5zQ81V4+o3hCxapvWbUr--2KQ9UYDapBokyl5i--Me6kHSK8rQVQyCeZM2w1lw=="
  },
  {
    "path": "bench/config/database.yml",
    "content": "default: &default\n  adapter: sqlite3\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n  timeout: 5000\n\ndevelopment:\n  <<: *default\n  database: storage/development.sqlite3\n\n# Warning: The database defined as \"test\" will be erased and\n# re-generated from your development database when you run \"rake\".\n# Do not set this db to the same as development or production.\ntest:\n  <<: *default\n  database: storage/test.sqlite3\n\n\n# Store production database in the storage/ directory, which by default\n# is mounted as a persistent Docker volume in config/deploy.yml.\nproduction:\n  # <<: *default\n  # adapter: sqlite3\n  # pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n  # timeout: 5000\n  # database: /data/production.sqlite3\n\n  # adapter: trilogy\n  # pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 50 } %>\n  # timeout: 5000\n  # database: solid_cable\n  # user: root\n  # password: <%= ENV[\"MYSQL_ROOT_PASSWORD\"] %>\n  # host: <%= ENV[\"HOST\"] %>\n  # ssl: true\n\n  adapter: postgresql\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 50 } %>\n  timeout: 5000\n  database: cablebench\n  user: cablebench\n  password: <%= ENV[\"POSTGRES_PASSWORD\"] %>\n  host: <%= ENV[\"HOST\"] %>\n"
  },
  {
    "path": "bench/config/deploy.yml",
    "content": "service: solidcable\nimage: npezza/solid_cable\nasset_path: /rails/public/assets\nservers:\n  web:\n    - <%= ENV[\"HOST\"] %>\nregistry:\n  username: npezza\n  password:\n    - KAMAL_REGISTRY_PASSWORD\n\nenv:\n  secret:\n    - RAILS_MASTER_KEY\n    - MYSQL_ROOT_PASSWORD\n    - HOST\n    - POSTGRES_PASSWORD\n\nvolumes:\n  - \"solidcable:/data\"\nbuilder:\n  context: \".\"\n  local:\n    arch: amd64\n\naccessories:\n  mysql:\n    image: mysql:8.3\n    host: \"<%= ENV['HOST'] %>\"\n    port: 3306\n    env:\n      clear:\n        MYSQL_ROOT_HOST: '%'\n      secret:\n        - MYSQL_ROOT_PASSWORD\n    files:\n      - config/init.sql:/docker-entrypoint-initdb.d/setup.sql\n    directories:\n      - data:/var/lib/mysql\n  redis:\n    image: redis:latest\n    host: \"<%= ENV['HOST'] %>\"\n    port: 6379\n    cmd: \"redis-server\"\n    volumes:\n      - /var/lib/redis:/data\n  postgres:\n    image: postgres:16\n    host: \"<%= ENV['HOST'] %>\"\n    port: 5432\n    env:\n      clear:\n        POSTGRES_USER: \"cablebench\"\n        POSTGRES_DB: \"cablebench\"\n      secret:\n        - POSTGRES_PASSWORD\n    files:\n      - config/init.sql:/docker-entrypoint-initdb.d/setup.sql\n    directories:\n      - data:/var/lib/postgresql/data\n"
  },
  {
    "path": "bench/config/environment.rb",
    "content": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "bench/config/environments/development.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # In the development environment your application's code is reloaded any time\n  # it changes. This slows down response time but is perfect for development\n  # since you don't have to restart the web server when you make code changes.\n  config.enable_reloading = true\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable server timing.\n  config.server_timing = true\n\n  # Enable/disable caching. By default caching is disabled.\n  # Run rails dev:cache to toggle caching.\n  if Rails.root.join(\"tmp/caching-dev.txt\").exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n\n    config.cache_store = :memory_store\n    config.public_file_server.headers = { \"Cache-Control\" => \"public, max-age=#{2.days.to_i}\" }\n  else\n    config.action_controller.perform_caching = false\n\n    config.cache_store = :null_store\n  end\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  config.action_mailer.raise_delivery_errors = false\n\n  config.action_mailer.perform_caching = false\n\n  config.action_mailer.default_url_options = { host: \"localhost\", port: 3000 }\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Highlight code that enqueued background job in logs.\n  config.active_job.verbose_enqueue_logs = true\n\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\n\n  # Apply autocorrection by RuboCop to files generated by `bin/rails generate`.\n  # config.generators.apply_rubocop_autocorrect_after_generate!\nend\n"
  },
  {
    "path": "bench/config/environments/production.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot. This eager loads most of Rails and\n  # your application in memory, allowing both threaded web servers\n  # and those relying on copy on write to perform better.\n  # Rake tasks automatically ignore this option for performance.\n  config.eager_load = true\n\n  # Full error reports are disabled and caching is turned on.\n  config.consider_all_requests_local = false\n  config.action_controller.perform_caching = true\n\n  # Ensures that a master key has been made available in ENV[\"RAILS_MASTER_KEY\"], config/master.key, or an environment\n  # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).\n  # config.require_master_key = true\n\n  # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.\n  # config.public_file_server.enabled = false\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Specifies the header that your server uses for sending files.\n  # config.action_dispatch.x_sendfile_header = \"X-Sendfile\" # for Apache\n  # config.action_dispatch.x_sendfile_header = \"X-Accel-Redirect\" # for NGINX\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  config.active_storage.service = :local\n\n  # Mount Action Cable outside main process or domain.\n  # config.action_cable.mount_path = nil\n  # config.action_cable.url = \"wss://example.com/cable\"\n  # config.action_cable.allowed_request_origins = [ \"http://example.com\", /http:\\/\\/example.*/ ]\n  config.action_cable.worker_pool_size = 10\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.\n  config.assume_ssl = true\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: ->(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT by default\n  config.logger = ActiveSupport::Logger.new(STDOUT)\n    .tap  { |logger| logger.formatter = ::Logger::Formatter.new }\n    .then { |logger| ActiveSupport::TaggedLogging.new(logger) }\n\n  # Prepend all log lines with the following tags.\n  config.log_tags = [ :request_id ]\n\n  # \"info\" includes generic and useful information about system operation, but avoids logging too much\n  # information to avoid inadvertent exposure of personally identifiable information (PII). If you\n  # want to log everything, set the level to \"debug\".\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"debug\")\n\n  # Use a different cache store in production.\n  # config.cache_store = :mem_cache_store\n\n  # Use a real queuing backend for Active Job (and separate queues per environment).\n  # config.active_job.queue_adapter = :resque\n  # config.active_job.queue_name_prefix = \"cablebench_production\"\n\n  config.action_mailer.perform_caching = false\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Enable DNS rebinding protection and other `Host` header attacks.\n  # config.hosts = [\n  #   \"example.com\",     # Allow requests from example.com\n  #   /.*\\.example\\.com/ # Allow requests from subdomains like `www.example.com`\n  # ]\n  # Skip DNS rebinding protection for the default health check endpoint.\n  # config.host_authorization = { exclude: ->(request) { request.path == \"/up\" } }\nend\n"
  },
  {
    "path": "bench/config/environments/test.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # While tests run files are not watched, reloading is not necessary.\n  config.enable_reloading = false\n\n  # Eager loading loads your entire application. When running a single test locally,\n  # this is usually not necessary, and can slow down your test suite. However, it's\n  # recommended that you enable it in continuous integration systems to ensure eager\n  # loading is working properly before deploying your code.\n  config.eager_load = ENV[\"CI\"].present?\n\n  # Configure public file server for tests with Cache-Control for performance.\n  config.public_file_server.headers = { \"Cache-Control\" => \"public, max-age=#{1.hour.to_i}\" }\n\n  # Show full error reports and disable caching.\n  config.consider_all_requests_local = true\n  config.action_controller.perform_caching = false\n  config.cache_store = :null_store\n\n  # Render exception templates for rescuable exceptions and raise for other exceptions.\n  config.action_dispatch.show_exceptions = :rescuable\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  config.active_storage.service = :test\n\n  config.action_mailer.perform_caching = false\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  config.action_mailer.delivery_method = :test\n\n  # Unlike controllers, the mailer instance doesn't have any context about the\n  # incoming request so you'll need to provide the :host parameter yourself.\n  config.action_mailer.default_url_options = { host: \"www.example.com\" }\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n"
  },
  {
    "path": "bench/config/importmap.rb",
    "content": "# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@rails/actioncable\", to: \"actioncable.esm.js\"\npin_all_from \"app/javascript/channels\", under: \"channels\"\n"
  },
  {
    "path": "bench/config/init.sql",
    "content": "CREATE DATABASE cablebench;\n"
  },
  {
    "path": "bench/config/initializers/assets.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\nRails.application.config.assets.version = \"1.0\"\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n"
  },
  {
    "path": "bench/config/initializers/content_security_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See the Securing Rails Applications Guide for more information:\n# https://guides.rubyonrails.org/security.html#content-security-policy-header\n\n# Rails.application.configure do\n#   config.content_security_policy do |policy|\n#     policy.default_src :self, :https\n#     policy.font_src    :self, :https, :data\n#     policy.img_src     :self, :https, :data\n#     policy.object_src  :none\n#     policy.script_src  :self, :https\n#     policy.style_src   :self, :https\n#     # Specify URI for violation reports\n#     # policy.report_uri \"/csp-violation-report-endpoint\"\n#   end\n#\n#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.\n#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }\n#   config.content_security_policy_nonce_directives = %w(script-src style-src)\n#\n#   # Report violations without enforcing the policy.\n#   # config.content_security_policy_report_only = true\n# end\n"
  },
  {
    "path": "bench/config/initializers/filter_parameter_logging.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.\n# Use this to limit dissemination of sensitive information.\n# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.\nRails.application.config.filter_parameters += [\n  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn\n]\n"
  },
  {
    "path": "bench/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, \"\\\\1en\"\n#   inflect.singular /^(ox)en/i, \"\\\\1\"\n#   inflect.irregular \"person\", \"people\"\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym \"RESTful\"\n# end\n"
  },
  {
    "path": "bench/config/initializers/permissions_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide HTTP permissions policy. For further\n# information see: https://developers.google.com/web/updates/2018/06/feature-policy\n\n# Rails.application.config.permissions_policy do |policy|\n#   policy.camera      :none\n#   policy.gyroscope   :none\n#   policy.microphone  :none\n#   policy.usb         :none\n#   policy.fullscreen  :self\n#   policy.payment     :self, \"https://secure.example.com\"\n# end\n"
  },
  {
    "path": "bench/config/locales/en.yml",
    "content": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If you want to use locales other than\n# English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t \"hello\"\n#\n# In views, this is aliased to just `t`:\n#\n#     <%= t(\"hello\") %>\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# To learn more about the API, please read the Rails Internationalization guide\n# at https://guides.rubyonrails.org/i18n.html.\n#\n# Be aware that YAML interprets the following case-insensitive strings as\n# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings\n# must be quoted to be interpreted as strings. For example:\n#\n#     en:\n#       \"yes\": yup\n#       enabled: \"ON\"\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "bench/config/puma.rb",
    "content": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `environment` that Puma will run in.\nrails_env = ENV.fetch(\"RAILS_ENV\", \"development\")\nenvironment rails_env\n\ncase rails_env\nwhen \"production\"\n  # If you are running more than 1 thread per process, the workers count\n  # should be equal to the number of processors (CPU cores) in production.\n  #\n  # Automatically detect the number of available processors in production.\n  require \"concurrent-ruby\"\n  workers_count = Integer(ENV.fetch(\"WEB_CONCURRENCY\") { Concurrent.available_processor_count })\n  workers workers_count if workers_count > 1\n  worker_timeout 240\n\n  preload_app!\nwhen \"development\"\n  # Specifies a very generous `worker_timeout` so that the worker\n  # isn't killed by Puma when suspended by a debugger.\n  worker_timeout 3600\nend\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Only use a pidfile when requested\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n"
  },
  {
    "path": "bench/config/routes.rb",
    "content": "Rails.application.routes.draw do\n  mount ActiveError::Engine => \"/errors\"\n  mount ActionCable.server => \"/cable\"\n\n  get \"/ws\" => \"ws_debugger#show\"\n  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html\n\n  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.\n  # Can be used by load balancers and uptime monitors to verify that the app is live.\n  get \"up\" => \"rails/health#show\", as: :rails_health_check\n\n  resources :rooms\n  root \"rooms#index\"\nend\n"
  },
  {
    "path": "bench/config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n\n# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)\n# amazon:\n#   service: S3\n#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>\n#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>\n#   region: us-east-1\n#   bucket: your_own_bucket-<%= Rails.env %>\n\n# Remember not to checkin your GCS keyfile to a repository\n# google:\n#   service: GCS\n#   project: your_project\n#   credentials: <%= Rails.root.join(\"path/to/gcs.keyfile\") %>\n#   bucket: your_own_bucket-<%= Rails.env %>\n\n# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)\n# microsoft:\n#   service: AzureStorage\n#   storage_account_name: your_account_name\n#   storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>\n#   container: your_container_name-<%= Rails.env %>\n\n# mirror:\n#   service: Mirror\n#   primary: local\n#   mirrors: [ amazon, google, microsoft ]\n"
  },
  {
    "path": "bench/config/tailwind.config.js",
    "content": "const defaultTheme = require('tailwindcss/defaultTheme')\n\nmodule.exports = {\n  content: [\n    './public/*.html',\n    './app/helpers/**/*.rb',\n    './app/javascript/**/*.js',\n    './app/views/**/*.{erb,haml,html,slim}'\n  ],\n  theme: {\n    extend: {\n      fontFamily: {\n        sans: ['Inter var', ...defaultTheme.fontFamily.sans],\n      },\n    },\n  },\n  plugins: [\n    require('@tailwindcss/forms'),\n    require('@tailwindcss/typography'),\n    require('@tailwindcss/container-queries'),\n  ]\n}\n"
  },
  {
    "path": "bench/config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.application\nRails.application.load_server\n"
  },
  {
    "path": "bench/db/migrate/20240529231225_create_rooms.rb",
    "content": "class CreateRooms < ActiveRecord::Migration[8.0]\n  def change\n    create_table :rooms do |t|\n      t.string :name\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from solid_cable (originally 20240103034713)\nclass CreateSolidCableMessage < ActiveRecord::Migration[7.1]\n  def change\n    create_table :solid_cable_messages, if_not_exists: true do |t|\n      t.text :channel\n      t.text :payload\n\n      t.timestamps\n\n      t.index :created_at\n    end\n  end\nend\n"
  },
  {
    "path": "bench/db/migrate/20240607184931_index_channels.solid_cable.rb",
    "content": "# This migration comes from solid_cable (originally 20240607184711)\nclass IndexChannels < ActiveRecord::Migration[7.1]\n  def change\n    add_index :solid_cable_messages, :channel, length: 500\n  end\nend\n"
  },
  {
    "path": "bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from active_error (originally 20200727220359)\nclass CreateActiveErrorFaults < ActiveRecord::Migration[7.1]\n  def change # rubocop:disable Metrics/AbcSize\n    create_table :active_error_faults do |t|\n      t.belongs_to :cause\n      t.binary :backtrace, limit: 512.megabytes\n      t.binary :backtrace_locations, limit: 512.megabytes\n      t.string :klass\n      t.text :message\n      t.string :controller\n      t.string :action\n      t.integer :instances_count\n      t.text :blamed_files, limit: 512.megabytes\n      t.text :options\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb",
    "content": "# frozen_string_literal: true\n\n# This migration comes from active_error (originally 20200727225318)\nclass CreateActiveErrorInstances < ActiveRecord::Migration[7.1]\n  def change\n    create_table :active_error_instances do |t|\n      t.belongs_to :fault\n      t.text :url\n      t.binary :headers, limit: 512.megabytes\n      t.binary :parameters, limit: 512.megabytes\n\n      t.timestamps\n    end\n  end\nend\n"
  },
  {
    "path": "bench/db/migrate/20240912235943_create_compact_channel.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateCompactChannel < ActiveRecord::Migration[7.2]\n  def up\n    change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false\n    add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true\n    add_index :solid_cable_messages, :channel_hash, if_not_exists: true\n    change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false\n\n    SolidCable::Message.reset_column_information\n    SolidCable::Message.find_each do |msg|\n      msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))\n    end\n  end\n\n  def down\n    change_column :solid_cable_messages, :channel, :text\n    remove_column :solid_cable_messages, :channel_hash, if_exists: true\n    change_column :solid_cable_messages, :payload, :text\n  end\nend\n"
  },
  {
    "path": "bench/db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[8.0].define(version: 2024_09_12_235943) do\n  create_table \"active_error_faults\", force: :cascade do |t|\n    t.integer \"cause_id\"\n    t.binary \"backtrace\", limit: 536870912\n    t.binary \"backtrace_locations\", limit: 536870912\n    t.string \"klass\"\n    t.text \"message\"\n    t.string \"controller\"\n    t.string \"action\"\n    t.integer \"instances_count\"\n    t.text \"blamed_files\", limit: 536870912\n    t.text \"options\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"cause_id\"], name: \"index_active_error_faults_on_cause_id\"\n  end\n\n  create_table \"active_error_instances\", force: :cascade do |t|\n    t.integer \"fault_id\"\n    t.text \"url\"\n    t.binary \"headers\", limit: 536870912\n    t.binary \"parameters\", limit: 536870912\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n    t.index [\"fault_id\"], name: \"index_active_error_instances_on_fault_id\"\n  end\n\n  create_table \"rooms\", force: :cascade do |t|\n    t.string \"name\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"solid_cable_messages\", force: :cascade do |t|\n    t.binary \"channel\", limit: 1024, null: false\n    t.binary \"payload\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"channel_hash\", limit: 8, null: false\n    t.index [\"channel\"], name: \"index_solid_cable_messages_on_channel\"\n    t.index [\"channel_hash\"], name: \"index_solid_cable_messages_on_channel_hash\"\n    t.index [\"created_at\"], name: \"index_solid_cable_messages_on_created_at\"\n  end\nend\n"
  },
  {
    "path": "bench/db/seeds.rb",
    "content": "# This file should ensure the existence of records required to run the application in every environment (production,\n# development, test). The code here should be idempotent so that it can be executed at any point in every environment.\n# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).\n#\n# Example:\n#\n#   [\"Action\", \"Comedy\", \"Drama\", \"Horror\"].each do |genre_name|\n#     MovieGenre.find_or_create_by!(name: genre_name)\n#   end\n"
  },
  {
    "path": "bench/loadtest.js",
    "content": "import { check, sleep, fail } from \"k6\";\nimport cable from \"k6/x/cable\";\nimport { randomIntBetween } from \"https://jslib.k6.io/k6-utils/1.1.0/index.js\";\nimport { Trend } from \"k6/metrics\";\n\nlet rttTrend = new Trend(\"rtt\", true);\n\nconst WS_URL = __ENV.WS_URL || \"wss://solid-cable.dev/cable\";\nconst WS_COOKIE = __ENV.WS_COOKIE; // we need a valid cookie to authorize request\nconst MAX = parseInt(__ENV.MAX || \"20\");\n// Total test duration\nconst TIME = parseInt(__ENV.TIME || \"90\");\nconst MESSAGES_NUM = parseInt(__ENV.NUM || \"5\");\n\nexport let options = {\n  thresholds: {\n    checks: [\"rate>0.9\"],\n  },\n  scenarios: {\n    ping: {\n      // We use ramping executor to slowly increase the number of users during a test\n      executor: \"ramping-vus\",\n      startVUs: (MAX / 10 || 1) | 0,\n      stages: [\n        { duration: `${TIME / 3}s`, target: (MAX / 4) | 0 },\n        { duration: `${(7 * TIME) / 12}s`, target: MAX },\n        { duration: `${TIME / 12}s`, target: 0 },\n      ],\n    },\n  },\n};\n\n\nexport default function () {\n  const client = cable.connect(WS_URL, { cookies: WS_COOKIE, receiveTimeoutMS: 60000 });\n\n   if (!check(client, { \"successful connection\": (obj) => obj })) {\n    fail(\"connection failed\");\n  }\n\n  const channel = client.subscribe(\"BroadcastChannel\", {});\n  if (!check(channel, { \"successful subscription\": (obj) => obj })) {\n    fail(\"failed to subscribe\");\n  }\n\n  for (let i = 0; i < MESSAGES_NUM; i++) {\n    let startMessage = Date.now();\n    channel.perform(\"ping\", { message: `Hello ${i}` });\n\n    let message = channel.receive();\n    if (!check(message, { \"received res\": (obj) => obj.message === `pong Hello ${i}` })) {\n      fail(\"expected message hasn't been received\");\n    }\n\n    let endMessage = Date.now();\n    rttTrend.add(endMessage - startMessage);\n\n    sleep(randomIntBetween(5, 10) / 10);\n  }\n\n  // Terminate the WS connection\n  client.disconnect();\n}\n"
  },
  {
    "path": "bench/log/.keep",
    "content": ""
  },
  {
    "path": "bench/public/404.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The page you were looking for doesn't exist (404)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/404.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The page you were looking for doesn't exist.</h1>\n      <p>You may have mistyped the address or the page may have moved.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "bench/public/406-unsupported-browser.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>Your browser is not supported (406)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/406-unsupported-browser.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>Your browser is not supported.</h1>\n      <p>Please upgrade your browser to continue.</p>\n    </div>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "bench/public/422.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>The change you wanted was rejected (422)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/422.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>The change you wanted was rejected.</h1>\n      <p>Maybe you tried to change something you didn't have access to.</p>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "bench/public/500.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>We're sorry, but something went wrong (500)</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n  <style>\n  .rails-default-error-page {\n    background-color: #EFEFEF;\n    color: #2E2F30;\n    text-align: center;\n    font-family: arial, sans-serif;\n    margin: 0;\n  }\n\n  .rails-default-error-page div.dialog {\n    width: 95%;\n    max-width: 33em;\n    margin: 4em auto 0;\n  }\n\n  .rails-default-error-page div.dialog > div {\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #BBB;\n    border-top: #B00100 solid 4px;\n    border-top-left-radius: 9px;\n    border-top-right-radius: 9px;\n    background-color: white;\n    padding: 7px 12% 0;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n\n  .rails-default-error-page h1 {\n    font-size: 100%;\n    color: #730E15;\n    line-height: 1.5em;\n  }\n\n  .rails-default-error-page div.dialog > p {\n    margin: 0 0 1em;\n    padding: 1em;\n    background-color: #F7F7F7;\n    border: 1px solid #CCC;\n    border-right-color: #999;\n    border-left-color: #999;\n    border-bottom-color: #999;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    border-top-color: #DADADA;\n    color: #666;\n    box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);\n  }\n  </style>\n</head>\n\n<body class=\"rails-default-error-page\">\n  <!-- This file lives in public/500.html -->\n  <div class=\"dialog\">\n    <div>\n      <h1>We're sorry, but something went wrong.</h1>\n    </div>\n    <p>If you are the application owner check the logs for more information.</p>\n  </div>\n</body>\n</html>\n"
  },
  {
    "path": "bench/public/robots.txt",
    "content": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n"
  },
  {
    "path": "bench/test/channels/application_cable/connection_test.rb",
    "content": "require \"test_helper\"\n\nmodule ApplicationCable\n  class ConnectionTest < ActionCable::Connection::TestCase\n    # test \"connects with cookies\" do\n    #   cookies.signed[:user_id] = 42\n    #\n    #   connect\n    #\n    #   assert_equal connection.user_id, \"42\"\n    # end\n  end\nend\n"
  },
  {
    "path": "bench/test/channels/broadcast_channel_test.rb",
    "content": "require \"test_helper\"\n\nclass BroadcastChannelTest < ActionCable::Channel::TestCase\n  # test \"subscribes\" do\n  #   subscribe\n  #   assert subscription.confirmed?\n  # end\nend\n"
  },
  {
    "path": "bench/test/controllers/.keep",
    "content": ""
  },
  {
    "path": "bench/test/fixtures/files/.keep",
    "content": ""
  },
  {
    "path": "bench/test/fixtures/rooms.yml",
    "content": "# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html\n\none:\n  name: MyString\n\ntwo:\n  name: MyString\n"
  },
  {
    "path": "bench/test/models/.keep",
    "content": ""
  },
  {
    "path": "bench/test/models/room_test.rb",
    "content": "require \"test_helper\"\n\nclass RoomTest < ActiveSupport::TestCase\n  # test \"the truth\" do\n  #   assert true\n  # end\nend\n"
  },
  {
    "path": "bench/test/test_helper.rb",
    "content": "ENV[\"RAILS_ENV\"] ||= \"test\"\nrequire_relative \"../config/environment\"\nrequire \"rails/test_help\"\n\nmodule ActiveSupport\n  class TestCase\n    # Run tests in parallel with specified workers\n    parallelize(workers: :number_of_processors)\n\n    # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.\n    fixtures :all\n\n    # Add more helper methods to be used by all tests here...\n  end\nend\n"
  },
  {
    "path": "bench/vendor/.keep",
    "content": ""
  },
  {
    "path": "bench/vendor/javascript/.keep",
    "content": ""
  },
  {
    "path": "bin/rails",
    "content": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails gems\n# installed from the root of your application.\n\nENGINE_ROOT = File.expand_path(\"..\", __dir__)\nENGINE_PATH = File.expand_path(\"../lib/solid_cable/engine\", __dir__)\nAPP_PATH = File.expand_path(\"../test/dummy/config/application\", __dir__)\n\n# Set up gems listed in the Gemfile.\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\nrequire \"bundler/setup\" if File.exist?(ENV[\"BUNDLE_GEMFILE\"])\n\nrequire \"rails/all\"\nrequire \"rails/engine/commands\"\n"
  },
  {
    "path": "bin/release",
    "content": "#!/usr/bin/env bash\n\nVERSION=$1\n\nif [ -z \"$VERSION\" ]\nthen\n  echo \"Usage: bin/release <version>\"\n  exit 1\nfi\n\nprintf \"# frozen_string_literal: true\\n\\nmodule SolidCable\\n  VERSION = \\\"$VERSION\\\"\\nend\\n\" > ./lib/solid_cable/version.rb\nbundle\ngit add lib/solid_cable/version.rb\ngit commit -m \"Bump version for $VERSION\"\nrake release\n"
  },
  {
    "path": "bin/test",
    "content": "#!/usr/bin/env ruby\n$: << File.expand_path(\"../test\", __dir__)\n\nrequire \"bundler/setup\"\nrequire \"rails/plugin/test\"\n"
  },
  {
    "path": "lib/action_cable/subscription_adapter/solid_cable.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"action_cable/subscription_adapter/base\"\nrequire \"action_cable/subscription_adapter/channel_prefix\"\nrequire \"action_cable/subscription_adapter/subscriber_map\"\nrequire \"concurrent/atomic/semaphore\"\n\nmodule ActionCable\n  module SubscriptionAdapter\n    class SolidCable < ::ActionCable::SubscriptionAdapter::Base\n      prepend ::ActionCable::SubscriptionAdapter::ChannelPrefix\n\n      def initialize(*)\n        super\n        @listener = nil\n      end\n\n      def broadcast(channel, payload)\n        ::SolidCable::Message.broadcast(channel, payload)\n\n        ::SolidCable::TrimJob.perform_now if ::SolidCable.autotrim?\n      end\n\n      def subscribe(channel, callback, success_callback = nil)\n        listener.add_subscriber(channel, callback, success_callback)\n      end\n\n      def unsubscribe(channel, callback)\n        listener.remove_subscriber(channel, callback)\n      end\n\n      delegate :shutdown, to: :listener\n\n      private\n        def listener\n          @listener || @server.mutex.synchronize do\n            @listener ||= Listener.new(@server.event_loop)\n          end\n        end\n\n        class Listener < ::ActionCable::SubscriptionAdapter::SubscriberMap\n          CONNECTION_ERRORS = [ ActiveRecord::ConnectionFailed ]\n          Stop = Class.new(Exception)\n\n          def initialize(event_loop)\n            super()\n\n            @event_loop = event_loop\n\n            # Critical section begins with 0 permits. It can be understood as\n            # being \"normally held\" by the listener thread. It is released\n            # for specific sections of code, rather than acquired.\n            @critical = Concurrent::Semaphore.new(0)\n\n            @reconnect_attempt = 0\n            @last_id = last_message_id\n\n            @thread = Thread.new do\n              Thread.current.name = \"solid_cable_listener\"\n              Thread.current.report_on_exception = true\n\n              begin\n                listen\n              rescue *CONNECTION_ERRORS\n                retry if retry_connecting?\n              end\n            end\n          end\n\n          def listen\n            loop do\n              begin\n                instance = interruptible { Rails.application.executor.run! }\n                with_polling_volume { broadcast_messages }\n              ensure\n                instance.complete! if instance\n              end\n\n              interruptible { sleep ::SolidCable.polling_interval }\n            end\n          rescue Stop\n          ensure\n            @critical.release\n          end\n\n          def interruptible\n            @critical.release\n            yield\n          ensure\n            @critical.acquire\n          end\n\n          def shutdown\n            @critical.acquire\n            # We have the critical permit, and so the listen thread must be\n            # safe to interrupt.\n            thread.raise(Stop)\n            @critical.release\n            thread.join\n          end\n\n          def add_channel(channel, on_success)\n            channels[channel] = last_message_id\n            event_loop.post(&on_success) if on_success\n          end\n\n          def remove_channel(channel)\n            channels.delete(channel)\n          end\n\n          def invoke_callback(*)\n            event_loop.post { super }\n          end\n\n          private\n            attr_reader :event_loop, :thread\n            attr_accessor :last_id, :reconnect_attempt\n\n            def last_message_id\n              ::SolidCable::Message.maximum(:id) || 0\n            end\n\n            def channels\n              @channels ||= Concurrent::Map.new\n            end\n\n            def broadcast_messages\n              current_channels = channels.dup\n\n              ::SolidCable::Message.\n                broadcastable(current_channels.keys, last_id).\n                each do |message|\n                  should_broadcast_message = false\n                  channels.compute_if_present(message.channel) do |channel_last_id|\n                    break if channel_last_id >= message.id\n\n                    should_broadcast_message = true\n                    message.id\n                  end\n\n                  broadcast(message.channel, message.payload) if should_broadcast_message\n                  self.last_id = message.id\n                end\n            end\n\n            def with_polling_volume\n              if ::SolidCable.silence_polling? && ActiveRecord::Base.logger\n                ActiveRecord::Base.logger.silence { yield }\n              else\n                yield\n              end\n            end\n\n            def reconnect_attempts\n              @reconnect_attempts ||= ::SolidCable.reconnect_attempts\n            end\n\n            def retry_connecting?\n              self.reconnect_attempt += 1\n\n              return false if reconnect_attempt > reconnect_attempts.size\n\n              sleep_t = reconnect_attempts[reconnect_attempt - 1]\n\n              sleep(sleep_t) if sleep_t > 0\n\n              true\n            end\n        end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/generators/solid_cable/install/USAGE",
    "content": "Description:\n    Installs solid_cable as the Action Cable adapter\n\nExample:\n    bin/rails generate solid_cable:install\n\n    This will perform the following:\n        Installs solid_cable migrations\n"
  },
  {
    "path": "lib/generators/solid_cable/install/install_generator.rb",
    "content": "# frozen_string_literal: true\n\nclass SolidCable::InstallGenerator < Rails::Generators::Base\n  source_root File.expand_path(\"templates\", __dir__)\n\n  def copy_files\n    template \"db/cable_schema.rb\"\n    template \"config/cable.yml\", force: true\n  end\nend\n"
  },
  {
    "path": "lib/generators/solid_cable/install/templates/config/cable.yml",
    "content": "# Async adapter only works within the same process, so for manually triggering cable updates from a console,\n# and seeing results in the browser, you must do so from the web console (running inside the dev process),\n# not a terminal started via bin/rails console! Add \"console\" to any action or any ERB template view\n# to make the web console appear.\ndevelopment:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: solid_cable\n  connects_to:\n    database:\n      writing: cable\n  polling_interval: 0.1.seconds\n  message_retention: 1.day\n"
  },
  {
    "path": "lib/generators/solid_cable/install/templates/db/cable_schema.rb",
    "content": "ActiveRecord::Schema[7.1].define(version: 1) do\n  create_table \"solid_cable_messages\", force: :cascade do |t|\n    t.binary \"channel\", limit: 1024, null: false\n    t.binary \"payload\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"channel_hash\", limit: 8, null: false\n    t.index [\"channel\"], name: \"index_solid_cable_messages_on_channel\"\n    t.index [\"channel_hash\"], name: \"index_solid_cable_messages_on_channel_hash\"\n    t.index [\"created_at\"], name: \"index_solid_cable_messages_on_created_at\"\n  end\nend\n"
  },
  {
    "path": "lib/generators/solid_cable/update/USAGE",
    "content": "Description:\n    Updates Solid Cable migrations\n\nExample:\n    bin/rails generate solid_cable:update\n\n    This will perform the following:\n        Installs new Solid Cable migrations\n"
  },
  {
    "path": "lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb",
    "content": "# frozen_string_literal: true\n\nclass CreateCompactChannel < ActiveRecord::Migration[7.2]\n  def up\n    change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false\n    add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true\n    add_index :solid_cable_messages, :channel_hash, if_not_exists: true\n    change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false\n\n    SolidCable::Message.find_each do |msg|\n      msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))\n    end\n  end\n\n  def down\n    change_column :solid_cable_messages, :channel, :text\n    remove_column :solid_cable_messages, :channel_hash, if_exists: true\n    change_column :solid_cable_messages, :payload, :text\n  end\nend\n"
  },
  {
    "path": "lib/generators/solid_cable/update/update_generator.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"rails/generators\"\nrequire \"rails/generators/active_record\"\n\nclass SolidCable::UpdateGenerator < Rails::Generators::Base\n  include ActiveRecord::Generators::Migration\n\n  source_root File.expand_path(\"templates\", __dir__)\n\n  def copy_files\n    migration_template \"db/migrate/create_compact_channel.rb\",\n                       \"db/cable_migrate/create_compact_channel.rb\"\n  end\nend\n"
  },
  {
    "path": "lib/solid_cable/engine.rb",
    "content": "# frozen_string_literal: true\n\nmodule SolidCable\n  class Engine < ::Rails::Engine\n    isolate_namespace SolidCable\n  end\nend\n"
  },
  {
    "path": "lib/solid_cable/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule SolidCable\n  VERSION = \"3.0.12\"\nend\n"
  },
  {
    "path": "lib/solid_cable.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"solid_cable/version\"\nrequire \"solid_cable/engine\"\nrequire \"action_cable/subscription_adapter/solid_cable\"\n\nmodule SolidCable\n  class << self\n    def connects_to\n      cable_config.connects_to.to_h.deep_transform_values(&:to_sym)\n    end\n\n    def silence_polling?\n      cable_config.silence_polling != false\n    end\n\n    def polling_interval\n      parse_duration(cable_config.polling_interval, default: 0.1.seconds)\n    end\n\n    def message_retention\n      parse_duration(cable_config.message_retention, default: 1.day)\n    end\n\n    def autotrim?\n      cable_config.autotrim != false\n    end\n\n    def trim_batch_size\n      if (size = cable_config.trim_batch_size.to_i) < 2\n        100\n      else\n        size\n      end\n    end\n\n    def use_skip_locked\n      cable_config.use_skip_locked != false\n    end\n\n    # For every write that we do, we attempt to delete trim_chance times as\n    # many records. This ensures there is downward pressure on the cache size\n    # while there is valid data to delete. Read this as 'every time the trim job\n    # runs theres a trim_multiplier chance this trims'. Adjust number to make it\n    # more or less likely to trim. Only works like this if trim_batch_size is\n    # 100\n    def trim_chance\n      2\n    end\n\n    def reconnect_attempts\n      attempts = cable_config.fetch(:reconnect_attempts, 1)\n      attempts = Array.new(attempts, 0) if attempts.is_a?(Integer)\n      attempts\n    end\n\n    private\n      def cable_config\n        Rails.application.config_for(\"cable\")\n      end\n\n      def parse_duration(duration, default:)\n        if duration.present?\n          *amount, units = duration.to_s.split(\".\")\n          amount.join(\".\").to_f.public_send(units)\n        else\n          default\n        end\n      end\n  end\nend\n"
  },
  {
    "path": "lib/tasks/solid_cable_tasks.rake",
    "content": "# frozen_string_literal: true\n\ndesc \"Copy over the schema and set cable adapter for Solid Cable\"\nnamespace :solid_cable do\n  task :install do\n    Rails::Command.invoke :generate, [ \"solid_cable:install\" ]\n  end\n\n  task :update do\n    Rails::Command.invoke :generate, [ \"solid_cable:update\" ]\n  end\nend\n"
  },
  {
    "path": "solid_cable.gemspec",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"lib/solid_cable/version\"\n\nGem::Specification.new do |spec|\n  spec.name        = \"solid_cable\"\n  spec.version     = SolidCable::VERSION\n  spec.authors     = [ \"Nick Pezza\" ]\n  spec.email       = [ \"pezza@hey.com\" ]\n  spec.homepage    = \"https://github.com/rails/solid_cable\"\n  spec.summary     = \"Database-backed Action Cable backend.\"\n  spec.description = \"Database-backed Action Cable backend.\"\n  spec.license     = \"MIT\"\n\n  spec.metadata[\"homepage_uri\"] = spec.homepage\n  spec.metadata[\"source_code_uri\"] = spec.homepage\n  spec.metadata[\"rubygems_mfa_required\"] = \"true\"\n\n  spec.files = Dir.chdir(File.expand_path(__dir__)) do\n    Dir[\"{app,config,db,lib}/**/*\", \"MIT-LICENSE\", \"Rakefile\", \"README.md\"]\n  end\n\n  rails_version = \">= 7.2\"\n  spec.required_ruby_version = \">= 3.2.0\"\n  spec.add_dependency \"activerecord\", rails_version\n  spec.add_dependency \"activejob\", rails_version\n  spec.add_dependency \"actioncable\", rails_version\n  spec.add_dependency \"railties\", rails_version\n\n  spec.add_development_dependency \"minitest\", \"~> 5.0\"\nend\n"
  },
  {
    "path": "test/config_stubs.rb",
    "content": "# frozen_string_literal: true\n\nmodule ConfigStubs\n  extend ActiveSupport::Concern\n\n  class ConfigStub\n    def initialize(**)\n      @config = ActiveSupport::OrderedOptions.new.\n                update({ adapter: :test }.merge(**))\n    end\n\n    def config_for(_file)\n      @config\n    end\n\n    def executor\n      @executor ||= ExectorStub.new\n    end\n\n    class ExectorStub\n      def run!\n      end\n    end\n  end\n\n  def with_cable_config(**)\n    Rails.stub(:application, ConfigStub.new(**)) { yield }\n  end\nend\n"
  },
  {
    "path": "test/dummy/Rakefile",
    "content": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.\n\nrequire_relative \"config/application\"\n\nRails.application.load_tasks\n"
  },
  {
    "path": "test/dummy/app/controllers/application_controller.rb",
    "content": "class ApplicationController < ActionController::Base\n  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.\n  allow_browser versions: :modern\nend\n"
  },
  {
    "path": "test/dummy/app/controllers/concerns/.keep",
    "content": ""
  },
  {
    "path": "test/dummy/app/helpers/application_helper.rb",
    "content": "module ApplicationHelper\nend\n"
  },
  {
    "path": "test/dummy/app/jobs/application_job.rb",
    "content": "class ApplicationJob < ActiveJob::Base\n  # Automatically retry jobs that encountered a deadlock\n  # retry_on ActiveRecord::Deadlocked\n\n  # Most jobs are safe to ignore if the underlying records are no longer available\n  # discard_on ActiveJob::DeserializationError\nend\n"
  },
  {
    "path": "test/dummy/app/models/application_record.rb",
    "content": "class ApplicationRecord < ActiveRecord::Base\n  primary_abstract_class\nend\n"
  },
  {
    "path": "test/dummy/app/models/concerns/.keep",
    "content": ""
  },
  {
    "path": "test/dummy/app/views/layouts/application.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title><%= content_for(:title) || \"Dummy\" %></title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"application-name\" content=\"Dummy\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <%= csrf_meta_tags %>\n    <%= csp_meta_tag %>\n\n    <%= yield :head %>\n\n    <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>\n    <%#= tag.link rel: \"manifest\", href: pwa_manifest_path(format: :json) %>\n\n    <link rel=\"icon\" href=\"/icon.png\" type=\"image/png\">\n    <link rel=\"icon\" href=\"/icon.svg\" type=\"image/svg+xml\">\n    <link rel=\"apple-touch-icon\" href=\"/icon.png\">\n\n    <%# Includes all stylesheet files in app/assets/stylesheets %>\n    <%= stylesheet_link_tag :app %>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "test/dummy/app/views/layouts/mailer.html.erb",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n    <style>\n      /* Email styles need to be inline */\n    </style>\n  </head>\n\n  <body>\n    <%= yield %>\n  </body>\n</html>\n"
  },
  {
    "path": "test/dummy/app/views/layouts/mailer.text.erb",
    "content": "<%= yield %>\n"
  },
  {
    "path": "test/dummy/app/views/pwa/manifest.json.erb",
    "content": "{\n  \"name\": \"Dummy\",\n  \"icons\": [\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\"\n    },\n    {\n      \"src\": \"/icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"512x512\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"start_url\": \"/\",\n  \"display\": \"standalone\",\n  \"scope\": \"/\",\n  \"description\": \"Dummy.\",\n  \"theme_color\": \"red\",\n  \"background_color\": \"red\"\n}\n"
  },
  {
    "path": "test/dummy/app/views/pwa/service-worker.js",
    "content": "// Add a service worker for processing Web Push notifications:\n//\n// self.addEventListener(\"push\", async (event) => {\n//   const { title, options } = await event.data.json()\n//   event.waitUntil(self.registration.showNotification(title, options))\n// })\n//\n// self.addEventListener(\"notificationclick\", function(event) {\n//   event.notification.close()\n//   event.waitUntil(\n//     clients.matchAll({ type: \"window\" }).then((clientList) => {\n//       for (let i = 0; i < clientList.length; i++) {\n//         let client = clientList[i]\n//         let clientPath = (new URL(client.url)).pathname\n//\n//         if (clientPath == event.notification.data.path && \"focus\" in client) {\n//           return client.focus()\n//         }\n//       }\n//\n//       if (clients.openWindow) {\n//         return clients.openWindow(event.notification.data.path)\n//       }\n//     })\n//   )\n// })\n"
  },
  {
    "path": "test/dummy/bin/ci",
    "content": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"active_support/continuous_integration\"\n\nCI = ActiveSupport::ContinuousIntegration\nrequire_relative \"../config/ci.rb\"\n"
  },
  {
    "path": "test/dummy/bin/dev",
    "content": "#!/usr/bin/env ruby\nexec \"./bin/rails\", \"server\", *ARGV\n"
  },
  {
    "path": "test/dummy/bin/rails",
    "content": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequire \"rails/commands\"\n"
  },
  {
    "path": "test/dummy/bin/rake",
    "content": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
  },
  {
    "path": "test/dummy/bin/setup",
    "content": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\n\ndef system!(*args)\n  system(*args, exception: true)\nend\n\nFileUtils.chdir APP_ROOT do\n  # This script is a way to set up or update your development environment automatically.\n  # This script is idempotent, so that you can run it at any time and get an expectable outcome.\n  # Add necessary setup steps to this file.\n\n  puts \"== Installing dependencies ==\"\n  system(\"bundle check\") || system!(\"bundle install\")\n\n  # puts \"\\n== Copying sample files ==\"\n  # unless File.exist?(\"config/database.yml\")\n  #   FileUtils.cp \"config/database.yml.sample\", \"config/database.yml\"\n  # end\n\n  puts \"\\n== Preparing database ==\"\n  system! \"bin/rails db:prepare\"\n  system! \"bin/rails db:reset\" if ARGV.include?(\"--reset\")\n\n  puts \"\\n== Removing old logs and tempfiles ==\"\n  system! \"bin/rails log:clear tmp:clear\"\n\n  unless ARGV.include?(\"--skip-server\")\n    puts \"\\n== Starting development server ==\"\n    STDOUT.flush # flush the output before exec(2) so that it displays\n    exec \"bin/dev\"\n  end\nend\n"
  },
  {
    "path": "test/dummy/config/application.rb",
    "content": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited to :test, :development, or :production.\nBundler.require(*Rails.groups)\n\nmodule Dummy\n  class Application < Rails::Application\n    config.load_defaults Rails::VERSION::STRING.to_f\n\n    # For compatibility with applications that use this config\n    config.action_controller.include_all_helpers = false\n\n    # Please, add to the `ignore` list any other `lib` subdirectories that do\n    # not contain `.rb` files, or that should not be reloaded or eager loaded.\n    # Common ones are `templates`, `generators`, or `middleware`, for example.\n    config.autoload_lib(ignore: %w[assets tasks])\n\n    # Configuration for the application, engines, and railties goes here.\n    #\n    # These settings can be overridden in specific environments using the files\n    # in config/environments, which are processed later.\n    #\n    # config.time_zone = \"Central Time (US & Canada)\"\n    # config.eager_load_paths << Rails.root.join(\"extras\")\n  end\nend\n"
  },
  {
    "path": "test/dummy/config/boot.rb",
    "content": "# Set up gems listed in the Gemfile.\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../../../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" if File.exist?(ENV[\"BUNDLE_GEMFILE\"])\n$LOAD_PATH.unshift File.expand_path(\"../../../lib\", __dir__)\n"
  },
  {
    "path": "test/dummy/config/cable.yml",
    "content": "development:\n  adapter: async\n\ntest:\n  adapter: test\n\nproduction:\n  adapter: redis\n  url: <%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>\n  channel_prefix: dummy_production\n"
  },
  {
    "path": "test/dummy/config/ci.rb",
    "content": "# Run using bin/ci\n\nCI.run do\n  step \"Setup\", \"bin/setup --skip-server\"\n\n\n  step \"Tests: Rails\", \"bin/rails test\"\n  step \"Tests: System\", \"bin/rails test:system\"\n  step \"Tests: Seeds\", \"env RAILS_ENV=test bin/rails db:seed:replant\"\n\n  # Optional: set a green GitHub commit status to unblock PR merge.\n  # Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.\n  # if success?\n  #   step \"Signoff: All systems go. Ready for merge and deploy.\", \"gh signoff\"\n  # else\n  #   failure \"Signoff: CI failed. Do not merge or deploy.\", \"Fix the issues and try again.\"\n  # end\nend\n"
  },
  {
    "path": "test/dummy/config/database.yml",
    "content": "<% def database_name_from(name); [\"mysql\", \"postgres\"].exclude?(ENV[\"TARGET_DB\"]) ? \"db/#{name}.sqlite3\" : name; end %>\n\n<% if ENV[\"TARGET_DB\"] == \"mysql\" %>\ndefault: &default\n  adapter: trilogy\n  username: root\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n  host: \"127.0.0.1\"\n  port: 33060\n<% elsif ENV[\"TARGET_DB\"] == \"postgres\" %>\ndefault: &default\n  adapter: postgresql\n  encoding: unicode\n  username: postgres\n  pool: 5\n  host: \"127.0.0.1\"\n  port: 55432\n  gssencmode: disable # https://github.com/ged/ruby-pg/issues/311\n<% else %>\ndefault: &default\n  adapter: sqlite3\n  pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 50 } %>\n  timeout: 100\n<% end %>\n\ndevelopment:\n  <<: *default\n  database: <%= database_name_from(\"solid_cable_development\") %>\n\ntest:\n  <<: *default\n  pool: 20\n  database: <%= database_name_from(\"solid_cable_test\") %>\n"
  },
  {
    "path": "test/dummy/config/environment.rb",
    "content": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.initialize!\n"
  },
  {
    "path": "test/dummy/config/environments/development.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Make code changes take effect immediately without server restart.\n  config.enable_reloading = true\n\n  # Do not eager load code on boot.\n  config.eager_load = false\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n\n  # Enable server timing.\n  config.server_timing = true\n\n  # Enable/disable Action Controller caching. By default Action Controller caching is disabled.\n  # Run rails dev:cache to toggle Action Controller caching.\n  if Rails.root.join(\"tmp/caching-dev.txt\").exist?\n    config.action_controller.perform_caching = true\n    config.action_controller.enable_fragment_cache_logging = true\n    config.public_file_server.headers = { \"cache-control\" => \"public, max-age=#{2.days.to_i}\" }\n  else\n    config.action_controller.perform_caching = false\n  end\n\n  # Change to :null_store to avoid any caching.\n  config.cache_store = :memory_store\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  # config.active_storage.service = :local\n\n  # Don't care if the mailer can't send.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Make template changes take effect immediately.\n  # config.action_mailer.perform_caching = false\n\n  # Set localhost to be used by links generated in mailer templates.\n  # config.action_mailer.default_url_options = { host: \"localhost\", port: 3000 }\n\n  # Print deprecation notices to the Rails logger.\n  config.active_support.deprecation = :log\n\n  # Raise an error on page load if there are pending migrations.\n  config.active_record.migration_error = :page_load\n\n  # Highlight code that triggered database queries in logs.\n  config.active_record.verbose_query_logs = true\n\n  # Append comments with runtime information tags to SQL queries in logs.\n  config.active_record.query_log_tags_enabled = true\n\n  # Highlight code that enqueued background job in logs.\n  config.active_job.verbose_enqueue_logs = true\n\n  # Highlight code that triggered redirect in logs.\n  config.action_dispatch.verbose_redirect_logs = true\n\n  # Suppress logger output for asset requests.\n  # config.assets.quiet = true\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Uncomment if you wish to allow Action Cable access from any origin.\n  # config.action_cable.disable_request_forgery_protection = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n"
  },
  {
    "path": "test/dummy/config/environments/production.rb",
    "content": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # Code is not reloaded between requests.\n  config.enable_reloading = false\n\n  # Eager load code on boot for better performance and memory savings (ignored by Rake tasks).\n  config.eager_load = true\n\n  # Full error reports are disabled.\n  config.consider_all_requests_local = false\n\n  # Turn on fragment caching in view templates.\n  config.action_controller.perform_caching = true\n\n  # Cache assets for far-future expiry since they are all digest stamped.\n  config.public_file_server.headers = { \"cache-control\" => \"public, max-age=#{1.year.to_i}\" }\n\n  # Enable serving of images, stylesheets, and JavaScripts from an asset server.\n  # config.asset_host = \"http://assets.example.com\"\n\n  # Store uploaded files on the local file system (see config/storage.yml for options).\n  # config.active_storage.service = :local\n\n  # Assume all access to the app is happening through a SSL-terminating reverse proxy.\n  config.assume_ssl = true\n\n  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.\n  config.force_ssl = true\n\n  # Skip http-to-https redirect for the default health check endpoint.\n  # config.ssl_options = { redirect: { exclude: ->(request) { request.path == \"/up\" } } }\n\n  # Log to STDOUT with the current request id as a default log tag.\n  config.log_tags = [ :request_id ]\n  config.logger   = ActiveSupport::TaggedLogging.logger(STDOUT)\n\n  # Change to \"debug\" to log everything (including potentially personally-identifiable information!).\n  config.log_level = ENV.fetch(\"RAILS_LOG_LEVEL\", \"info\")\n\n  # Prevent health checks from clogging up the logs.\n  config.silence_healthcheck_path = \"/up\"\n\n  # Don't log any deprecations.\n  config.active_support.report_deprecations = false\n\n  # Replace the default in-process memory cache store with a durable alternative.\n  # config.cache_store = :mem_cache_store\n\n  # Replace the default in-process and non-durable queuing backend for Active Job.\n  # config.active_job.queue_adapter = :resque\n\n  # Ignore bad email addresses and do not raise email delivery errors.\n  # Set this to true and configure the email server for immediate delivery to raise delivery errors.\n  # config.action_mailer.raise_delivery_errors = false\n\n  # Set host to be used by links generated in mailer templates.\n  config.action_mailer.default_url_options = { host: \"example.com\" }\n\n  # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.\n  # config.action_mailer.smtp_settings = {\n  #   user_name: Rails.application.credentials.dig(:smtp, :user_name),\n  #   password: Rails.application.credentials.dig(:smtp, :password),\n  #   address: \"smtp.example.com\",\n  #   port: 587,\n  #   authentication: :plain\n  # }\n\n  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to\n  # the I18n.default_locale when a translation cannot be found).\n  config.i18n.fallbacks = true\n\n  # Do not dump schema after migrations.\n  config.active_record.dump_schema_after_migration = false\n\n  # Only use :id for inspections in production.\n  config.active_record.attributes_for_inspect = [ :id ]\n\n  # Enable DNS rebinding protection and other `Host` header attacks.\n  # config.hosts = [\n  #   \"example.com\",     # Allow requests from example.com\n  #   /.*\\.example\\.com/ # Allow requests from subdomains like `www.example.com`\n  # ]\n  #\n  # Skip DNS rebinding protection for the default health check endpoint.\n  # config.host_authorization = { exclude: ->(request) { request.path == \"/up\" } }\nend\n"
  },
  {
    "path": "test/dummy/config/environments/test.rb",
    "content": "# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherwise. Remember that\n# your test database is \"scratch space\" for the test suite and is wiped\n# and recreated between test runs. Don't rely on the data there!\n\nRails.application.configure do\n  # Settings specified here will take precedence over those in config/application.rb.\n\n  # While tests run files are not watched, reloading is not necessary.\n  config.enable_reloading = false\n\n  # Eager loading loads your entire application. When running a single test locally,\n  # this is usually not necessary, and can slow down your test suite. However, it's\n  # recommended that you enable it in continuous integration systems to ensure eager\n  # loading is working properly before deploying your code.\n  config.eager_load = ENV[\"CI\"].present?\n\n  # Configure public file server for tests with cache-control for performance.\n  config.public_file_server.headers = { \"cache-control\" => \"public, max-age=3600\" }\n\n  # Show full error reports.\n  config.consider_all_requests_local = true\n  config.cache_store = :null_store\n\n  # Render exception templates for rescuable exceptions and raise for other exceptions.\n  config.action_dispatch.show_exceptions = :rescuable\n\n  # Disable request forgery protection in test environment.\n  config.action_controller.allow_forgery_protection = false\n\n  # Store uploaded files on the local file system in a temporary directory.\n  # config.active_storage.service = :test\n\n  # Tell Action Mailer not to deliver emails to the real world.\n  # The :test delivery method accumulates sent emails in the\n  # ActionMailer::Base.deliveries array.\n  # config.action_mailer.delivery_method = :test\n\n  # Set host to be used by links generated in mailer templates.\n  # config.action_mailer.default_url_options = { host: \"example.com\" }\n\n  # Print deprecation notices to the stderr.\n  config.active_support.deprecation = :stderr\n\n  # Raises error for missing translations.\n  # config.i18n.raise_on_missing_translations = true\n\n  # Annotate rendered view with file names.\n  # config.action_view.annotate_rendered_view_with_filenames = true\n\n  # Raise error when a before_action's only/except options reference missing actions.\n  config.action_controller.raise_on_missing_callback_actions = true\nend\n"
  },
  {
    "path": "test/dummy/config/initializers/assets.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire all your assets.\n# Rails.application.config.assets.version = \"1.0\"\n\n# Add additional assets to the asset load path.\n# Rails.application.config.assets.paths << Emoji.images_path\n"
  },
  {
    "path": "test/dummy/config/initializers/content_security_policy.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See the Securing Rails Applications Guide for more information:\n# https://guides.rubyonrails.org/security.html#content-security-policy-header\n\n# Rails.application.configure do\n#   config.content_security_policy do |policy|\n#     policy.default_src :self, :https\n#     policy.font_src    :self, :https, :data\n#     policy.img_src     :self, :https, :data\n#     policy.object_src  :none\n#     policy.script_src  :self, :https\n#     policy.style_src   :self, :https\n#     # Specify URI for violation reports\n#     # policy.report_uri \"/csp-violation-report-endpoint\"\n#   end\n#\n#   # Generate session nonces for permitted importmap, inline scripts, and inline styles.\n#   config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }\n#   config.content_security_policy_nonce_directives = %w(script-src style-src)\n#\n#   # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`\n#   # if the corresponding directives are specified in `content_security_policy_nonce_directives`.\n#   # config.content_security_policy_nonce_auto = true\n#\n#   # Report violations without enforcing the policy.\n#   # config.content_security_policy_report_only = true\n# end\n"
  },
  {
    "path": "test/dummy/config/initializers/filter_parameter_logging.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.\n# Use this to limit dissemination of sensitive information.\n# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.\nRails.application.config.filter_parameters += [\n  :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc\n]\n"
  },
  {
    "path": "test/dummy/config/initializers/inflections.rb",
    "content": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Inflections\n# are locale specific, and you may define rules for as many different\n# locales as you wish. All of these examples are active by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.plural /^(ox)$/i, \"\\\\1en\"\n#   inflect.singular /^(ox)en/i, \"\\\\1\"\n#   inflect.irregular \"person\", \"people\"\n#   inflect.uncountable %w( fish sheep )\n# end\n\n# These inflection rules are supported but not enabled by default:\n# ActiveSupport::Inflector.inflections(:en) do |inflect|\n#   inflect.acronym \"RESTful\"\n# end\n"
  },
  {
    "path": "test/dummy/config/locales/en.yml",
    "content": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If you want to use locales other than\n# English, add the necessary files in this directory.\n#\n# To use the locales, use `I18n.t`:\n#\n#     I18n.t \"hello\"\n#\n# In views, this is aliased to just `t`:\n#\n#     <%= t(\"hello\") %>\n#\n# To use a different locale, set it with `I18n.locale`:\n#\n#     I18n.locale = :es\n#\n# This would use the information in config/locales/es.yml.\n#\n# To learn more about the API, please read the Rails Internationalization guide\n# at https://guides.rubyonrails.org/i18n.html.\n#\n# Be aware that YAML interprets the following case-insensitive strings as\n# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings\n# must be quoted to be interpreted as strings. For example:\n#\n#     en:\n#       \"yes\": yup\n#       enabled: \"ON\"\n\nen:\n  hello: \"Hello world\"\n"
  },
  {
    "path": "test/dummy/config/puma.rb",
    "content": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's configuration DSL. For more information\n# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.\n#\n# Puma starts a configurable number of processes (workers) and each process\n# serves each request in a thread from an internal thread pool.\n#\n# You can control the number of workers using ENV[\"WEB_CONCURRENCY\"]. You\n# should only set this value when you want to run 2 or more workers. The\n# default is already 1. You can set it to `auto` to automatically start a worker\n# for each available processor.\n#\n# The ideal number of threads per worker depends both on how much time the\n# application spends waiting for IO operations and on how much you wish to\n# prioritize throughput over latency.\n#\n# As a rule of thumb, increasing the number of threads will increase how much\n# traffic a given process can handle (throughput), but due to CRuby's\n# Global VM Lock (GVL) it has diminishing returns and will degrade the\n# response time (latency) of the application.\n#\n# The default is set to 3 threads as it's deemed a decent compromise between\n# throughput and latency for the average Rails application.\n#\n# Any libraries that use a connection pool or another resource pool should\n# be configured to provide at least as many connections as the number of\n# threads. This includes Active Record's `pool` parameter in `database.yml`.\nthreads_count = ENV.fetch(\"RAILS_MAX_THREADS\", 3)\nthreads threads_count, threads_count\n\n# Specifies the `port` that Puma will listen on to receive requests; default is 3000.\nport ENV.fetch(\"PORT\", 3000)\n\n# Allow puma to be restarted by `bin/rails restart` command.\nplugin :tmp_restart\n\n# Specify the PID file. Defaults to tmp/pids/server.pid in development.\n# In other environments, only set the PID file if requested.\npidfile ENV[\"PIDFILE\"] if ENV[\"PIDFILE\"]\n"
  },
  {
    "path": "test/dummy/config/routes.rb",
    "content": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html\n\n  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.\n  # Can be used by load balancers and uptime monitors to verify that the app is live.\n  get \"up\" => \"rails/health#show\", as: :rails_health_check\n\n  # Defines the root path route (\"/\")\n  # root \"posts#index\"\nend\n"
  },
  {
    "path": "test/dummy/config/storage.yml",
    "content": "test:\n  service: Disk\n  root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n  service: Disk\n  root: <%= Rails.root.join(\"storage\") %>\n\n# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)\n# amazon:\n#   service: S3\n#   access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>\n#   secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>\n#   region: us-east-1\n#   bucket: your_own_bucket-<%= Rails.env %>\n\n# Remember not to checkin your GCS keyfile to a repository\n# google:\n#   service: GCS\n#   project: your_project\n#   credentials: <%= Rails.root.join(\"path/to/gcs.keyfile\") %>\n#   bucket: your_own_bucket-<%= Rails.env %>\n\n# mirror:\n#   service: Mirror\n#   primary: local\n#   mirrors: [ amazon, google, microsoft ]\n"
  },
  {
    "path": "test/dummy/config.ru",
    "content": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.application\nRails.application.load_server\n"
  },
  {
    "path": "test/dummy/db/schema.rb",
    "content": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# This file is the source Rails uses to define your schema when running `bin/rails\n# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to\n# be faster and is potentially less error prone than running all of your\n# migrations from scratch. Old migrations may fail to apply correctly if those\n# migrations use external dependencies or application code.\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema[7.2].define(version: 2024_09_12_130854) do\n  create_table \"solid_cable_messages\", force: :cascade do |t|\n    t.binary \"channel\", limit: 1024, null: false\n    t.binary \"payload\", limit: 536870912, null: false\n    t.datetime \"created_at\", null: false\n    t.integer \"channel_hash\", limit: 8, null: false\n    t.index [\"channel\"], name: \"index_solid_cable_messages_on_channel\"\n    t.index [\"channel_hash\"], name: \"index_solid_cable_messages_on_channel_hash\"\n    t.index [\"created_at\"], name: \"index_solid_cable_messages_on_created_at\"\n  end\nend\n"
  },
  {
    "path": "test/dummy/log/.keep",
    "content": ""
  },
  {
    "path": "test/dummy/public/400.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The server cannot process the request due to a client error (400 Bad Request)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100dvh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      #error-description {\n        fill: #d30001;\n      }\n\n      #error-id {\n        fill: #f0eff0;\n      }\n\n      @media (prefers-color-scheme: dark) {\n        body {\n          background: #101010;\n          color: #e0e0e0;\n        }\n\n        #error-description {\n          fill: #FF6161;\n        }\n\n        #error-id {\n          fill: #2c2c2c;\n        }\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/400.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z\" id=\"error-id\"/><path d=\"m123.606 85.4445c3.212 1.0523 5.538 4.2089 5.538 8.0301 0 6.1472-4.209 9.5254-11.298 9.5254h-15.617v-34.0033h14.565c7.089 0 11.353 3.1566 11.353 9.2484 0 3.6551-2.049 6.3134-4.541 7.1994zm-12.904-2.9905h5.095c2.603 0 3.988-.9968 3.988-3.1013 0-2.1044-1.385-3.0459-3.988-3.0459h-5.095zm0 6.6456v6.5902h5.981c2.492 0 3.877-1.3291 3.877-3.2674 0-2.049-1.385-3.3228-3.877-3.3228zm43.786 13.9004h-8.362v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.083-2.769-9.083-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.483 1.3845v-1.606c0-1.606-.942-2.9905-3.046-2.9905-1.606 0-2.548.7199-2.935 1.8275h-8.197c.72-4.8181 4.985-8.6393 11.409-8.6393 7.088 0 11.131 3.7659 11.131 10.2453zm-8.362-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm27.996 6.9779v-1.994c-1.163 1.329-3.599 2.548-6.147 2.548-7.199 0-11.131-5.8151-11.131-13.0145s3.932-13.0143 11.131-13.0143c2.548 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.664-1.3291-2.159-2.326-3.821-2.326-2.99 0-4.763 2.4368-4.763 5.6488s1.773 5.5934 4.763 5.5934c1.717 0 3.157-.9415 3.821-2.326zm35.471-2.049h-3.101v11.2421h-8.806v-34.0033h15.285c7.31 0 12.35 4.1535 12.35 11.5744 0 5.1503-2.603 8.6947-6.757 10.2453l7.975 12.1836h-9.858zm-3.101-15.2849v8.1962h5.538c3.156 0 4.596-1.606 4.596-4.0981s-1.44-4.0981-4.596-4.0981zm36.957 17.8323h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm30.98 27.5234v-10.799c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.9259-11.132-13.0145 0-7.144 3.932-13.0143 11.132-13.0143 2.547 0 4.984 1.2184 6.147 2.5475v-1.9937h8.695v33.726zm0-17.9981v-6.5902c-.665-1.3291-2.105-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.661 0 3.156-.9415 3.821-2.326zm36.789-15.7279v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.996 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm19.084 16.2263h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.963 5.095 11.963 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm13.428 11.0206h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.762-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm27.538-.8861v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.993-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.871 0-9.193-2.769-9.193-9.0819z\" id=\"error-description\"/></svg>\n      </header>\n      <article>\n        <p><strong>The server cannot process the request due to a client error.</strong> Please check the request and try again. If you're the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "test/dummy/public/404.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The page you were looking for doesn't exist (404 Not found)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100dvh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      #error-description {\n        fill: #d30001;\n      }\n\n      #error-id {\n        fill: #f0eff0;\n      }\n\n      @media (prefers-color-scheme: dark) {\n        body {\n          background: #101010;\n          color: #e0e0e0;\n        }\n\n        #error-description {\n          fill: #FF6161;\n        }\n\n        #error-id {\n          fill: #2c2c2c;\n        }\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/404.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm165.328-35.41581-45.689 100.02991h26.224v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.184v-31.901l50.285-103.27391z\" id=\"error-id\"/><path d=\"m157.758 68.9967v34.0033h-7.199l-14.233-19.8814v19.8814h-8.584v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm13.184 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm37.027 8.5839h-8.806v-34.0033h23.924v7.6978h-15.118v6.7564h13.9v7.5316h-13.9zm41.876-12.4605c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm35.337-12.4605v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.997 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm4.076 24.921v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.156.9969-3.6 2.7136v15.0634zm44.113 0v-1.994c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.8151-11.132-13.0145s3.932-13.0143 11.132-13.0143c2.547 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.665-1.3291-2.16-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.717 0 3.156-.9415 3.821-2.326z\" id=\"error-description\"/></svg>\n      </header>\n      <article>\n        <p><strong>The page you were looking for doesn't exist.</strong> You may have mistyped the address or the page may have moved. If you're the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "test/dummy/public/406-unsupported-browser.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>Your browser is not supported (406 Not Acceptable)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100dvh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      #error-description {\n        fill: #d30001;\n      }\n\n      #error-id {\n        fill: #f0eff0;\n      }\n\n      @media (prefers-color-scheme: dark) {\n        body {\n          background: #101010;\n          color: #e0e0e0;\n        }\n\n        #error-description {\n          fill: #FF6161;\n        }\n\n        #error-id {\n          fill: #2c2c2c;\n        }\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/406-unsupported-browser.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm202.906 9.7326h-41.093c-2.433-7.2994-7.84-12.4361-17.302-12.4361-16.221 0-25.413 17.5728-25.954 34.8752v1.3517c5.137-7.0291 16.221-12.4361 30.82-12.4361 33.524 0 54.881 24.0612 54.881 53.7998 0 33.253-23.791 58.396-61.64 58.396-21.628 0-39.741-10.003-50.825-27.576-9.733-14.599-13.788-32.442-13.788-54.3406 0-51.9072 24.331-89.485807 66.236-89.485807 32.712 0 53.258 18.654107 58.665 47.851907zm-82.727 66.2355c0 13.247 9.463 22.439 22.71 22.439 12.977 0 22.439-9.192 22.439-22.439 0-13.517-9.462-22.7091-22.439-22.7091-13.247 0-22.71 9.1921-22.71 22.7091z\" id=\"error-id\"/><path d=\"m100.761 68.9967v34.0033h-7.1991l-14.2326-19.8814v19.8814h-8.5839v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm13.185 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.02-25.4194h9.083l12.958 34.0033h-9.027l-2.436-6.5902h-12.35l-2.381 6.5902h-8.806zm4.431 10.5222-3.489 9.5807h6.978zm17.44 11.0206c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm25.676 0c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm42.013 3.7658h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm23.4 16.7244v10.799h-8.694v-33.726h8.694v1.9937c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8703 11.131 13.0143 0 7.0886-3.932 13.0145-11.131 13.0145-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.16 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.717 0-3.157.9969-3.822 2.326zm21.892 7.1994v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.458 8.5839h-8.363v-1.274c-.83.831-3.322 1.717-5.981 1.717-4.928 0-9.082-2.769-9.082-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.045-2.9905-1.606 0-2.548.7199-2.936 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.553-1.0522-2.049-1.7167-3.655-1.7167-1.716 0-3.433.7199-3.433 2.3813 0 1.7168 1.717 2.4367 3.433 2.4367 1.606 0 3.102-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.694v-35.997h8.694v13.0697c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8149 11.131 13.0143s-3.932 13.0145-11.131 13.0145c-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.662 0-3.157.9969-3.822 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997z\" id=\"error-description\"/></svg>\n      </header>\n      <article>\n        <p><strong>Your browser is not supported.</strong><br> Please upgrade your browser to continue.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "test/dummy/public/422.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>The change you wanted was rejected (422 Unprocessable Entity)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100dvh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      #error-description {\n        fill: #d30001;\n      }\n\n      #error-id {\n        fill: #f0eff0;\n      }\n\n      @media (prefers-color-scheme: dark) {\n        body {\n          background: #101010;\n          color: #e0e0e0;\n        }\n\n        #error-description {\n          fill: #FF6161;\n        }\n\n        #error-id {\n          fill: #2c2c2c;\n        }\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/422.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm130.453 51.63681c0-8.9215-6.218-15.4099-15.681-15.4099-10.273 0-15.95 7.5698-16.491 16.4913h-44.608c3.244-30.8199 25.683-55.421707 61.099-55.421707 36.498 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.869 36.7676-31.901 52.7186l-27.305 27.035h59.747v37.308h-120.306v-27.846l57.044-56.7736c11.084-11.8954 18.925-20.0059 18.925-29.7385zm140.455 0c0-8.9215-6.218-15.4099-15.68-15.4099-10.274 0-15.951 7.5698-16.492 16.4913h-44.608c3.245-30.8199 25.684-55.421707 61.1-55.421707 36.497 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.87 36.7676-31.902 52.7186l-27.305 27.035h59.747v37.308h-120.305v-27.846l57.043-56.7736c11.085-11.8954 18.925-20.0059 18.925-29.7385z\" id=\"error-id\"/><path d=\"m19.3936 103.554c-8.9715 0-14.84183-5.0952-14.84183-14.4544v-20.1029h8.86083v19.3276c0 4.8181 2.2706 7.3102 5.981 7.3102 3.6551 0 5.9257-2.4921 5.9257-7.3102v-19.3276h8.8608v20.1583c0 9.3038-5.8149 14.399-14.7865 14.399zm18.734-.554v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm36.8584-1.994v10.799h-8.6946v-33.726h8.6946v1.9937c1.163-1.3291 3.5997-2.5475 6.1472-2.5475 7.1994 0 11.1314 5.8703 11.1314 13.0143 0 7.0886-3.932 13.0145-11.1314 13.0145-2.5475 0-4.9842-1.219-6.1472-2.548zm0-13.7893v6.5902c.6646 1.3845 2.1599 2.326 3.8213 2.326 2.9905 0 4.7626-2.3814 4.7626-5.5934s-1.7721-5.6488-4.7626-5.6488c-1.7168 0-3.1567.9969-3.8213 2.326zm36.789-9.2485v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.6949v-24.921h8.6949v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm10.352 0c0-7.6978 5.095-13.0143 12.571-13.0143 6.701 0 10.855 3.9874 11.574 9.8023h-8.417c-.222-1.4953-1.385-2.6029-3.157-2.6029-2.437 0-3.987 2.2706-3.987 5.8149s1.55 5.7595 3.987 5.7595c1.772 0 2.935-1.0522 3.157-2.5475h8.417c-.719 5.7596-4.873 9.8025-11.574 9.8025-7.476 0-12.571-5.3167-12.571-13.0145zm42.013 3.7658h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.544-3.5997zm13.428 11.0206h8.473c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.938-1.5506l-4.874-.9969c-4.153-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.74-8.2518zm24.269 0h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm47.918 7.6978h-8.363v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.695v-35.997h8.695v13.0697c1.163-1.3291 3.6-2.5475 6.147-2.5475 7.2 0 11.132 5.8149 11.132 13.0143s-3.932 13.0145-11.132 13.0145c-2.547 0-4.984-1.219-6.147-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.821 2.326 2.991 0 4.763-2.3814 4.763-5.5934s-1.772-5.6488-4.763-5.6488c-1.661 0-3.156.9969-3.821 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm25.461-15.2849h24.311v7.6424h-15.561v5.3165h14.232v7.4763h-14.232v5.8703h15.561v7.6978h-24.311zm27.942 34.0033v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm29.991-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm26.161-16.3371v24.921h-8.694v-24.921zm.61-6.7564c0 2.8244-2.271 4.652-4.929 4.652s-4.929-1.8276-4.929-4.652c0-2.8797 2.271-4.7073 4.929-4.7073s4.929 1.8276 4.929 4.7073zm5.382 23.0935v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm29.22 17.3889h-8.584l3.655-9.414-9.303-24.312h9.026l4.763 14.1773 4.652-14.1773h8.639z\" id=\"error-description\"/></svg>\n      </header>\n      <article>\n        <p><strong>The change you wanted was rejected.</strong> Maybe you tried to change something you didn't have access to. If you're the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "test/dummy/public/500.html",
    "content": "<!doctype html>\n\n<html lang=\"en\">\n\n  <head>\n\n    <title>We're sorry, but something went wrong (500 Internal Server Error)</title>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"initial-scale=1, width=device-width\">\n    <meta name=\"robots\" content=\"noindex, nofollow\">\n\n    <style>\n\n      *, *::before, *::after {\n        box-sizing: border-box;\n      }\n\n      * {\n        margin: 0;\n      }\n\n      html {\n        font-size: 16px;\n      }\n\n      body {\n        background: #FFF;\n        color: #261B23;\n        display: grid;\n        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n        font-size: clamp(1rem, 2.5vw, 2rem);\n        -webkit-font-smoothing: antialiased;\n        font-style: normal;\n        font-weight: 400;\n        letter-spacing: -0.0025em;\n        line-height: 1.4;\n        min-height: 100dvh;\n        place-items: center;\n        text-rendering: optimizeLegibility;\n        -webkit-text-size-adjust: 100%;\n      }\n\n      #error-description {\n        fill: #d30001;\n      }\n\n      #error-id {\n        fill: #f0eff0;\n      }\n\n      @media (prefers-color-scheme: dark) {\n        body {\n          background: #101010;\n          color: #e0e0e0;\n        }\n\n        #error-description {\n          fill: #FF6161;\n        }\n\n        #error-id {\n          fill: #2c2c2c;\n        }\n      }\n\n      a {\n        color: inherit;\n        font-weight: 700;\n        text-decoration: underline;\n        text-underline-offset: 0.0925em;\n      }\n\n      b, strong {\n        font-weight: 700;\n      }\n\n      i, em {\n        font-style: italic;\n      }\n\n      main {\n        display: grid;\n        gap: 1em;\n        padding: 2em;\n        place-items: center;\n        text-align: center;\n      }\n\n      main header {\n        width: min(100%, 12em);\n      }\n\n      main header svg {\n        height: auto;\n        max-width: 100%;\n        width: 100%;\n      }\n\n      main article {\n        width: min(100%, 30em);\n      }\n\n      main article p {\n        font-size: 75%;\n      }\n\n      main article br {\n        display: none;\n\n        @media(min-width: 48em) {\n          display: inline;\n        }\n      }\n\n    </style>\n\n  </head>\n\n  <body>\n\n    <!-- This file lives in public/500.html -->\n\n    <main>\n      <header>\n        <svg height=\"172\" viewBox=\"0 0 480 172\" width=\"480\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m101.23 93.8427c-8.1103 0-15.4098 3.7849-19.7354 8.3813h-36.2269v-99.21891h103.8143v37.03791h-68.3984v24.8722c5.1366-2.7035 15.1396-5.9477 24.6014-5.9477 35.146 0 56.233 22.7094 56.233 55.4215 0 34.605-23.791 57.315-60.558 57.315-37.8492 0-61.64-22.169-63.8028-55.963h42.9857c1.0814 10.814 9.1919 19.195 21.6281 19.195 11.355 0 19.465-8.381 19.465-20.547 0-11.625-7.299-20.5463-20.006-20.5463zm138.833 77.8613c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z\" id=\"error-id\"/><path d=\"m23.1377 68.9967v34.0033h-8.9162v-34.0033zm4.3157 34.0033v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm29.9913-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.5839v6.8671h5.2058v6.7564h-5.2058v8.307c0 1.9383.9415 2.769 2.6583 2.769.9414 0 1.9937-.2216 2.769-.5538v7.3654c-.9969.443-2.8798.775-4.8181.775-5.8703 0-9.1931-2.769-9.1931-9.0819zm32.3666-.1108h8.0301c-.8861 5.7597-5.2057 9.2487-11.6852 9.2487-7.6424 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.3165-13.0143 12.5159-13.0143 7.6424 0 11.9621 5.095 11.9621 12.5159v2.1598h-16.1156c.2769 2.9905 1.8275 4.5965 4.3196 4.5965 1.7722 0 3.1567-.7753 3.6551-2.4921zm-3.8212-10.0237c-2.0491 0-3.4336 1.2737-3.9874 3.5997h7.5317c-.1107-2.0491-1.3845-3.5997-3.5443-3.5997zm31.4299-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.599-.7753-2.382 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.381.443zm2.949 25.0318v-24.921h8.694v2.1598c1.385-1.5506 3.821-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.64v-14.2327c0-2.049-1.052-3.5443-3.267-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm50.371 0h-8.363v-1.274c-.83.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.433.7199-3.433 2.3813 0 1.7168 1.716 2.4367 3.433 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742-29.0191v35.997h-8.694v-35.997zm13.036 25.9178h9.248c.72 2.326 2.714 3.489 5.483 3.489 2.713 0 4.596-1.163 4.596-3.2674 0-1.6061-1.052-2.326-3.212-2.8244l-6.534-1.3845c-4.985-1.1076-8.751-3.7105-8.751-9.47 0-6.6456 5.538-11.0206 13.07-11.0206 8.307 0 13.014 4.5411 13.956 10.4114h-8.695c-.72-1.8829-2.27-3.3228-5.205-3.3228-2.548 0-4.265 1.1076-4.265 2.9905 0 1.4953 1.052 2.326 2.825 2.7137l6.645 1.5506c5.815 1.3845 9.027 4.5412 9.027 9.8023 0 6.9778-5.87 10.9654-13.291 10.9654-8.141 0-13.679-3.9322-14.897-10.6332zm46.509 1.3845h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm31.431-6.3134v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.382.443zm18.288 25.0318h-7.809l-9.47-24.921h8.861l4.763 14.288 4.652-14.288h8.528zm25.614-8.6947h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997zm31.43-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443zm13.703-8.9715h24.312v7.6424h-15.562v5.3165h14.232v7.4763h-14.232v5.8703h15.562v7.6978h-24.312zm44.667 8.9715v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm19.673 0v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm28.082-12.5713v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443z\" id=\"error-description\"/></svg>\n      </header>\n      <article>\n        <p><strong>We're sorry, but something went wrong.</strong><br> If you're the application owner check the logs for more information.</p>\n      </article>\n    </main>\n\n  </body>\n\n</html>\n"
  },
  {
    "path": "test/jobs/trim_job_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"config_stubs\"\n\nclass TrimJobTest < ActiveJob::TestCase\n  include ConfigStubs\n\n  test \"trims a limited number of messages\" do\n    SolidCable.stub(:trim_chance, 99.999) do\n      with_cable_config trim_batch_size: 2, message_rention: \"1.second\" do\n        4.times do\n          SolidCable::Message.broadcast(\"foo\", \"bar\")\n          SolidCable::Message.update_all(created_at: 2.days.ago)\n        end\n\n        assert_difference -> { SolidCable::Message.count }, -2 do\n          SolidCable::TrimJob.perform_now\n        end\n      end\n    end\n  end\n\n  test \"trims when out of band with autotrim disabled\" do\n    SolidCable.stub(:trim_chance, 0) do\n      with_cable_config autotrim: false, trim_batch_size: 2, message_rention: \"1.second\" do\n        4.times do\n          SolidCable::Message.broadcast(\"foo\", \"bar\")\n          SolidCable::Message.update_all(created_at: 2.days.ago)\n        end\n\n        assert_difference -> { SolidCable::Message.count }, -2 do\n          SolidCable::TrimJob.perform_now\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/lib/action_cable/subscription_adapter/solid_cable_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"concurrent\"\n\nrequire \"active_support/core_ext/hash/indifferent_access\"\nrequire \"pathname\"\nrequire \"config_stubs\"\n\nclass ActionCable::SubscriptionAdapter::SolidCableTest < ActionCable::TestCase\n  include ConfigStubs\n\n  WAIT_WHEN_EXPECTING_EVENT = 1\n  WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2\n\n  setup do\n    server = ActionCable::Server::Base.new\n    server.config.cable = cable_config.with_indifferent_access\n    server.config.logger = Logger.new(StringIO.new).tap do |l|\n      l.level = Logger::UNKNOWN\n    end\n\n    adapter_klass = server.config.pubsub_adapter\n\n    @rx_adapter = adapter_klass.new(server)\n    @tx_adapter = adapter_klass.new(server)\n\n    @tx_adapter.shutdown\n    @tx_adapter = @rx_adapter\n  end\n\n  teardown do\n    [@rx_adapter, @tx_adapter].uniq.compact.each(&:shutdown)\n  end\n\n  test \"subscribe_and_unsubscribe\" do\n    subscribe_as_queue(\"channel\") do |queue|\n    end\n  end\n\n  test \"basic_broadcast\" do\n    subscribe_as_queue(\"channel\") do |queue|\n      @tx_adapter.broadcast(\"channel\", \"hello world\")\n\n      assert_equal \"hello world\", next_message_in_queue(queue)\n    end\n  end\n\n  test \"broadcast_after_unsubscribe\" do\n    keep_queue = nil\n    subscribe_as_queue(\"channel\") do |queue|\n      keep_queue = queue\n\n      @tx_adapter.broadcast(\"channel\", \"hello world\")\n\n      assert_equal \"hello world\", next_message_in_queue(queue)\n    end\n\n    @tx_adapter.broadcast(\"channel\", \"hello void\")\n\n    sleep WAIT_WHEN_NOT_EXPECTING_EVENT\n    assert_empty keep_queue\n  end\n\n  test \"trims_after_unsubscribe\" do\n    SolidCable.stub(:trim_chance, 99.999999) do\n      with_cable_config message_retention: \"2.seconds\", trim_batch_size: 2 do\n        subscribe_as_queue(\"channel\") do |queue|\n          4.times do\n            @tx_adapter.broadcast(\"channel\", \"hello world\")\n            sleep 3\n          end\n\n          queue.clear\n        end\n        assert_equal 1, SolidCable::Message.where(channel: \"channel\").count\n      end\n    end\n  end\n\n  test \"multiple_broadcast\" do\n    subscribe_as_queue(\"channel\") do |queue|\n      @tx_adapter.broadcast(\"channel\", \"bananas\")\n      @tx_adapter.broadcast(\"channel\", \"apples\")\n\n      received = []\n      2.times { received << next_message_in_queue(queue) }\n      assert_equal %w(apples bananas), received.sort\n    end\n  end\n\n  test \"identical_subscriptions\" do\n    subscribe_as_queue(\"channel\") do |queue|\n      subscribe_as_queue(\"channel\") do |queue_2|\n        @tx_adapter.broadcast(\"channel\", \"hello\")\n\n        assert_equal \"hello\", next_message_in_queue(queue_2)\n      end\n\n      assert_equal \"hello\", next_message_in_queue(queue)\n    end\n  end\n\n  test \"simultaneous_subscriptions\" do\n    subscribe_as_queue(\"channel\") do |queue|\n      subscribe_as_queue(\"other channel\") do |queue_2|\n        @tx_adapter.broadcast(\"channel\", \"apples\")\n        @tx_adapter.broadcast(\"other channel\", \"oranges\")\n\n        assert_equal \"apples\", next_message_in_queue(queue)\n        assert_equal \"oranges\", next_message_in_queue(queue_2)\n      end\n    end\n  end\n\n  test \"channel_filtered_broadcast\" do\n    subscribe_as_queue(\"channel\") do |queue|\n      @tx_adapter.broadcast(\"other channel\", \"one\")\n      @tx_adapter.broadcast(\"channel\", \"two\")\n\n      assert_equal \"two\", next_message_in_queue(queue)\n    end\n  end\n\n  test \"long_identifiers\" do\n    channel_1 = \"#{'a' * 100}1\"\n    channel_2 = \"#{'a' * 100}2\"\n    subscribe_as_queue(channel_1) do |queue|\n      subscribe_as_queue(channel_2) do |queue_2|\n        @tx_adapter.broadcast(channel_1, \"apples\")\n        @tx_adapter.broadcast(channel_2, \"oranges\")\n\n        assert_equal \"apples\", next_message_in_queue(queue)\n        assert_equal \"oranges\", next_message_in_queue(queue_2)\n      end\n    end\n  end\n\n  test \"does not raise error when polling with no Active Record logger\" do\n    with_active_record_logger(nil) do\n      assert_nothing_raised do\n        subscribe_as_queue(\"channel\") do |queue|\n          @tx_adapter.broadcast(\"channel\", \"hello world\")\n\n          assert_equal \"hello world\", next_message_in_queue(queue)\n        end\n      end\n    end\n  end\n\n  test \"does not send old messages\" do\n    @tx_adapter.broadcast(\"channel\", \"channel1\")\n    @tx_adapter.broadcast(\"channel\", \"channel2\")\n\n    subscribe_as_queue(\"channel\") do |queue|\n      assert_empty queue\n\n      @tx_adapter.broadcast(\"channel\", \"channel3\")\n      @tx_adapter.broadcast(\"channel\", \"channel4\")\n      @tx_adapter.broadcast(\"other\", \"other1\")\n      @tx_adapter.broadcast(\"other\", \"other2\")\n\n      subscribe_as_queue(\"other\") do |other_queue|\n        assert_empty other_queue\n      end\n      assert_equal \"channel3\", next_message_in_queue(queue)\n      assert_equal \"channel4\", next_message_in_queue(queue)\n    end\n\n    @tx_adapter.broadcast(\"channel\", \"channel5\")\n    @tx_adapter.broadcast(\"channel\", \"channel6\")\n\n    subscribe_as_queue(\"channel\") do |queue|\n      assert_empty queue\n    end\n  end\n\n  test \"retries after a connection failure and keeps listening\" do\n    with_cable_config reconnect_attempts: [0] do\n      raised = false\n      original = SolidCable::Message.method(:broadcastable)\n\n      SolidCable::Message.stub(:broadcastable, lambda { |channels, last_id|\n        if raised\n          original.call(channels, last_id)\n        else\n          raised = true\n          raise ActiveRecord::ConnectionFailed, \"boom\"\n        end\n      }) do\n        subscribe_as_queue(\"reconnect-channel\") do |queue|\n          @tx_adapter.broadcast(\"reconnect-channel\", \"hello\")\n\n          assert_equal \"hello\", next_message_in_queue(queue)\n        end\n      end\n\n      assert raised\n    end\n  end\n\n  private\n    def cable_config\n      { adapter: \"solid_cable\", message_retention: \"1.second\",\n        polling_interval: \"0.01.seconds\" }\n    end\n\n    def subscribe_as_queue(channel, adapter = @rx_adapter)\n      queue = Queue.new\n\n      callback = ->(data) { queue << data }\n      subscribed = Concurrent::Event.new\n      adapter.subscribe(channel, callback, proc { subscribed.set })\n      subscribed.wait(WAIT_WHEN_EXPECTING_EVENT)\n      assert_predicate subscribed, :set?\n\n      yield queue\n\n      sleep WAIT_WHEN_NOT_EXPECTING_EVENT\n      assert_empty queue\n    ensure\n      adapter.unsubscribe(channel, callback) if subscribed.set?\n    end\n\n    def with_active_record_logger(logger)\n      old_logger = ActiveRecord::Base.logger\n      ActiveRecord::Base.logger = logger\n      yield\n    ensure\n      ActiveRecord::Base.logger = old_logger\n    end\n\n    def next_message_in_queue(queue)\n      Timeout.timeout(5, nil, \"Failed to get next item in queue\") { queue.pop }\n    end\nend\n"
  },
  {
    "path": "test/lib/generators/solid_cable/install/install_generator_test.rb",
    "content": "require \"test_helper\"\nrequire_relative \"../../../../../lib/generators/solid_cable/install/install_generator\"\n\nclass SolidCable::InstallGeneratorTest < Rails::Generators::TestCase\n  tests SolidCable::InstallGenerator\n  destination File.expand_path(\"../../../../../tmp\", __dir__)\n\n  setup :prepare_destination\n  setup :run_generator\n\n  test \"cable_schema exists\" do\n    assert_file \"db/cable_schema.rb\"\n  end\n\n  test \"cable.yml exists\" do\n    assert_file \"config/cable.yml\"\n  end\nend\n"
  },
  {
    "path": "test/lib/generators/solid_cable/update/update_generator_test.rb",
    "content": "require \"test_helper\"\nrequire_relative \"../../../../../lib/generators/solid_cable/update/update_generator\"\n\nclass SolidCable::UpdateGeneratorTest < Rails::Generators::TestCase\n  tests SolidCable::UpdateGenerator\n  destination File.expand_path(\"../../../../../tmp\", __dir__)\n\n  setup :prepare_destination\n  setup :run_generator\n\n  test \"cable_schema exists\" do\n    assert_migration \"db/cable_migrate/create_compact_channel.rb\"\n  end\nend\n"
  },
  {
    "path": "test/solid_cable_test.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"config_stubs\"\n\nclass SolidCableTest < ActiveSupport::TestCase\n  include ConfigStubs\n\n  test \"it has a version number\" do\n    assert SolidCable::VERSION\n  end\n\n  test \"autotrimming when nothing is set\" do\n    assert_not Rails.application.config_for(\"cable\").key?(:autotrim)\n    assert SolidCable.autotrim?\n  end\n\n  test \"autotrimming when set to false\" do\n    with_cable_config autotrim: false do\n      assert_not SolidCable.autotrim?\n    end\n  end\n\n  test \"autotrimming when set to true\" do\n    with_cable_config autotrim: true do\n      assert SolidCable.autotrim?\n    end\n  end\n\n  test \"default trim_batch_size is 100\" do\n    assert_equal 100, SolidCable.trim_batch_size\n  end\n\n  test \"trim_batch_size when set badly\" do\n    with_cable_config trim_batch_size: \"weird\" do\n      assert_equal 100, SolidCable.trim_batch_size\n    end\n\n    with_cable_config trim_batch_size: \"0\" do\n      assert_equal 100, SolidCable.trim_batch_size\n    end\n  end\n\n  test \"trim_batch_size when set\" do\n    with_cable_config trim_batch_size: 42 do\n      assert_equal 42, SolidCable.trim_batch_size\n    end\n  end\n\n  test \"reconnect_attempts defaults to a single zero\" do\n    assert_equal [ 0 ], SolidCable.reconnect_attempts\n  end\n\n  test \"reconnect_attempts accepts an integer\" do\n    with_cable_config reconnect_attempts: 3 do\n      assert_equal [ 0, 0, 0 ], SolidCable.reconnect_attempts\n    end\n  end\n\n  test \"reconnect_attempts accepts an array\" do\n    with_cable_config reconnect_attempts: [ 0, 1, 2 ] do\n      assert_equal [ 0, 1, 2 ], SolidCable.reconnect_attempts\n    end\n  end\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "content": "# frozen_string_literal: true\n\n# Configure Rails Environment\nENV[\"RAILS_ENV\"] = \"test\"\n\nrequire_relative \"../test/dummy/config/environment\"\nActiveRecord::Migrator.migrations_paths = [ File.expand_path(\n\"../test/dummy/db/migrate\", __dir__\n) ]\nrequire \"rails/test_help\"\nrequire \"minitest/autorun\"\n\n# Load fixtures from the engine\nif ActiveSupport::TestCase.respond_to?(:fixture_paths=)\n  ActiveSupport::TestCase.fixture_paths =\n    [ File.expand_path(\"fixtures\", __dir__) ]\n  ActionDispatch::IntegrationTest.fixture_paths =\n    ActiveSupport::TestCase.fixture_paths\n  ActiveSupport::TestCase.file_fixture_path =\n    \"#{File.expand_path('fixtures', __dir__)}/files\"\n  ActiveSupport::TestCase.fixtures :all\nend\n"
  }
]