[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: \"3.3\"\n          bundler-cache: true\n\n      - name: Rubocop\n        run: bundle exec rubocop\n\n      - name: syntax_tree\n        if: ${{ !cancelled() }}\n        run: |\n          set -E\n          bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')\n\n  test:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        ruby: [\"2.7\", \"3.0\", \"3.1\", \"3.2\", \"3.3\"]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup ruby\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby }}\n          bundler-cache: true\n\n      - name: Tests\n        run: bundle exec rake test\n\n  publish:\n    if: github.event_name == 'push' && github.ref == 'refs/heads/main'\n    needs: [lint, test]\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Release Gem\n        uses: discourse/publish-rubygems-action@v3\n        env:\n          RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}\n          GIT_EMAIL: team@discourse.org\n          GIT_NAME: discoursebot\n"
  },
  {
    "path": ".gitignore",
    "content": "Gemfile.lock\ncoverage\n/config.yml\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "inherit_gem:\n  rubocop-discourse: stree-compat.yml\n\nDiscourse/Plugins:\n  Enabled: false\n"
  },
  {
    "path": ".streerc",
    "content": "--print-width=100\n--plugins=plugin/trailing_comma,plugin/disable_auto_ternary\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [2.1.0] - 2025-12-02\n### Changed\n- Add support for `external_id` in topic creation\n### Added\n- Add get topic url by `external_id`\n\n## [2.0.1] - 2023-06-09\n### Removed\n- Invite admin method removed\n\n## [2.0.0] - 2023-05-26\n### Changed\n- The package now requires ruby 2.7+\n- The package now requires faraday 2.7+\n\n## [1.1.0] - 2022-07-05\n### Changed\n- `DiscourseApi::SingleSignOn.parse` now raises `DiscourseApi::SingleSignOn::ParseError` (inherits from `RuntimeError` to preserve backward compatibility) instead of `RuntimeError` when there's a signature mismatch.\n- `DiscourseApi::SingleSignOn.parse` now raises `DiscourseApi::SingleSignOn::MissingConfigError` (also inherits from `RuntimeError`) if `sso_secret` or `sso_url` are missing.\n\n## [1.0.0] - 2022-05-01\n### Changed\n- The package now requires ruby 2.6+\n\n## [0.48.1] - 2022-04-13\n### Added\n- New attributes for Discourse Connect (aka SSO)\n\n## [0.48.0] - 2022-01-28\n### Added\n- `group_add_owners` method (#239)\n- `group_remove_owners` method (#239)\n- `anonymize` method (#241)\n\n### Changed\n- `DiscourseApi::Timeout` error now inherits from `DiscourseApi::Error` (#240)\n- `DiscourseApi::SingleSignOn#groups` now returns an array of strings where each string is a group name, rather than an array with a single string that contains all the groups comma-concatenated (#243)\n\n## [0.47.0] - 2021-07-19\n### Added\n- Update invite method\n- Retrieve invite method\n- Destroy all expired invites method\n- Destroy invite method\n- Resend all invites method\n- Resend invite method\n- Pass params to get notifications API\n\n### Deprecated\n- `invite_user_to_topic` has been deprecated, use `invite_to_topic` instead.\n- `create_private_message` has been deprecated, use `create_pm` instead.\n\n## [0.46.0] - 2021-04-12\n### Added\n- Allow bookmarking topics\n- Add timeout to requests\n- Add params to get_topic_posts\n\n## [0.45.1] - 2021-03-11\n### Added\n- Fetch global top topics\n- Allow setting topic notifications\n- Return full category response\n\n### Changed\n- Use new search endpoint\n\n## [0.45.0] - 2021-01-15\n### Added\n- Tag configuration in create_category/update_category\n- Topic#change_owner\n- Support passing approved to #create_user\n### Changed\n- API key methods use the latest endpoints\n\n## [0.44.0] - 2020-11-13\n### Fixed\n- Updated `show_tag` method to use new route\n### Removed\n- Support for Ruby 2.3 and 2.4\n\n## [0.43.1] - 2020-11-04\n### Fixed\n- Tagged version 0.43.0 got pushed without commit due to new master branch\n  protections in github. No, code changes here just making sure tags align with\n  commits.\n\n## [0.43.0] - 2020-11-04\n### Added\n- Add pagination to list groups endpoint\n### Deprecated\n- `change_topic_status` has been deprecated, use `update_topic_status` instead.\n\n## [0.42.0] - 2020-07-09\n### Added\n- Create topics with tags\n\n## [0.41.0] - 2020-06-17\n### Added\n- Add basic auth support\n### Fixed\n- Fix SSO custom field prefixes\n### Removed\n- Obsolete api key endpoints\n\n## [0.40.0] - 2020-05-07\n### Fixed\n- Add missing attributes to `sync_sso`\n### Added\n- Add delete category method\n\n## [0.39.3] - 2020-04-30\n### Fixed\n- Add `reviewable_by_group_name` to categories\n\n## [0.39.2] - 2020-04-30\n### Fixed\n- Add `members_visibility_level` to group\n\n## [0.39.1] - 2020-03-27\n### Fixed\n- Ensure released gem version matches this commit\n\n## [0.39.0] - 2020-03-27\n### Added\n- Get latest posts across topics via posts.json\n- Allow  more options parameters when creating a category\n- Don't require topic slug when updating topic status\n- Example files now read config.yml file when present for client settings\n### Fixed\n- Issue with `topic_posts` and frozen strings\n- Fixed some topic and category methods\n\n## [0.38.0] - 2019-10-18\n### Added\n- Allow setting locale in SingleSignOn\n- Optional param to group members to include owners as well as members\n\n## [0.37.0] - 2019-09-23\n### Added\n- user-badges endpoint for full badges list\n- expanded list of allowed messages\n- grant/revoke moderation\n\n## [0.36.0] - 2019-07-18\n### Added\n- Added poll methods\n### Fixed\n- Updated create topic example\n- Fixed capitalization for header auth keys\n\n## [0.35.0] - 2019-05-15\n### Added\n- Added `custom_fields` param to create/update category\n- Added `frozen_string_literal: true` to all the files\n- Added rubocop and all the changes that went along with it\n### Fixed\n- Allow `api_username` to be changed for an initialized client\n- Update many of the `/users` routes to use the `/u` route\n### Changed\n- Changed `update_trust_level` to follow consistent method param syntax where\n  you specify the id first followed by params\n\n## [0.34.0] - 2019-04-02\n### Added\n- Header based authentication\n### Removed\n- Query param based authentication\n\n## [0.33.0] - 2019-03-04\n### Added\n- Added a new method to update a users notification level in a category\n\n## [0.32.0] - 2019-02-13\n### Added\n- Added a new method to update a users notification level in a group\n\n## [0.31.0] - 2019-02-07\n### Added\n- Added `deactivate` method\n- Added 201 and 204 as valid POST responses\n\n## [0.30.0] - 2018-12-19\n### Added\n- Add params hash to `list_users`\n\n## [0.29.0] - 2018-12-07\n### Added\n- Add `add_groups` and `remove_groups` to `sync_sso`\n\n## [0.28.2] - 2018-11-26\n### Fixed\n- Updated arguments for suspending a user\n\n## [0.28.1] - 2018-10-26\n### Fixed\n- Fixed non-URI chars in `check_username` method\n\n## [0.28.0] - 2018-10-23\n### Added\n- Added `check_username` method\n\n## [0.27.0] - 2018-09-14\n### Added\n- Added `site_settings_update` method\n\n## [0.26.0] - 2018-09-10\n### Added\n- Added user `user_actions` endpoint so you can retrieve `user_replies` and\n  `user_topics_and_replies`\n\n## [0.25.0] - 2018-08-15\n### Added\n- Added ability to rescue certain error classes and inspect the response object\n\n## [0.24.0] - 2018-05-30\n### Added\n- Added support for custom `user_fields` when creating a user\n\n## [0.23.1] - 2018-05-24\n### Fixed\n- Can now change `api_username` without creating a new client\n\n## [0.23.0] - 2018-05-24\n### Added\n- Added `delete_user` method\n\n## [0.22.0] - 2018-05-04\n### Added\n- Support for subfolder paths\n\n## [0.21.0] - 2018-04-23\n### Fixed\n- Update GET groups api route\n- Update PUT groups api route\n\n## [0.20.0] - 2017-12-13\n### Added\n- Add base error class\n### Fixed\n- Update SSO\n\n## [0.19.0] - 2017-11-22\n### Added\n- Added optional `create_post` params\n\n## [0.18.0] - 2017-10-17\n### Added\n- Added `update_group` API call\n### Fixed\n- Fixed params for create groups endpoint\n- Fixed invite token API endpoint\n\n## [0.17.0] - 2017-06-29\n### Added\n- Add title to SSO sync\n\n## [0.16.1] - 2017-06-23\n### Fixed\n- `user_sso` should use `user_id` instead of `username`\n- `upload_file` should also include optional `user_id` param\n\n## [0.16.0] - 2017-05-14\n### Added\n- added `upload_file`\n### Removed\n- removed `upload_post_image`\n\n## [0.15.0] - 2017-04-12\n### Added\n- added the ability to create private messages\n\n## [0.14.1] - 2016-12-20\n### Fixed\n- allow for rack 2.0+ versions so that it doesn't clash with rails.\n\n## [0.14.0] - 2016-10-30\n### Added\n- improved error responses by adding `NotFoundError`, `UnprocessableEntity`, and `TooManyRequests`\n- added `delete_post` method\n\n## [0.13.0] - 2016-10-09\n### Added\n- added `update_category`\n- added `upload_post_image`\n\n## [0.12.0] - 2016-10-06\n### Added\n- add endpoint for `/admin/users/{id}/suspend`\n- add endpoint for `/admin/users/{id}/unsuspend`\n\n## [0.11.0] - 2016-09-03\n### Fixed\n- add destination folder to backup download\n- `post_action_users`\n\n### Added\n- `change_topic_status`\n- set username of topic on creation\n\n## [0.10.1] - 2016-05-04\n### Fixed\n- raise an error if search is empty\n- fix /category path to be just /c\n- return errors for category_latest_topics if there are some\n\n## [0.10.0] - 2016-04-19\n### Added\n- group_members: Allows you to retrieve more than 100 users with pagination (offset &\n  limit)\n### Fixed\n- Deprecation warning with SimpleCov\n- updated rack dependency and added ruby 2.3 to travis config\n\n## [0.9.1] - 2016-03-23\n### Fixed\n- topic and post like/flag need to use `:id`\n\n## [0.9.0] - 2016-03-22\n### Added\n- can now like/flag topics and posts\n\n## [0.8.1] - 2016-03-03\n### Fixed\n- enable use of discourse_api to make unauthenticated requests to discourse\n  endpoints like /categories and /topics\n\n## [0.8.0] - 2016-02-28\n### Added\n- get stats from admin dashboard\n- get only stat totals from admin dashboard\n\n## [0.7.0] - 2015-12-09\n### Added\n- get user by external_id\n\n## [0.6.2] - 2015-12-02\n### Fixed\n- `API::Params` will not work correctly when both optional and defaults are\n  specified\n\n## [0.6.1] - 2015-11-28\n### Fixed\n- typo in topic_posts method\n\n## [0.6.0] - 2015-11-27\n### Added\n- get posts in topic by an array of id's\n\n## [0.5.1] - 2015-11-21\n### Fixed\n- remove puts statement\n\n## [0.5.0] - 2015-11-21\n### Added\n- get latest category topics by page\n\n## [0.4.0] - 2015-01-15\n### Added\n- generate an api key for a user\n- revoke an api key for a user\n- update user trust level\n- grant user badge\n\n## [0.3.6] - 2015-01-11\n### Added\n- list badges\n- view email settings\n- list emails sent\n- list badges by user\n- be able to specify SSL connection settings\n- list api keys generated\n- list backups created\n\n## [0.3.5] - 2015-01-06\n### Added\n- Can now get a list of users by type: active, new, staff, etc.\n- `client.category_latest_posts(\"category-slug\")` endpoint\n\n## [0.1.2] - 2014-05-11\n- Release\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\nsource \"https://rubygems.org\"\n\n# Specify your gem's dependencies in discourse_api.gemspec\ngemspec\n"
  },
  {
    "path": "Guardfile",
    "content": "# frozen_string_literal: true\nguard :rspec do\n  watch(%r{^spec/.+_spec\\.rb$})\n  watch(%r{^lib/(.+)\\.rb$})     { |m| \"spec/lib/#{m[1]}_spec.rb\" }\n  watch('spec/spec_helper.rb')  { 'spec' }\nend\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2014 Civilized Discourse Construction Kit, Inc.\n\nMIT License\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": "# DiscourseApi\n\nThe Discourse API gem allows you to consume the Discourse API\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'discourse_api'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install discourse_api\n\n## Usage\n\nOver time this project intends to have a full Discourse API. At the moment there are only a\nfew endpoints available:\n\n```ruby\nclient = DiscourseApi::Client.new(\"http://try.discourse.org\")\nclient.api_key = \"YOUR_API_KEY\"\nclient.api_username = \"YOUR_USERNAME\"\n\nclient.ssl(...)                                 #=> specify SSL connection settings if needed\n\n# Topic endpoints\nclient.latest_topics                            #=> Gets a list of the latest topics\nclient.new_topics                               #=> Gets a list of new topics\nclient.topics_by(\"sam\")                         #=> Gets a list of topics created by user \"sam\"\nclient.topic(57)                                #=> Gets the topic with id 57\n\n# Search endpoint\nclient.search(\"sandbox\")                        #=> Gets a list of topics that match \"sandbox\"\n\n# Categories endpoint\nclient.categories                               #=> Gets a list of categories\nclient.category_latest_topics(category_slug: \"lounge\")  #=> Gets a list of latest topics in a category\n\n# SSO endpoint\nclient.sync_sso(                                #=> Synchronizes the SSO record\n  sso_secret: \"discourse_sso_rocks\",\n  name: \"Test Name\",\n  username: \"test_name\",\n  email: \"name@example.com\",\n  external_id: \"2\",\n  custom_fields: {\n    field_1: 'potato'\n  }\n)\n\n# Private messages\nclient.private_messages(\"test_user\")            #=> Gets a list of private messages received by \"test_user\"\nclient.sent_private_messages(\"test_user\")       #=> Gets a list of private messages sent by \"test_user\"\nclient.create_private_message(                  #=> Creates a private messages by api_username user\n  title: \"Confidential: Hello World!\",\n  raw: \"This is the raw markdown for my private message\",\n  target_usernames: \"user1,user2\"\n)\n\n```\n\nYou can handle some basic errors by rescuing from certain error classes and inspecting the response object passed to those errors:\n\n```ruby\nbegin\n  client.create_group({ name: 'NO' })\nrescue DiscourseApi::UnprocessableEntity => error\n  # `body` is something like `{ errors: [\"Name must be at least 3 characters\"] }`\n  # This outputs \"Name must be at least 3 characters\"\n  puts error.response.body['errors'].first\nend\n```\n\nCheck out [lib/discourse_api/error.rb](lib/discourse_api/error.rb) and [lib/discourse_api/client.rb](lib/discourse_api/client.rb)'s `handle_error` method for the types of errors raised by the API.\n\nIf your forum has a basic HTTP authentication enabled, set user and password:\n\n```ruby\nclient.basic_auth = {\n  user: \"test\",\n  password: \"secret\"\n}\n```\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## Testing\n\n1. Install discourse locally\n2. Inside of your discourse directory, run: `bundle exec rake db:api_test_seed`\n3. Start discourse: `bundle exec rails s`\n4. Install bundler in the discourse_api directory, run `gem install bundler`\n5. Inside of your discourse_api directory, run: `bundle exec rspec spec/`\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\nrequire 'bundler/gem_tasks'\n\nrequire 'rspec/core/rake_task'\nRSpec::Core::RakeTask.new(:spec)\n\nrequire 'rubocop/rake_task'\nRuboCop::RakeTask.new(:rubocop)\n\ntask test: :spec\ntask lint: :rubocop\ntask default: [:spec, :lint]\n"
  },
  {
    "path": "config_example.yml",
    "content": "# Specify your environment by making a copy of this file to create a file in your root directory called config.yml with your environment settings.\n\n# host e.g. http://localhost:3000 or https://discourse.my_domain.com\nhost: YOUR_HOST_NAME\n\n# api user (can effect results returned, e.g. .categories method returns only the categories your api_username can see)\n# create a new client with when changing api user\napi_username: YOUR_API_USERNAME\n\n# api key from Discourse admin panel /admin/api/keys\napi_key: YOUR_API_KEY\n"
  },
  {
    "path": "discourse_api.gemspec",
    "content": "# frozen_string_literal: true\n\nlib = File.expand_path(\"lib\", __dir__)\n$LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib)\nrequire \"discourse_api/version\"\n\nGem::Specification.new do |spec|\n  spec.name = \"discourse_api\"\n  spec.version = DiscourseApi::VERSION\n  spec.authors = [\"Sam Saffron\", \"John Paul Ashenfelter\", \"Michael Herold\", \"Blake Erickson\"]\n  spec.email = %w[\n    sam.saffron@gmail.com\n    john@ashenfelter.com\n    michael.j.herold@gmail.com\n    o.blakeerickson@gmail.com\n  ]\n  spec.description = \"Discourse API\"\n  spec.summary = \"Allows access to the Discourse API\"\n  spec.homepage = \"http://github.com/discourse/discourse_api\"\n  spec.license = \"MIT\"\n\n  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)\n  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }\n  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})\n  spec.require_paths = [\"lib\"]\n\n  spec.add_runtime_dependency \"faraday\", \"~> 2.7\"\n  spec.add_runtime_dependency \"faraday-follow_redirects\"\n  spec.add_runtime_dependency \"faraday-multipart\"\n  spec.add_runtime_dependency \"rack\", \">= 1.6\"\n\n  spec.add_development_dependency \"bundler\", \"~> 2.0\"\n  spec.add_development_dependency \"guard\", \"~> 2.14\"\n  spec.add_development_dependency \"guard-rspec\", \"~> 4.7\"\n  spec.add_development_dependency \"rake\", \">= 12.3.3\"\n  spec.add_development_dependency \"rb-inotify\", \"~> 0.9\"\n  spec.add_development_dependency \"rspec\", \"~> 3.4\"\n  spec.add_development_dependency \"simplecov\", \"~> 0.11\"\n  spec.add_development_dependency \"webmock\", \"~> 3.0\"\n  spec.add_development_dependency \"rubocop-discourse\", \"= 3.8.1\"\n  spec.add_development_dependency \"syntax_tree\", \"~> 6.2.0\"\n\n  spec.required_ruby_version = \">= 2.7.0\"\nend\n"
  },
  {
    "path": "examples/backups.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# get list of backup files\nputs client.backups()\n\n# create backup\nputs client.create_backup()\n\n# restore backup\nputs client.restore_backup(\"backup_file_name.tar.gz\")\n\n# download backup\nputs client.download_backup(\"backup_file_name.tar.gz\", \"/tmp/\")\n"
  },
  {
    "path": "examples/badges.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# get badges\nputs client.badges\n\n# get badges for a user\nputs client.user_badges(\"test-user\")\n\n# Grants a badge to a user.\nputs client.grant_user_badge(badge_id: 9, username: \"test-user\", reason: \"Really nice person\")\n\n# Creates a new badge\n\n###\n# Required params:\n#   :name, :badge_type_id (gold: 1, bronze: 3, silver: 2)\n# Optional params:\n#   :description, :allow_title, :multiple_grant, :icon, :listable,\n#   :target_posts, :query, :enabled, :auto_revoke, :badge_grouping_id,\n#   :trigger, :show_posts, :image, :long_description\n###\n\nputs client.create_badge(name: \"Shiny new badge\", badge_type_id: 1)\n"
  },
  {
    "path": "examples/bookmark_topic.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# Bookmark topic\nputs client.bookmark_topic(1418)\n\n# Remove bookmark from topic\nputs client.remove_topic_bookmark(1418)\n"
  },
  {
    "path": "examples/category.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# get categories\nputs client.categories()\n\n# get sub categories for parent category with id 2\nputs client.categories(parent_category_id: 2)\n\n# get the full categories response\nputs client.categories_full()\n\n# List topics in a category\ncategory_topics = client.category_latest_topics(category_slug: \"test-category\")\nputs category_topics\n\n# List topics in a category paged\ncategory_topics_paged = client.category_latest_topics(category_slug: \"test-category\", page: \"5\")\nputs category_topics_paged\n\n# update category notification_level\nupdate_response =\n  client.category_set_user_notification(\n    id: \"test-id\",\n    notification_level: \"test-notification-level\",\n  )\nputs update_response\n"
  },
  {
    "path": "examples/change_topic_status.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\nresponse =\n  client.create_topic(\n    category: 1,\n    skip_validations: true,\n    auto_track: false,\n    title: \"Concert Master: A new way to choose\",\n    raw: \"This is the raw markdown for my post\",\n  )\n\n# get topic_id from response\ntopic_id = response[\"topic_id\"]\n\n##\n# available options (guessing from reading discourse source)\n# status can be: ['autoclose', 'closed', 'archived', 'disabled', 'visible']\n# enabled can be: [true, false]\n##\n\n# lock topic (note: api_username determines user that is performing action)\nparams = { status: \"closed\", enabled: true, api_username: \"YOUR USERNAME/USERS USERNAME\" }\nclient.change_topic_status(topic_id, params)\n\n# unlock topic (note: api_username determines user that is performing action)\nparams = { status: \"closed\", enabled: false, api_username: \"YOUR USERNAME/USERS USERNAME\" }\nclient.change_topic_status(topic_id, params)\n"
  },
  {
    "path": "examples/create_private_message.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\nclient.create_private_message(\n  title: \"Confidential: Hello World!\",\n  raw: \"This is the raw markdown for my private message\",\n  target_usernames: \"user1,user2\",\n)\n"
  },
  {
    "path": "examples/create_topic.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\nclient.create_topic(\n  category: 1,\n  skip_validations: true,\n  auto_track: false,\n  title: \"Concert Master: A new way to choose\",\n  raw: \"This is the raw markdown for my post\",\n)\n\n# create Poll topic\nclient.create_topic(\n  category: 2,\n  skip_validations: false,\n  auto_track: false,\n  title: \"Your Favorite Color?\",\n  raw: \"[poll name=color]\\n- Green\\n- Blue\\n- Red\\n[/poll]\",\n)\n\n# Create Topic with Tags\nclient.create_topic(\n  category: 1,\n  skip_validations: true,\n  auto_track: false,\n  title: \"Concert Master: A new way to choose\",\n  raw: \"This is the raw markdown for my post\",\n  tags: %w[asdf fdsa],\n)\n"
  },
  {
    "path": "examples/create_update_category.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n###\n# Required category params:\n#   :name, :color, :text_color\n# Optional category params:\n#   :slug, :permissions, :auto_close_hours, :auto_close_based_on_last_post, :position, :email_in,\n#   :email_in_allow_strangers, :logo_url, :background_url, :allow_badges, :topic_template\n###\n\n# Create category\nnew_category = client.create_category(name: \"Test Category\", color: \"AB9364\", text_color: \"FFFFFF\")\nputs \"Created category: \" + new_category.to_s\n\n# Update category\nresponse =\n  client.update_category(\n    id: new_category[\"id\"],\n    name: \"The Best Test Category\",\n    slug: \"the-best-test-category\",\n    color: \"0E76BD\",\n    text_color: \"000000\",\n  )\nputs \"Updated category: \" + response.to_s\n"
  },
  {
    "path": "examples/create_user.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# create user\nuser =\n  client.create_user(\n    name: \"Bruce Wayne\",\n    email: \"bruce@wayne.com\",\n    username: \"batman\",\n    password: \"WhySoSerious\",\n  )\n\n# activate user\nclient.activate(user[\"user_id\"])\n"
  },
  {
    "path": "examples/dashboard.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# get all dashboard status as json\nputs client.get_dashboard_stats\n\n# get hash of some dashboard total value\nputs client.get_dashboard_stats_totals\n# sample output: {\"users\"=>9, \"topics\"=>230, \"posts\"=>441}\n"
  },
  {
    "path": "examples/disposable_invite_tokens.rb",
    "content": "# frozen_string_literal: true\n# requires this plugin => https://github.com/discourse/discourse-invite-tokens\n\nrequire \"csv\"\n\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# fetch email-less invite tokens\ninvite_tokens =\n  client.disposable_tokens(username: \"eviltrout\", quantity: 5, group_names: \"security,support\")\n\n# write to CSV file\nCSV.open(File.expand_path(\"../invite_tokens.csv\", __FILE__), \"w\") do |csv|\n  invite_tokens.each { |value| csv << [value] }\nend\n"
  },
  {
    "path": "examples/example.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# get latest topics\nputs client.latest_topics\n"
  },
  {
    "path": "examples/group_set_user_notification_level.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n@target_username = \"YOUR_TARGET_USERNAME\"\n@target_group_id = # YOUR NUMERIC TARGET GROUP ID\n  @user = client.user(@target_username)\n\n# each user's group and the group's default notification level are stored under user['groups']\n@user[\"groups\"].each do |group|\n  if group[\"id\"] == @target_group_id\n    @group_name = group[\"name\"]\n    @default_level = group[\"default_notification_level\"]\n  end\nend\n\n# and the user's notification setting for each group is stored under user['group_users]\n@user[\"group_users\"].each do |users_group|\n  if users_group[\"group_id\"] == @target_group_id\n    @notification_level = users_group[\"notification_level\"]\n    puts \"Group ID:#{@target_group_id} #{@group_name}    Current Notification Level: #{@notification_level}    Default: #{@default_level}\"\n    response = client.group_set_user_notification_level(@group_name, @user[\"id\"], @default_level)\n    puts response\n    @users_group_users_after_update = client.user(@target_username)[\"group_users\"]\n    # this just pulls the user from the database again to make sure we updated the user's group notification level\n    @users_group_users_after_update.each do |users_group_second_pass|\n      if users_group_second_pass[\"group_id\"] == @target_group_id\n        puts \"Updated ID:#{@target_group_id} #{@group_name}    Notification Level: #{users_group_second_pass[\"notification_level\"]}    Default: #{@default_level}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "examples/groups.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\nresponse = client.create_group(name: \"engineering_team\")\ngroup_id = response[\"basic_group\"][\"id\"]\n\nclient.group_add(group_id, username: \"sam\")\nclient.group_add(group_id, username: \"jeff\")\nclient.group_add(group_id, usernames: %w[neil dan])\nclient.group_add(group_id, user_id: 123)\nclient.group_add(group_id, user_ids: [123, 456])\n\nclient.group_remove(group_id, username: \"neil\")\nclient.group_remove(group_id, user_id: 123)\n\nclient.delete_group(group_id)\n\n## List users of a group\n\nmembers = client.group_members(\"trust_level_0\")\nputs members\n"
  },
  {
    "path": "examples/invite_users.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# invite user\ninvite = client.invite_user(email: \"name@example.com\", group_ids: \"41,42\")\n\n#update invite\nclient.update_invite(invite[\"id\"], email: \"namee@example.com\")\n\n# resend invite\nclient.resend_invite(\"namee@example.com\")\n\n# invite to a topic\nclient.invite_to_topic(1, email: \"foo@bar.com\")\n\n# if the user is an admin you may invite to a group as well\nclient.invite_to_topic(1, email: \"foo@bar.com\", group_ids: \"1,2,3\")\n\n# retrieve invite\nputs client.retrieve_invite(email: \"foo@bar.com\")\n\n# resend all invites\nclient.resend_all_invites\n\n# destroy invite\nclient.destroy_invite(invite[\"id\"])\n\n# destroy all expired invites\nclient.destroy_all_expired_invites\n"
  },
  {
    "path": "examples/manage_api_keys.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# generate user api key\nresponse = client.create_api_key(key: { description: \"Key to The Batmobile\", username: \"batman\" })\n\napi_key_id = response[\"key\"][\"id\"]\n\nputs response\n# sample output: {\"key\"=>{\"id\"=>13, \"key\"=>\"abc\", \"description\"=>\"Key to the Batmobile\"}}\n\nresponse = client.revoke_api_key(api_key_id)\n\nputs response\n# sample output: {\"key\"=>{\"id\"=>13, \"key\"=>\"abc\", \"description\"=>\"Key to the Batmobile\", \"revoked_at\"=>\"2021-01-01T00:00:00.000Z\"}}\n\nresponse = client.undo_revoke_api_key(api_key_id)\n\nputs response\n# sample output: {\"key\"=>{\"id\"=>13, \"key\"=>\"abc\", \"description\"=>\"Key to the Batmobile\", \"revoked_at\"=>nil}}\n\nresponse = client.list_api_keys\n\nputs response\n# sample output: {\"keys\"=>[{\"id\"=>13, \"key\"=>\"abc\", \"description\"=>\"Key to the Batmobile\"}]}\n\nresponse = client.delete_api_key(api_key_id)\n\nputs response\n# sample output: {\"success\"=>\"OK\"}\n"
  },
  {
    "path": "examples/notifications.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# watch an entire category\nclient.category_set_user_notification_level(1, notification_level: 3)\n\n# mute a topic\nclient.topic_set_user_notification_level(1, notification_level: 0)\n\n# get user notifications\nclient.notifications(username: \"discourse\")\n"
  },
  {
    "path": "examples/polls.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\noptions = [\"8b4736b1ae3dfb5a28088530f036f9e5\"]\npoll_option_votes = client.poll_vote post_id: 5, poll_name: \"poll\", options: options\nputs poll_option_votes.body[\"vote\"]\n\npoll_option_votes = client.toggle_poll_status(post_id: 5, poll_name: \"poll\", status: \"closed\")\nputs poll_option_votes[:body][\"poll\"][\"status\"]\n\npoll_option_votes = client.poll_voters(post_id: 5, poll_name: \"poll\")\nputs poll_option_votes[\"voters\"]\n"
  },
  {
    "path": "examples/post_action.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# Post Action Type IDs\n# 1 - Bookmark\n# 2 - Like\n# 3 - Flag: Off-topic\n# 4 - Flag: Inappropriate\n# 5 - Vote\n# 6 - Notify User\n# 7 - Flag - Notify Moderators\n# 8 - Flag - Spam\n\n# Like a post\nclient.create_post_action(id: 2, post_action_type_id: 2)\n\n# Flag a topic as spam\nclient.create_topic_action(id: 1, post_action_type_id: 8)\n\n# Unlike a post\nclient.destroy_post_action(id: 3, post_action_type_id: 2)\n"
  },
  {
    "path": "examples/search.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# search for term\nputs client.search(\"discourse\")\n"
  },
  {
    "path": "examples/sent_private_messages.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\nclient.sent_private_messages(\"test_user\")\n"
  },
  {
    "path": "examples/sso.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\nclient.sync_sso(\n  sso_secret: \"discourse_sso_rocks\",\n  name: \"Test Name\",\n  username: \"test_name\",\n  email: \"name@example.com\",\n  external_id: \"2\",\n)\n"
  },
  {
    "path": "examples/topic_lists.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# get latest topics\nputs client.latest_topics({})\n\n# get top topics\nputs client.top_topics\n\n# recategorize topic\nputs client.recategorize_topic(topic_id: 108, category_id: 5)\n\n# get all categories\nputs client.categories({})\n"
  },
  {
    "path": "examples/update_user.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# update username from \"robin\" to \"batman\"\nputs client.update_username(\"robin\", \"batman\")\n# update name of user whose username is \"batman\"\nputs client.update_user(\"batman\", name: \"Bruce Wayne\")\n# update email of user whose username is \"batman\"\nputs client.update_email(\"batman\", \"batman@gotham.com\")\n# update avatar of user whose username is \"batman\"\nputs client.update_avatar(\n       username: \"batman\",\n       url: \"http://meta-discourse.r.worldssl.net/uploads/default/2497/724a6ef2e79d2bc7.png\",\n     )\n# update trust level of user whose id is \"102\"\nputs client.update_trust_level(user_id: 102, level: 2)\n# update user bio, location or website\nputs client.update_user(\n       \"batman\",\n       bio_raw: \"I am Batman.\",\n       location: \"Gotham\",\n       website: \"https://en.wikipedia.org/wiki/Batman\",\n     )\n\n# log out everywhere and refresh browser of user whose id is \"2\"\nputs client.log_out(2)\n"
  },
  {
    "path": "examples/upload_file.rb",
    "content": "# frozen_string_literal: true\n$LOAD_PATH.unshift File.expand_path(\"../../lib\", __FILE__)\nrequire File.expand_path(\"../../lib/discourse_api\", __FILE__)\n\nconfig = DiscourseApi::ExampleHelper.load_yml\n\nclient = DiscourseApi::Client.new(config[\"host\"] || \"http://localhost:3000\")\nclient.api_key = config[\"api_key\"] || \"YOUR_API_KEY\"\nclient.api_username = config[\"api_username\"] || \"YOUR_USERNAME\"\n\n# Upload a file\nfile = Faraday::UploadIO.new(\"grumpy_cat.pdf\", \"application/pdf\")\nclient.upload_file(file: file)\n\n# Upload a file via URL\nclient.upload_file(url: \"https://giphy.com/grumpy_cat.gif\")\n"
  },
  {
    "path": "lib/discourse_api/api/api_key.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module ApiKey\n      def list_api_keys\n        response = get(\"/admin/api/keys\")\n        response[:body]\n      end\n\n      def create_api_key(args)\n        args = API.params(args).required(:key).to_h\n        post(\"/admin/api/keys\", args)\n      end\n\n      def revoke_api_key(id)\n        post(\"/admin/api/keys/#{id}/revoke\")\n      end\n\n      def undo_revoke_api_key(id)\n        post(\"/admin/api/keys/#{id}/undo-revoke\")\n      end\n\n      def delete_api_key(id)\n        delete(\"/admin/api/keys/#{id}\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/backups.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Backups\n      def backups\n        response = get(\"/admin/backups.json\")\n        response.body\n      end\n\n      def create_backup\n        post(\"/admin/backups\", with_uploads: true)\n      end\n\n      def restore_backup(file_name)\n        post(\"/admin/backups/#{file_name}/restore\")\n      end\n\n      def download_backup(file_name, destination)\n        response = get(\"/admin/backups/#{file_name}\")\n        # write file\n        File.open(\"#{destination}/#{file_name}\", \"wb\") { |fp| fp.write(response.body) }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/badges.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Badges\n      def badges\n        response = get(\"/admin/badges.json\")\n        response.body\n      end\n\n      def user_badges(username)\n        response = get(\"/user-badges/#{username}.json\")\n        response.body[\"badges\"]\n      end\n\n      def grant_user_badge(params = {})\n        post(\"/user_badges\", params)\n      end\n\n      def create_badge(params = {})\n        args =\n          API\n            .params(params)\n            .required(:name, :badge_type_id)\n            .optional(\n              :description,\n              :allow_title,\n              :multiple_grant,\n              :icon,\n              :listable,\n              :target_posts,\n              :query,\n              :enabled,\n              :auto_revoke,\n              :badge_grouping_id,\n              :trigger,\n              :show_posts,\n              :image,\n              :long_description,\n            )\n        post(\"/admin/badges.json\", args)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/categories.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Categories\n      # :color and :text_color are RGB hexadecimal strings\n      # :permissions is a hash with the group name and permission_type which is\n      # an integer 1 = Full 2 = Create Post 3 = Read Only\n      def create_category(args = {})\n        params = common_category_params(args)\n        response = post(\"/categories\", params.to_h)\n        response[\"category\"]\n      end\n\n      def update_category(args = {})\n        category_id = args[:id]\n        params = common_category_params(args, include_id: true)\n        response = put(\"/categories/#{category_id}\", params.to_h)\n        response[\"body\"][\"category\"] if response[\"body\"]\n      end\n\n      def reorder_categories(args = {})\n        params = API.params(args).required(:mapping)\n        post(\"/categories/reorder\", params)\n      end\n\n      def delete_category(id)\n        response = delete(\"/categories/#{id}\")\n        response[:body][\"success\"]\n      end\n\n      def categories(params = {})\n        categories_full(params)[\"category_list\"][\"categories\"]\n      end\n\n      def categories_full(params = {})\n        response = get(\"/categories.json\", params)\n        response[:body]\n      end\n\n      def category_latest_topics(args = {})\n        response = category_latest_topics_full(args)\n        if response[\"errors\"]\n          response[\"errors\"]\n        else\n          response[\"topic_list\"][\"topics\"]\n        end\n      end\n\n      def category_latest_topics_full(args = {})\n        params = API.params(args).required(:category_slug).optional(:page).to_h\n        url = \"/c/#{params[:category_slug]}/l/latest.json\"\n        url = \"#{url}?page=#{params[:page]}\" if params.include?(:page)\n        response = get(url)\n        response[:body]\n      end\n\n      def category_top_topics(category_slug)\n        response = category_top_topics_full(category_slug)\n        if response[\"errors\"]\n          response[\"errors\"]\n        else\n          response[\"topic_list\"][\"topics\"]\n        end\n      end\n\n      def category_top_topics_full(category_slug)\n        response = get(\"/c/#{category_slug}/l/top.json\")\n        response[:body]\n      end\n\n      def category_new_topics(category_slug)\n        response = category_new_topics_full(category_slug)\n        response[\"topic_list\"][\"topics\"]\n      end\n\n      def category_new_topics_full(category_slug)\n        response = get(\"/c/#{category_slug}/l/new.json\")\n        response[:body]\n      end\n\n      def category(id)\n        response = get(\"/c/#{id}/show\")\n        response[:body][\"category\"]\n      end\n\n      # TODO: Deprecated. Remove after 20210727\n      def category_set_user_notification(args = {})\n        category_id = args[:id]\n        args = API.params(args).required(:notification_level)\n        post(\"/category/#{category_id}/notifications\", args)\n      end\n\n      def category_set_user_notification_level(category_id, params)\n        params = API.params(params).required(:notification_level)\n        post(\"/category/#{category_id}/notifications\", params)\n      end\n\n      private\n\n      def common_category_params(args, include_id: false)\n        params = API.params(args)\n        params = params.required(:id) if include_id\n        params\n          .required(:name)\n          .optional(\n            :color,\n            :text_color,\n            :slug,\n            :permissions,\n            :auto_close_hours,\n            :auto_close_based_on_last_post,\n            :position,\n            :email_in,\n            :email_in_allow_strangers,\n            :logo_url,\n            :background_url,\n            :allow_badges,\n            :topic_template,\n            :custom_fields,\n            :description,\n            :reviewable_by_group_name,\n            :show_subcategory_list,\n            :subcategory_list_style,\n            :allowed_tags,\n            :allowed_tag_groups,\n            :required_tag_group_name,\n            :topic_featured_links_allowed,\n            :search_priority,\n            :form_template_ids,\n          )\n          .default(parent_category_id: nil)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/dashboard.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Dashboard\n      def get_dashboard_stats\n        response = get(\"admin/dashboard.json\")\n        response[:body]\n      end\n\n      def get_dashboard_stats_totals\n        stats = get_dashboard_stats\n        global_reports = stats[\"global_reports\"]\n        users = global_reports[1]\n        topics = global_reports[3]\n        posts = global_reports[4]\n\n        { \"users\" => users[\"total\"], \"topics\" => topics[\"total\"], \"posts\" => posts[\"total\"] }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/email.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Email\n      def email_settings\n        response = get(\"/admin/email.json\")\n        response.body\n      end\n\n      def list_email(filter)\n        response = get(\"/admin/email/#{filter}.json\")\n        response.body\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/groups.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Groups\n      def create_group(args)\n        args =\n          API\n            .params(args)\n            .required(:name)\n            .default(visibility_level: 0)\n            .optional(\n              :mentionable_level,\n              :messageable_level,\n              :automatic_membership_email_domains,\n              :automatic_membership_retroactive,\n              :title,\n              :primary_group,\n              :grant_trust_level,\n              :incoming_email,\n              :flair_url,\n              :flair_bg_color,\n              :flair_color,\n              :bio_raw,\n              :members_visibility_level,\n              :public_admission,\n              :public_exit,\n              :allow_membership_requests,\n              :full_name,\n              :default_notification_level,\n              :usernames,\n              :owner_usernames,\n              :membership_request_template,\n            )\n            .to_h\n        post(\"/admin/groups\", group: args)\n      end\n\n      def update_group(group_id, args)\n        args =\n          API\n            .params(args)\n            .default(visibility_level: 0)\n            .optional(\n              :mentionable_level,\n              :messageable_level,\n              :name,\n              :automatic_membership_email_domains,\n              :title,\n              :primary_group,\n              :grant_trust_level,\n              :incoming_email,\n              :flair_url,\n              :flair_bg_color,\n              :flair_color,\n              :bio_raw,\n              :visibility_level,\n              :public_admission,\n              :public_exit,\n              :allow_membership_requests,\n              :full_name,\n              :default_notification_level,\n              :membership_request_template,\n            )\n            .to_h\n        put(\"/groups/#{group_id}\", group: args)\n      end\n\n      def group_add_owners(group_id, args)\n        args = API.params(args).required(:usernames).to_h\n        put(\"/admin/groups/#{group_id}/owners.json\", group: args)\n      end\n\n      def group_remove_owners(group_id, args)\n        args = API.params(args).required(:usernames).to_h\n        delete(\"/admin/groups/#{group_id}/owners.json\", group: args)\n      end\n\n      def groups(args = {})\n        params = API.params(args).optional(:page).to_h\n\n        url = \"/groups.json\"\n        url += \"?page=#{params[:page]}\" if params.include?(:page)\n        response = get(url)\n        response.body\n      end\n\n      def group(group_name)\n        response = get(\"/groups/#{group_name}.json\")\n        response.body\n      end\n\n      def group_add(group_id, users)\n        users.keys.each do |key|\n          # Accept arrays and convert to comma-delimited string.\n          users[key] = users[key].join(\",\") if users[key].respond_to? :join\n\n          # Accept non-plural user_id or username, but send pluralized version in the request.\n          if key.to_s[-1] != \"s\"\n            users[\"#{key}s\"] = users[key]\n            users.delete(key)\n          end\n        end\n\n        put(\"/admin/groups/#{group_id}/members.json\", users)\n      end\n\n      def group_remove(group_id, users)\n        users.keys.each do |key|\n          # Accept arrays and convert to comma-delimited string.\n          users[key] = users[key].join(\",\") if users[key].respond_to? :join\n\n          # Accept non-plural user_id or username, but send pluralized version in the request.\n          if key.to_s[-1] != \"s\"\n            users[\"#{key}s\"] = users[key]\n            users.delete(key)\n          end\n        end\n\n        delete(\"/admin/groups/#{group_id}/members.json\", users)\n      end\n\n      def delete_group(group_id)\n        delete(\"/admin/groups/#{group_id}.json\")\n      end\n\n      def group_members(group_name, params = {})\n        options = params\n        params = API.params(params).optional(:offset, :limit).default(offset: 0, limit: 100).to_h\n        response = get(\"/groups/#{group_name}/members.json\", params)\n\n        if options[:all] == true\n          response.body\n        else\n          response.body[\"members\"]\n        end\n      end\n\n      def group_set_user_notification_level(group, user_id, notification_level)\n        post(\n          \"/groups/#{group}/notifications?user_id=#{user_id}&notification_level=#{notification_level}\",\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/invite.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Invite\n      def invite_user(params = {})\n        args =\n          API\n            .params(params)\n            .optional(\n              :email,\n              :skip_email,\n              :custom_message,\n              :max_redemptions_allowed,\n              :topic_id,\n              :group_ids,\n              :expires_at,\n            )\n            .to_h\n\n        post(\"/invites\", args)\n      end\n\n      # TODO: Deprecated. Remove after 20220506\n      def invite_user_to_topic(params = {})\n        deprecated(__method__, \"invite_to_topic\")\n        invite_to_topic(params[:topic_id], params)\n      end\n\n      def invite_to_topic(topic_id, params = {})\n        args = API.params(params).optional(:email, :user, :group_ids, :custom_message).to_h\n\n        post(\"/t/#{topic_id}/invite\", args)\n      end\n\n      def retrieve_invite(params = {})\n        args = API.params(params).required(:email).to_h\n\n        response = get(\"invites/retrieve.json\", args)\n\n        response.body\n      end\n\n      # requires this plugin => https://github.com/discourse/discourse-invite-tokens\n      def disposable_tokens(params = {})\n        post(\"/invite-token/generate\", params)\n      end\n\n      def update_invite(invite_id, params = {})\n        args =\n          API\n            .params(params)\n            .optional(\n              :topic_id,\n              :group_ids,\n              :group_names,\n              :email,\n              :send_email,\n              :custom_message,\n              :max_redemptions_allowed,\n              :expires_at,\n            )\n            .to_h\n\n        put(\"invites/#{invite_id}\", args)\n      end\n\n      def destroy_all_expired_invites\n        post(\"invites/destroy-all-expired\")\n      end\n\n      def resend_all_invites\n        post(\"invites/reinvite-all\")\n      end\n\n      def resend_invite(email)\n        post(\"invites/reinvite\", { email: email })\n      end\n\n      def destroy_invite(invite_id)\n        delete(\"/invites\", { id: invite_id })\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/notifications.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Notifications\n      def notifications(params = {})\n        params = API.params(params).optional(:username, :recent, :limit, :offset, :filter)\n\n        response = get(\"/notifications.json\", params)\n        response[:body]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/params.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    def self.params(args)\n      Params.new(args)\n    end\n\n    class Params\n      def initialize(args)\n        raise ArgumentError.new(\"Required to be initialized with a Hash\") unless args.is_a? Hash\n        @args = args\n        @required = []\n        @optional = []\n        @defaults = {}\n      end\n\n      def required(*keys)\n        @required.concat(keys)\n        @required.each do |k|\n          raise ArgumentError.new(\"#{k} is required but not specified\") unless @args.key?(k)\n        end\n        self\n      end\n\n      def optional(*keys)\n        @optional.concat(keys)\n        self\n      end\n\n      def default(args)\n        args.each { |k, v| @defaults[k] = v }\n        self\n      end\n\n      def to_h\n        h = {}\n\n        @required.each { |k| h[k] = @args[k] }\n\n        @optional.each { |k| h[k] = @args[k] if @args.include?(k) }\n\n        @defaults.each { |k, v| @args.key?(k) ? h[k] = @args[k] : h[k] = v }\n\n        h\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/polls.rb",
    "content": "# frozen_string_literal: true\n\nmodule DiscourseApi\n  module API\n    module Polls\n      def poll_vote(args)\n        args = API.params(args).required(:post_id, :poll_name, :options).optional(:created_at)\n        put(\"/polls/vote\", args)\n      end\n\n      def toggle_poll_status(args)\n        args =\n          API\n            .params(args)\n            .required(:post_id, :poll_name, :status)\n            .optional(:api_username)\n            .optional(:raise_errors)\n        put(\"/polls/toggle_status\", args)\n      end\n\n      def poll_voters(args)\n        args = API.params(args).required(:post_id, :poll_name).optional(:opts)\n        response = get(\"/polls/voters.json\", args)\n        response[:body]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/posts.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Posts\n      def create_post(args)\n        args = API.params(args).required(:topic_id, :raw).optional(:created_at, :api_username)\n        post(\"/posts\", args)\n      end\n\n      def create_post_action(args)\n        args = API.params(args).required(:id, :post_action_type_id)\n        post(\"/post_actions\", args.to_h.merge(flag_topic: false))\n      end\n\n      def get_post(id, args = {})\n        args = API.params(args).optional(:version)\n        response = get(\"/posts/#{id}.json\", args)\n        response[:body]\n      end\n\n      def posts(args = {})\n        args = API.params(args).default(before: 0)\n        response = get(\"/posts.json\", args)\n        response[:body]\n      end\n\n      def wikify_post(id)\n        put(\"/posts/#{id}/wiki\", wiki: true)\n      end\n\n      def edit_post(id, raw)\n        put(\"/posts/#{id}\", post: { raw: raw })\n      end\n\n      def delete_post(id)\n        delete(\"/posts/#{id}.json\")\n      end\n\n      def destroy_post_action(post_id, post_action_type_id)\n        delete(\"/post_actions/#{post_id}.json\", post_action_type_id: post_action_type_id)\n      end\n\n      def post_action_users(post_id, post_action_type_id)\n        response =\n          get(\"/post_action_users.json\", id: post_id, post_action_type_id: post_action_type_id)\n        response[:body]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/private_messages.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module PrivateMessages\n      # TODO: Deprecated. Remove after 20220628\n      def create_private_message(args = {})\n        deprecated(__method__, \"create_pm\")\n        args[:target_recipients] = args.delete :target_usernames\n        create_pm(args.to_h)\n      end\n\n      # :target_recipients REQUIRED comma separated list of usernames\n      # :category OPTIONAL name of category, not ID\n      # :created_at OPTIONAL seconds since epoch.\n      def create_pm(args = {})\n        args[:archetype] = \"private_message\"\n        args =\n          API\n            .params(args)\n            .required(:title, :raw, :target_recipients, :archetype)\n            .optional(:category, :created_at, :api_username)\n        post(\"/posts\", args.to_h)\n      end\n\n      def private_messages(username, *args)\n        response = get(\"topics/private-messages/#{username}.json\", args)\n        response[:body][\"topic_list\"][\"topics\"]\n      end\n\n      def sent_private_messages(username, *args)\n        response = get(\"topics/private-messages-sent/#{username}.json\", args)\n        response[:body][\"topic_list\"][\"topics\"]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/search.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Search\n      # Returns search results that match the specified term.\n      #\n      # @param term [String] a search term\n      # @param options [Hash] A customizable set of options\n      # @option options [String] :type_filter Returns results of the specified type.\n      # @return [Array] Return results as an array of Hashes.\n      def search(term, options = {})\n        raise ArgumentError.new(\"#{term} is required but not specified\") unless term\n        raise ArgumentError.new(\"#{term} is required but not specified\") if term.empty?\n\n        response = get(\"/search\", options.merge(q: term))\n        response[:body]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/site_settings.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module SiteSettings\n      def site_setting_update(args = {})\n        params = API.params(args).required(:name, :value).to_h\n        new_site_setting = { params[:name] => params[:value] }\n\n        put(\"/admin/site_settings/#{params[:name]}\", new_site_setting)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/sso.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module SSO\n      def sync_sso(params = {})\n        sso = DiscourseApi::SingleSignOn.parse_hash(params)\n\n        post(\"/admin/users/sync_sso\", sso.payload)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/tags.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Tags\n      def show_tag(tag)\n        response = get(\"/tag/#{tag}\")\n        response[:body]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/topics.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Topics\n      # :category OPTIONAL name of category, not ID\n      # :skip_validations OPTIONAL boolean\n      # :auto_track OPTIONAL boolean\n      # :created_at OPTIONAL seconds since epoch.\n      def create_topic(args = {})\n        args =\n          API\n            .params(args)\n            .required(:title, :raw)\n            .optional(\n              :skip_validations,\n              :category,\n              :auto_track,\n              :created_at,\n              :api_username,\n              :tags,\n              :external_id,\n              :visible,\n            )\n        post(\"/posts\", args.to_h)\n      end\n\n      def create_topic_action(args)\n        args = API.params(args).required(:id, :post_action_type_id)\n        post(\"/post_actions\", args.to_h.merge(flag_topic: true))\n      end\n\n      # timestamp is seconds past the epoch.\n      def edit_topic_timestamp(topic_id, timestamp)\n        put(\"/t/#{topic_id}/change-timestamp\", timestamp: timestamp)\n      end\n\n      def latest_topics(params = {})\n        response = get(\"/latest.json\", params)\n        response[:body][\"topic_list\"][\"topics\"]\n      end\n\n      def top_topics(params = {})\n        response = get(\"/top.json\", params)\n        response[:body][\"topic_list\"][\"topics\"]\n      end\n\n      def new_topics(params = {})\n        response = get(\"/new.json\", params)\n        response[:body][\"topic_list\"][\"topics\"]\n      end\n\n      def rename_topic(topic_id, title)\n        put(\"/t/#{topic_id}.json\", topic_id: topic_id, title: title)\n      end\n\n      def recategorize_topic(topic_id, category_id)\n        put(\"/t/#{topic_id}.json\", topic_id: topic_id, category_id: category_id)\n      end\n\n      # TODO: Deprecated. Remove after 20201231\n      def change_topic_status(topic_slug, topic_id, params = {})\n        deprecated(__method__, \"update_topic_status\")\n        update_topic_status(topic_id, params)\n      end\n\n      def update_topic_status(topic_id, params = {})\n        params = API.params(params).required(:status, :enabled).optional(:api_username)\n        put(\"/t/#{topic_id}/status\", params)\n      end\n\n      def topic(id, params = {})\n        response = get(\"/t/#{id}.json\", params)\n        response[:body]\n      end\n\n      def topics_by(username, params = {})\n        response = get(\"/topics/created-by/#{username}.json\", params)\n        response[:body][\"topic_list\"][\"topics\"]\n      end\n\n      def delete_topic(id)\n        delete(\"/t/#{id}.json\")\n      end\n\n      def topic_posts(topic_id, post_ids = [], params = {})\n        params =\n          API.params(params).optional(\n            :asc,\n            :filter,\n            :include_raw,\n            :include_suggested,\n            :post_number,\n            :username_filters,\n          )\n\n        url = [\"/t/#{topic_id}/posts.json\"]\n        if post_ids.count > 0\n          url.push(\"?\")\n          url.push(post_ids.map { |id| \"post_ids[]=#{id}\" }.join(\"&\"))\n        end\n        response = get(url.join, params)\n        response[:body]\n      end\n\n      def change_owner(topic_id, params = {})\n        params = API.params(params).required(:username, :post_ids)\n\n        post(\"/t/#{topic_id}/change-owner.json\", params)\n      end\n\n      def topic_set_user_notification_level(topic_id, params)\n        params = API.params(params).required(:notification_level)\n        post(\"/t/#{topic_id}/notifications\", params)\n      end\n\n      def bookmark_topic(topic_id)\n        put(\"/t/#{topic_id}/bookmark.json\")\n      end\n\n      def remove_topic_bookmark(topic_id)\n        put(\"/t/#{topic_id}/remove_bookmarks.json\")\n      end\n\n      def get_topic_url_by_external_id(external_id)\n        get(\"/t/external_id/#{external_id}\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/uploads.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Uploads\n      def upload_file(args)\n        args =\n          API\n            .params(args)\n            .optional(:file, :url, :user_id)\n            .default(type: \"composer\", synchronous: true)\n            .to_h\n        post(\"/uploads\", args)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/user_actions.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module UserActions\n      def user_replies(username)\n        params = { username: username, filter: \"5\" }\n        response = get(\"/user_actions.json\", params)\n        response.body[\"user_actions\"]\n      end\n\n      def user_topics_and_replies(username)\n        params = { username: username, filter: \"4,5\" }\n        response = get(\"/user_actions.json\", params)\n        response.body[\"user_actions\"]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/api/users.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  module API\n    module Users\n      def activate(id)\n        put(\"/admin/users/#{id}/activate\")\n      end\n\n      def user(username, params = {})\n        response = get(\"/users/#{username}.json\", params)\n        response[:body][\"user\"]\n      end\n\n      def user_sso(user_id)\n        response = get(\"/admin/users/#{user_id}.json\")\n        response[:body][\"single_sign_on_record\"]\n      end\n\n      def update_avatar(username, args)\n        args =\n          API.params(args).optional(:file, :url).default(type: \"avatar\", synchronous: true).to_h\n        upload_response = post(\"/uploads\", args)\n        put(\"/u/#{username}/preferences/avatar/pick\", upload_id: upload_response[\"id\"])\n      end\n\n      def update_email(username, email)\n        put(\"/u/#{username}/preferences/email\", email: email)\n      end\n\n      def update_user(username, args)\n        args =\n          API\n            .params(args)\n            .optional(\n              :name,\n              :title,\n              :bio_raw,\n              :location,\n              :website,\n              :profile_background,\n              :card_background,\n              :email_messages_level,\n              :mailing_list_mode,\n              :homepage_id,\n              :theme_ids,\n              :user_fields,\n            )\n            .to_h\n        put(\"/u/#{username}\", args)\n      end\n\n      def update_username(username, new_username)\n        put(\"/u/#{username}/preferences/username\", new_username: new_username)\n      end\n\n      def update_trust_level(user_id, args)\n        args = API.params(args).required(:level).to_h\n        response = put(\"/admin/users/#{user_id}/trust_level\", args)\n        response[:body]\n      end\n\n      def create_user(args)\n        args =\n          API\n            .params(args)\n            .required(:name, :email, :password, :username)\n            .optional(:active, :approved, :staged, :user_fields)\n            .to_h\n        post(\"/users\", args)\n      end\n\n      def log_out(id)\n        post(\"/admin/users/#{id}/log_out\")\n      end\n\n      def list_users(type, params = {})\n        response = get(\"admin/users/list/#{type}.json\", params)\n        response[:body]\n      end\n\n      def grant_admin(user_id)\n        response = put(\"admin/users/#{user_id}/grant_admin\")\n        response[:body]\n      end\n\n      def revoke_admin(user_id)\n        response = put(\"admin/users/#{user_id}/revoke_admin\")\n        response[:body]\n      end\n\n      def grant_moderation(user_id)\n        response = put(\"admin/users/#{user_id}/grant_moderation\")\n        response[:body]\n      end\n\n      def revoke_moderation(user_id)\n        put(\"admin/users/#{user_id}/revoke_moderation\")\n      end\n\n      def by_external_id(external_id)\n        response = get(\"/users/by-external/#{external_id}\")\n        response[:body][\"user\"]\n      end\n\n      def suspend(user_id, suspend_until, reason)\n        put(\"/admin/users/#{user_id}/suspend\", suspend_until: suspend_until, reason: reason)\n      end\n\n      def unsuspend(user_id)\n        put(\"/admin/users/#{user_id}/unsuspend\")\n      end\n\n      def anonymize(user_id)\n        put(\"/admin/users/#{user_id}/anonymize\")\n      end\n\n      def delete_user(user_id, delete_posts = false)\n        delete(\"/admin/users/#{user_id}.json?delete_posts=#{delete_posts}\")\n      end\n\n      def check_username(username)\n        response = get(\"/users/check_username.json?username=#{CGI.escape(username)}\")\n        response[:body]\n      end\n\n      def deactivate(user_id)\n        put(\"/admin/users/#{user_id}/deactivate\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/client.rb",
    "content": "# frozen_string_literal: true\nrequire \"faraday\"\nrequire \"faraday/follow_redirects\"\nrequire \"faraday/multipart\"\nrequire \"json\"\nrequire \"uri\"\nrequire \"discourse_api/version\"\nrequire \"discourse_api/api/categories\"\nrequire \"discourse_api/api/search\"\nrequire \"discourse_api/api/sso\"\nrequire \"discourse_api/api/tags\"\nrequire \"discourse_api/api/topics\"\nrequire \"discourse_api/api/polls\"\nrequire \"discourse_api/api/posts\"\nrequire \"discourse_api/api/users\"\nrequire \"discourse_api/api/groups\"\nrequire \"discourse_api/api/invite\"\nrequire \"discourse_api/api/private_messages\"\nrequire \"discourse_api/api/notifications\"\nrequire \"discourse_api/api/badges\"\nrequire \"discourse_api/api/email\"\nrequire \"discourse_api/api/api_key\"\nrequire \"discourse_api/api/backups\"\nrequire \"discourse_api/api/dashboard\"\nrequire \"discourse_api/api/uploads\"\nrequire \"discourse_api/api/user_actions\"\nrequire \"discourse_api/api/site_settings\"\n\nmodule DiscourseApi\n  class Client\n    attr_accessor :api_key\n    attr_accessor :basic_auth\n    attr_reader :host, :api_username, :timeout\n\n    DEFAULT_TIMEOUT = 30\n\n    include DiscourseApi::API::Categories\n    include DiscourseApi::API::Search\n    include DiscourseApi::API::SSO\n    include DiscourseApi::API::Tags\n    include DiscourseApi::API::Topics\n    include DiscourseApi::API::Polls\n    include DiscourseApi::API::Posts\n    include DiscourseApi::API::Users\n    include DiscourseApi::API::Groups\n    include DiscourseApi::API::Invite\n    include DiscourseApi::API::PrivateMessages\n    include DiscourseApi::API::Notifications\n    include DiscourseApi::API::Badges\n    include DiscourseApi::API::Email\n    include DiscourseApi::API::ApiKey\n    include DiscourseApi::API::Backups\n    include DiscourseApi::API::Dashboard\n    include DiscourseApi::API::Uploads\n    include DiscourseApi::API::UserActions\n    include DiscourseApi::API::SiteSettings\n\n    def initialize(host, api_key = nil, api_username = nil)\n      raise ArgumentError, \"host needs to be defined\" if host.nil? || host.empty?\n      @host = host\n      @api_key = api_key\n      @api_username = api_username\n      @use_relative = check_subdirectory(host)\n    end\n\n    def timeout=(timeout)\n      @timeout = timeout\n      @connection.options.timeout = timeout if @connection\n    end\n\n    def api_username=(api_username)\n      @api_username = api_username\n      @connection.headers[\"Api-Username\"] = api_username unless @connection.nil?\n    end\n\n    def connection_options\n      @connection_options ||= {\n        url: @host,\n        request: {\n          timeout: @timeout || DEFAULT_TIMEOUT,\n        },\n        headers: {\n          accept: \"application/json\",\n          user_agent: user_agent,\n        },\n      }\n    end\n\n    def ssl(options)\n      connection_options[:ssl] = options\n    end\n\n    def delete(path, params = {})\n      request(:delete, path, params)\n    end\n\n    def get(path, params = {})\n      request(:get, path, params)\n    end\n\n    def post(path, params = {})\n      response = request(:post, path, params)\n      case response.status\n      when 200, 201, 204\n        response.body\n      else\n        raise DiscourseApi::Error, response.body\n      end\n    end\n\n    def put(path, params = {})\n      request(:put, path, params)\n    end\n\n    def patch(path, params = {})\n      request(:patch, path, params)\n    end\n\n    def user_agent\n      @user_agent ||= \"DiscourseAPI Ruby Gem #{DiscourseApi::VERSION}\"\n    end\n\n    def deprecated(old, new)\n      warn \"[DEPRECATED]: `#{old}` is deprecated. Please use `#{new}` instead.\"\n    end\n\n    private\n\n    def connection\n      @connection ||=\n        Faraday.new connection_options do |conn|\n          # Allow uploading of files\n          conn.request :multipart\n\n          # Convert request params to \"www-form-encoded\"\n          conn.request :url_encoded\n\n          # Allow to interact with forums behind basic HTTP authentication\n          if basic_auth\n            conn.request :authorization, :basic, basic_auth[:user], basic_auth[:password]\n          end\n\n          # Follow redirects\n          conn.response :follow_redirects, limit: 5\n\n          # Parse responses as JSON\n          conn.response :json, content_type: \"application/json\"\n\n          # For HTTP debugging, uncomment\n          # conn.response :logger\n\n          # Use Faraday's default HTTP adapter\n          conn.adapter Faraday.default_adapter\n\n          # Pass api_key and api_username on every request\n          unless api_username.nil?\n            conn.headers[\"Api-Key\"] = api_key\n            conn.headers[\"Api-Username\"] = api_username\n          end\n        end\n    end\n\n    def request(method, path, params = {})\n      unless Hash === params\n        params = params.to_h if params.respond_to? :to_h\n      end\n      path = @use_relative ? path.sub(%r{^/}, \"\") : path\n      response = connection.send(method.to_sym, path, params)\n      handle_error(response)\n      response.env\n    rescue Faraday::ClientError, JSON::ParserError\n      raise DiscourseApi::Error\n    rescue Faraday::ConnectionFailed\n      raise DiscourseApi::Timeout\n    end\n\n    def handle_error(response)\n      case response.status\n      when 403\n        raise DiscourseApi::UnauthenticatedError.new(response.env[:body], response.env)\n      when 404, 410\n        raise DiscourseApi::NotFoundError.new(response.env[:body], response.env)\n      when 422\n        raise DiscourseApi::UnprocessableEntity.new(response.env[:body], response.env)\n      when 429\n        raise DiscourseApi::TooManyRequests.new(response.env[:body], response.env)\n      when 500...600\n        raise DiscourseApi::Error.new(response.env[:body])\n      end\n    end\n\n    def check_subdirectory(host)\n      URI(host).request_uri != \"/\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/error.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  class DiscourseError < StandardError\n    attr_reader :response\n\n    def initialize(message, response = nil)\n      super(message)\n      @response = response\n    end\n  end\n\n  class Error < DiscourseError\n    attr_reader :wrapped_exception\n\n    # Initializes a new Error object\n    #\n    # @param exception [Exception, String]\n    # @return [DiscourseApi::Error]\n    def initialize(exception = $!)\n      @wrapped_exception = exception\n      exception.respond_to?(:message) ? super(exception.message) : super(exception.to_s)\n    end\n  end\n\n  class UnauthenticatedError < DiscourseError\n  end\n\n  class NotFoundError < DiscourseError\n  end\n\n  class UnprocessableEntity < DiscourseError\n  end\n\n  class TooManyRequests < DiscourseError\n  end\n\n  class Timeout < Error\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/example_helper.rb",
    "content": "# frozen_string_literal: true\nrequire \"yaml\"\n\nmodule DiscourseApi\n  class ExampleHelper\n    def self.load_yml\n      config_yml = File.expand_path(\"../../../config.yml\", __FILE__)\n      puts config_yml\n      begin\n        config = YAML.load_file config_yml\n      rescue Errno::ENOENT\n        config = {}\n      end\n      config\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/single_sign_on.rb",
    "content": "# frozen_string_literal: true\nrequire \"base64\"\nrequire \"rack\"\nrequire \"openssl\"\n\nmodule DiscourseApi\n  class SingleSignOn\n    class ParseError < RuntimeError\n    end\n    class MissingConfigError < RuntimeError\n    end\n\n    ACCESSORS = %i[\n      add_groups\n      admin\n      avatar_force_update\n      avatar_url\n      bio\n      card_background_url\n      confirmed_2fa\n      email\n      external_id\n      groups\n      locale\n      locale_force_update\n      moderator\n      name\n      no_2fa_methods\n      nonce\n      profile_background_url\n      remove_groups\n      require_2fa\n      require_activation\n      return_sso_url\n      suppress_welcome_message\n      title\n      username\n    ]\n\n    FIXNUMS = []\n\n    BOOLS = %i[\n      admin\n      avatar_force_update\n      confirmed_2fa\n      locale_force_update\n      moderator\n      no_2fa_methods\n      require_2fa\n      require_activation\n      suppress_welcome_message\n    ]\n    ARRAYS = [:groups]\n    #NONCE_EXPIRY_TIME = 10.minutes # minutes is a rails method and is causing an error. Is this needed in the api?\n\n    attr_accessor(*ACCESSORS)\n    attr_writer :custom_fields, :sso_secret, :sso_url\n\n    def self.sso_secret\n      raise MissingConfigError, \"sso_secret not implemented on class, be sure to set it on instance\"\n    end\n\n    def self.sso_url\n      raise MissingConfigError, \"sso_url not implemented on class, be sure to set it on instance\"\n    end\n\n    def self.parse_hash(payload)\n      sso = new\n\n      sso.sso_secret = payload.delete(:sso_secret)\n      sso.sso_url = payload.delete(:sso_url)\n\n      ACCESSORS.each do |k|\n        val = payload[k]\n\n        val = val.to_i if FIXNUMS.include? k\n        val = %w[true false].include?(val) ? val == \"true\" : nil if BOOLS.include? k\n        val = val.split(\",\") if ARRAYS.include?(k) && !val.nil?\n        sso.send(\"#{k}=\", val)\n      end\n\n      # Set custom_fields\n      sso.custom_fields = payload[:custom_fields]\n\n      # Add custom_fields (old format)\n      payload.each do |k, v|\n        if field = k[/^custom\\.(.+)$/, 1]\n          # Maintain adding of .custom bug\n          sso.custom_fields[\"custom.#{field}\"] = v\n        end\n      end\n\n      sso\n    end\n\n    def self.parse(payload, sso_secret = nil)\n      sso = new\n      sso.sso_secret = sso_secret if sso_secret\n\n      parsed = Rack::Utils.parse_query(payload)\n      if parsed[\"sso\"].nil? || sso.sign(parsed[\"sso\"]) != parsed[\"sig\"]\n        diags =\n          \"\\n\\nsso: #{parsed[\"sso\"].inspect}\\n\\nsig: #{parsed[\"sig\"].inspect}\\n\\nexpected sig: #{sso.sign(parsed.fetch(\"sso\", \"\"))}\"\n        if parsed[\"sso\"].nil? || parsed[\"sso\"] =~ %r{[^a-zA-Z0-9=\\r\\n/+]}m\n          raise ParseError,\n                \"The SSO field should be Base64 encoded, using only A-Z, a-z, 0-9, +, /, and = characters. Your input contains characters we don't understand as Base64, see http://en.wikipedia.org/wiki/Base64 #{diags}\"\n        else\n          raise ParseError, \"Bad signature for payload #{diags}\"\n        end\n      end\n\n      decoded = Base64.decode64(parsed[\"sso\"])\n      decoded_hash = Rack::Utils.parse_query(decoded)\n\n      ACCESSORS.each do |k|\n        val = decoded_hash[k.to_s]\n        val = val.to_i if FIXNUMS.include? k\n        val = %w[true false].include?(val) ? val == \"true\" : nil if BOOLS.include? k\n        val = val.split(\",\") if ARRAYS.include?(k) && !val.nil?\n        sso.send(\"#{k}=\", val)\n      end\n\n      decoded_hash.each do |k, v|\n        if field = k[/^custom\\.(.+)$/, 1]\n          sso.custom_fields[field] = v\n        end\n      end\n\n      sso\n    end\n\n    def diagnostics\n      DiscourseApi::SingleSignOn::ACCESSORS.map { |a| \"#{a}: #{send(a)}\" }.join(\"\\n\")\n    end\n\n    def sso_secret\n      @sso_secret || self.class.sso_secret\n    end\n\n    def sso_url\n      @sso_url || self.class.sso_url\n    end\n\n    def custom_fields\n      @custom_fields ||= {}\n    end\n\n    def sign(payload)\n      OpenSSL::HMAC.hexdigest(\"sha256\", sso_secret, payload)\n    end\n\n    def to_url(base_url = nil)\n      base = \"#{base_url || sso_url}\"\n      \"#{base}#{base.include?(\"?\") ? \"&\" : \"?\"}#{payload}\"\n    end\n\n    def payload\n      payload = Base64.strict_encode64(unsigned_payload)\n      \"sso=#{CGI.escape(payload)}&sig=#{sign(payload)}\"\n    end\n\n    def unsigned_payload\n      payload = {}\n\n      ACCESSORS.each do |k|\n        next if (val = send k) == nil\n        payload[k] = val\n      end\n\n      @custom_fields.each { |k, v| payload[\"custom.#{k}\"] = v.to_s } if @custom_fields\n\n      Rack::Utils.build_query(payload)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/discourse_api/version.rb",
    "content": "# frozen_string_literal: true\nmodule DiscourseApi\n  VERSION = \"2.1.0\"\nend\n"
  },
  {
    "path": "lib/discourse_api.rb",
    "content": "# frozen_string_literal: true\nrequire \"discourse_api/api/params\"\nrequire \"discourse_api/client\"\nrequire \"discourse_api/error\"\nrequire \"discourse_api/version\"\nrequire \"discourse_api/example_helper\"\nrequire \"discourse_api/single_sign_on\"\n"
  },
  {
    "path": "spec/discourse_api/api/api_key_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::ApiKey do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#list_api_keys\" do\n    before do\n      url = \"#{host}/admin/api/keys\"\n      stub_get(url).to_return(\n        body: fixture(\"list_api_keys.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.list_api_keys\n      url = \"#{host}/admin/api/keys\"\n      expect(a_get(url)).to have_been_made\n    end\n\n    it \"returns the requested api keys\" do\n      keys = client.list_api_keys\n      expect(keys[\"keys\"]).to be_an Array\n      expect(keys[\"keys\"].first).to be_a Hash\n      expect(keys[\"keys\"].first).to have_key(\"key\")\n    end\n  end\n\n  describe \"#create_api_key\" do\n    before do\n      url = \"#{host}/admin/api/keys\"\n      stub_post(url).to_return(\n        body: fixture(\"api_key.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.create_api_key(key: { username: \"robin\" })\n      url = \"#{host}/admin/api/keys\"\n      expect(a_post(url)).to have_been_made\n    end\n\n    it \"returns the generated api key\" do\n      api_key = client.create_api_key(key: { username: \"robin\" })\n      expect(api_key).to be_a Hash\n      expect(api_key[\"key\"]).to have_key(\"key\")\n    end\n  end\n\n  describe \"#revoke_api_key\" do\n    before do\n      url = \"#{host}/admin/api/keys/10/revoke\"\n      stub_post(url).to_return(\n        body: fixture(\"api_key.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.revoke_api_key(10)\n      url = \"#{host}/admin/api/keys/10/revoke\"\n      expect(a_post(url)).to have_been_made\n    end\n\n    it \"returns the api key\" do\n      api_key = client.revoke_api_key(10)\n      expect(api_key[\"key\"]).to have_key(\"key\")\n    end\n  end\n\n  describe \"#undo_revoke_api_key\" do\n    before do\n      url = \"#{host}/admin/api/keys/10/undo-revoke\"\n      stub_post(url).to_return(\n        body: fixture(\"api_key.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.undo_revoke_api_key(10)\n      url = \"#{host}/admin/api/keys/10/undo-revoke\"\n      expect(a_post(url)).to have_been_made\n    end\n\n    it \"returns the api key\" do\n      api_key = client.undo_revoke_api_key(10)\n      expect(api_key[\"key\"]).to have_key(\"key\")\n    end\n  end\n\n  describe \"#delete_api_key\" do\n    before do\n      url = \"#{host}/admin/api/keys/10\"\n      stub_delete(url).to_return(\n        body: '{\"success\": \"OK\"}',\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.delete_api_key(10)\n      url = \"#{host}/admin/api/keys/10\"\n      expect(a_delete(url)).to have_been_made\n    end\n\n    it \"returns 200\" do\n      response = client.delete_api_key(10)\n      expect(response[\"status\"]).to eq(200)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/backups_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Backups do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#backups\" do\n    before do\n      stub_get(\"#{host}/admin/backups.json\").to_return(\n        body: fixture(\"backups.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.backups\n      expect(a_get(\"#{host}/admin/backups.json\")).to have_been_made\n    end\n\n    it \"returns the requested backups\" do\n      backups = client.backups\n      expect(backups).to be_an Array\n      expect(backups.first).to be_a Hash\n      expect(backups.first).to have_key(\"filename\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/badges_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Badges do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#badges\" do\n    before do\n      stub_get(\"#{host}/admin/badges.json\").to_return(\n        body: fixture(\"badges.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.badges\n      expect(a_get(\"#{host}/admin/badges.json\")).to have_been_made\n    end\n\n    it \"returns the requested badges\" do\n      badges = client.badges\n      expect(badges).to be_a Hash\n      expect(badges[\"badges\"]).to be_an Array\n    end\n  end\n\n  describe \"#user-badges\" do\n    before do\n      stub_get(\"#{host}/user-badges/test_user.json\").to_return(\n        body: fixture(\"user_badges.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.user_badges(\"test_user\")\n      expect(a_get(\"#{host}/user-badges/test_user.json\")).to have_been_made\n    end\n\n    it \"returns the requested user badges\" do\n      badges = client.user_badges(\"test_user\")\n      expect(badges).to be_an Array\n      expect(badges.first).to be_a Hash\n      expect(badges.first).to have_key(\"badge_type_id\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/categories_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Categories do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#categories\" do\n    before do\n      stub_get(\"#{host}/categories.json\").to_return(\n        body: fixture(\"categories.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.categories\n      expect(a_get(\"#{host}/categories.json\")).to have_been_made\n    end\n\n    it \"returns the requested categories\" do\n      categories = client.categories\n      expect(categories).to be_an Array\n      expect(categories.first).to be_a Hash\n    end\n\n    it \"returns the requested categories with hash arg\" do\n      categories = client.categories({})\n      expect(categories).to be_an Array\n      expect(categories.first).to be_a Hash\n    end\n  end\n\n  describe \"#categories_full\" do\n    before do\n      stub_get(\"#{host}/categories.json\").to_return(\n        body: fixture(\"categories.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.categories\n      expect(a_get(\"#{host}/categories.json\")).to have_been_made\n    end\n\n    it \"returns the entire categories response\" do\n      categories = client.categories_full\n      expect(categories).to be_a Hash\n      expect(categories).to have_key \"category_list\"\n      expect(categories).to have_key \"featured_users\"\n    end\n  end\n\n  describe \"#category_latest_topics\" do\n    before do\n      stub_get(\"#{host}/c/category-slug/l/latest.json\").to_return(\n        body: fixture(\"category_latest_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"returns the latest topics in a category\" do\n      latest_topics = client.category_latest_topics(category_slug: \"category-slug\")\n      expect(latest_topics).to be_an Array\n    end\n  end\n\n  describe \"#category_latest_topics_full\" do\n    before do\n      stub_get(\"#{host}/c/category-slug/l/latest.json\").to_return(\n        body: fixture(\"category_latest_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"returns the entire latest topics in a category response\" do\n      latest_topics = client.category_latest_topics_full(category_slug: \"category-slug\")\n      expect(latest_topics).to be_a Hash\n      expect(latest_topics).to have_key \"topic_list\"\n      expect(latest_topics).to have_key \"users\"\n    end\n  end\n\n  describe \"#category_top_topics\" do\n    before do\n      stub_get(\"#{host}/c/category-slug/l/top.json\").to_return(\n        body: fixture(\"category_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"returns the top topics in a category\" do\n      topics = client.category_top_topics(\"category-slug\")\n      expect(topics).to be_an Array\n    end\n  end\n\n  describe \"#category_top_topics_full\" do\n    before do\n      stub_get(\"#{host}/c/category-slug/l/top.json\").to_return(\n        body: fixture(\"category_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"returns the entire top topics in a category response\" do\n      topics = client.category_top_topics_full(\"category-slug\")\n      expect(topics).to be_a Hash\n      expect(topics).to have_key \"topic_list\"\n      expect(topics).to have_key \"users\"\n    end\n  end\n\n  describe \"#category_new_topics\" do\n    before do\n      stub_get(\"#{host}/c/category-slug/l/new.json\").to_return(\n        body: fixture(\"category_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"returns the new topics in a category\" do\n      topics = client.category_new_topics(\"category-slug\")\n      expect(topics).to be_an Array\n    end\n  end\n\n  describe \"#category_new_topics_full\" do\n    before do\n      stub_get(\"#{host}/c/category-slug/l/new.json\").to_return(\n        body: fixture(\"category_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"returns the new topics in a category\" do\n      topics = client.category_new_topics_full(\"category-slug\")\n      expect(topics).to be_a Hash\n      expect(topics).to have_key \"topic_list\"\n      expect(topics).to have_key \"users\"\n    end\n  end\n\n  describe \"#category_new_category\" do\n    before do\n      stub_post(\"#{host}/categories\")\n      client.create_category(\n        name: \"test_category\",\n        color: \"283890\",\n        text_color: \"FFFFFF\",\n        description: \"This is a description\",\n        permissions: {\n          \"group_1\" => 1,\n          \"admins\" => 1,\n        },\n      )\n    end\n\n    it \"makes a create category request\" do\n      expect(\n        a_post(\"#{host}/categories\").with(\n          body:\n            \"color=283890&description=This+is+a+description&name=test_category&parent_category_id&permissions%5Badmins%5D=1&permissions%5Bgroup_1%5D=1&text_color=FFFFFF\",\n        ),\n      ).to have_been_made\n    end\n  end\n\n  describe \"#reorder_categories\" do\n    before do\n      stub_post(\"#{host}/categories/reorder\").to_return(\n        body: fixture(\"notification_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes a categories reordering request\" do\n      payload = { \"1\": 2, \"2\": 3, \"3\": 4, \"4\": 1 }\n      response = client.reorder_categories(mapping: payload.to_json)\n      expect(\n        a_post(\"#{host}/categories/reorder\").with(\n          body: \"mapping=#{CGI.escape(payload.to_json.to_s)}\",\n        ),\n      ).to have_been_made\n      expect(response[\"success\"]).to eq(\"OK\")\n    end\n  end\n\n  describe \"#category_set_user_notification\" do\n    before do\n      stub_post(\"#{host}/category/1/notifications\").to_return(\n        body: fixture(\"notification_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the post request\" do\n      response = client.category_set_user_notification(id: 1, notification_level: 3)\n      expect(a_post(\"#{host}/category/1/notifications\")).to have_been_made\n      expect(response[\"success\"]).to eq(\"OK\")\n    end\n  end\n\n  describe \"#category_set_user_notification_level\" do\n    before do\n      stub_post(\"#{host}/category/1/notifications\").to_return(\n        body: fixture(\"notification_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the post request\" do\n      response = client.category_set_user_notification_level(1, notification_level: 3)\n      expect(\n        a_post(\"#{host}/category/1/notifications\").with(body: \"notification_level=3\"),\n      ).to have_been_made\n      expect(response[\"success\"]).to eq(\"OK\")\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/email_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Email do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#email_settings\" do\n    before do\n      stub_get(\"#{host}/admin/email.json\").to_return(\n        body: fixture(\"email_settings.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.email_settings\n      expect(a_get(\"#{host}/admin/email.json\")).to have_been_made\n    end\n\n    it \"returns the requested settings\" do\n      settings = client.email_settings\n      expect(settings).to be_a Hash\n      expect(settings).to have_key(\"delivery_method\")\n      expect(settings).to have_key(\"settings\")\n    end\n  end\n\n  describe \"#list_email_all\" do\n    before do\n      stub_get(\"#{host}/admin/email/all.json\").to_return(\n        body: fixture(\"email_list_all.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.list_email(\"all\")\n      expect(a_get(\"#{host}/admin/email/all.json\")).to have_been_made\n    end\n\n    it \"returns all email\" do\n      all_email = client.list_email(\"all\")\n      expect(all_email).to be_an Array\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/groups_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Groups do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#groups\" do\n    before do\n      stub_get(\"#{host}/groups.json\").to_return(\n        body: fixture(\"groups.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.groups\n      expect(a_get(\"#{host}/groups.json\")).to have_been_made\n    end\n\n    it \"returns the requested groups\" do\n      groups = client.groups\n      expect(groups).to be_an Array\n      groups.each { |g| expect(g).to be_a Hash }\n    end\n\n    it \"returns a single group\" do\n      stub_get(\"#{host}/groups/some-group.json\").to_return(\n        body: fixture(\"group.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n      group = client.group(\"some-group\")\n      expect(group[\"basic_group\"]).to be_a Hash\n    end\n\n    it \"create new groups\" do\n      stub_post(\"#{host}/admin/groups\")\n      client.create_group(name: \"test_group\")\n      params = escape_params(\"group[name]\" => \"test_group\", \"group[visibility_level]\" => 0)\n      expect(a_post(\"#{host}/admin/groups\").with(body: params)).to have_been_made\n    end\n\n    it \"update an existing group\" do\n      stub_put(\"#{host}/groups/42\")\n      client.update_group(42, name: \"test_group\")\n      params = escape_params(\"group[name]\" => \"test_group\", \"group[visibility_level]\" => 0)\n      expect(a_put(\"#{host}/groups/42\").with(body: params)).to have_been_made\n    end\n\n    describe \"add members\" do\n      before { stub_request(:put, \"#{host}/admin/groups/123/members.json\") }\n\n      it \"adds a single member by username\" do\n        client.group_add(123, username: \"sam\")\n        expect(\n          a_request(:put, \"#{host}/admin/groups/123/members.json\").with(body: { usernames: \"sam\" }),\n        ).to have_been_made\n      end\n\n      it \"adds an array of members by username\" do\n        client.group_add(123, usernames: %w[sam jeff])\n        expect(\n          a_request(:put, \"#{host}/admin/groups/123/members.json\").with(\n            body: {\n              usernames: \"sam,jeff\",\n            },\n          ),\n        ).to have_been_made\n      end\n\n      it \"adds a single member by user_id\" do\n        client.group_add(123, user_id: 456)\n        expect(\n          a_request(:put, \"#{host}/admin/groups/123/members.json\").with(body: { user_ids: \"456\" }),\n        ).to have_been_made\n      end\n\n      it \"adds an array of members by user_id\" do\n        client.group_add(123, user_id: [123, 456])\n        expect(\n          a_request(:put, \"#{host}/admin/groups/123/members.json\").with(\n            body: {\n              user_ids: \"123,456\",\n            },\n          ),\n        ).to have_been_made\n      end\n    end\n\n    describe \"remove members\" do\n      let(:url) { \"#{host}/admin/groups/123/members.json?usernames=sam\" }\n\n      before { stub_delete(url) }\n\n      it \"removes member\" do\n        client.group_remove(123, username: \"sam\")\n        expect(a_delete(url)).to have_been_made\n      end\n    end\n\n    describe \"add owners\" do\n      let(:url) { \"#{host}/admin/groups/123/owners.json\" }\n\n      before { stub_put(url) }\n\n      it \"makes the member an owner\" do\n        client.group_add_owners(123, usernames: \"sam\")\n        params = escape_params(\"group[usernames]\" => \"sam\")\n        expect(\n          a_request(:put, \"#{host}/admin/groups/123/owners.json\").with(body: params),\n        ).to have_been_made\n      end\n    end\n\n    describe \"remove owners\" do\n      let(:url) { \"#{host}/admin/groups/123/owners.json?group%5Busernames%5D=sam\" }\n\n      before { stub_delete(url) }\n\n      it \"removes the owner role from the group member\" do\n        client.group_remove_owners(123, usernames: \"sam\")\n        expect(a_delete(url)).to have_been_made\n      end\n    end\n\n    describe \"group members\" do\n      it \"list members\" do\n        stub_get(\"#{host}/groups/mygroup/members.json?limit=100&offset=0\").to_return(\n          body: fixture(\"members_0.json\"),\n          headers: {\n            content_type: \"application/json\",\n          },\n        )\n        stub_get(\"#{host}/groups/mygroup/members.json?limit=100&offset=100\").to_return(\n          body: fixture(\"members_1.json\"),\n          headers: {\n            content_type: \"application/json\",\n          },\n        )\n        members = client.group_members(\"mygroup\")\n        expect(a_get(\"#{host}/groups/mygroup/members.json?limit=100&offset=0\")).to have_been_made\n        expect(members.length).to eq(100)\n        members = client.group_members(\"mygroup\", offset: 100)\n        expect(a_get(\"#{host}/groups/mygroup/members.json?limit=100&offset=100\")).to have_been_made\n        expect(members.length).to eq(90)\n      end\n\n      context \"with :all params\" do\n        it \"lists members and owners\" do\n          stub_get(\"#{host}/groups/mygroup/members.json?limit=100&offset=0\").to_return(\n            body: fixture(\"members_2.json\"),\n            headers: {\n              content_type: \"application/json\",\n            },\n          )\n          member_data = client.group_members(\"mygroup\", all: true)\n          expect(a_get(\"#{host}/groups/mygroup/members.json?limit=100&offset=0\")).to have_been_made\n          expect(member_data[\"members\"].length).to eq(100)\n          expect(member_data[\"owners\"].length).to eq(7)\n          expect(member_data.keys.sort).to eq(%w[members meta owners])\n        end\n      end\n    end\n\n    describe \"group user notification level\" do\n      before { stub_post(\"#{host}/groups/mygroup/notifications?user_id=77&notification_level=3\") }\n\n      it \"updates user's notification level for group\" do\n        client.group_set_user_notification_level(\"mygroup\", 77, 3)\n        expect(\n          a_post(\"#{host}/groups/mygroup/notifications?user_id=77&notification_level=3\"),\n        ).to have_been_made\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/invite_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Invite do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#invite_user\" do\n    before do\n      stub_post(\"#{host}/invites\").to_return(\n        body: fixture(\"topic_invite_user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.invite_user(email: \"fake_user@example.com\", group_ids: \"41,42\")\n      expect(a_post(\"#{host}/invites\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response = client.invite_user(email: \"fake_user@example.com\", group_ids: \"41,42\")\n      expect(response).to be_a Hash\n      expect(response[\"success\"]).to be_truthy\n    end\n  end\n\n  describe \"#update_invite\" do\n    before do\n      stub_put(\"#{host}/invites/27\").to_return(\n        body: fixture(\"topic_invite_user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"updates invite\" do\n      client.update_invite(27, email: \"namee@example.com\")\n      expect(a_put(\"#{host}/invites/27\")).to have_been_made\n    end\n  end\n\n  describe \"#retrieve_invite\" do\n    before do\n      stub_get(\"#{host}/invites/retrieve.json?email=foo@bar.com\").to_return(\n        body: fixture(\"retrieve_invite.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.retrieve_invite(email: \"foo@bar.com\")\n      expect(a_get(\"#{host}/invites/retrieve.json?email=foo@bar.com\")).to have_been_made\n    end\n\n    it \"returns the requested topics\" do\n      invites = client.retrieve_invite(email: \"foo@bar.com\")\n      expect(invites).to be_an Hash\n    end\n\n    it \"returns the requested invite\" do\n      invites = client.retrieve_invite(email: \"foo@bar.com\")\n      expect(invites[\"email\"]).to eq(\"foo@bar.com\")\n      expect(invites).to have_key(\"invite_key\")\n    end\n  end\n\n  describe \"#destroy_all_expired_invites\" do\n    let(:url) { \"#{host}/invites/destroy-all-expired\" }\n\n    before do\n      stub_post(url).to_return(\n        body: '{\"success\": \"OK\"}',\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"destroys all expired invites\" do\n      client.destroy_all_expired_invites\n      expect(a_post(url)).to have_been_made\n    end\n  end\n\n  describe \"#resend_all_invites\" do\n    let(:url) { \"#{host}/invites/reinvite-all\" }\n\n    before do\n      stub_post(url).to_return(\n        body: '{\"success\": \"OK\"}',\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"resends all invites\" do\n      client.resend_all_invites\n      expect(a_post(url)).to have_been_made\n    end\n  end\n\n  describe \"#resend_invite\" do\n    let(:url) { \"#{host}/invites/reinvite\" }\n\n    before do\n      stub_post(url).to_return(\n        body: '{\"success\": \"OK\"}',\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"resends invite\" do\n      client.resend_invite(\"foo@bar.com\")\n      expect(a_post(url)).to have_been_made\n    end\n  end\n\n  describe \"#destroy_invite\" do\n    let(:url) { \"#{host}/invites?id=27\" }\n\n    before do\n      stub_delete(url).to_return(\n        body: '{\"success\": \"OK\"}',\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"destroys the invite\" do\n      client.destroy_invite(27)\n      expect(a_delete(url)).to have_been_made\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/notifications_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Notifications do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#notifications\" do\n    before do\n      stub_get(\"#{host}/notifications.json\").to_return(\n        body: fixture(\"notifications.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.notifications\n      expect(a_get(\"#{host}/notifications.json\")).to have_been_made\n    end\n\n    it \"returns the requested notifications\" do\n      notifications = client.notifications\n      expect(notifications).to be_an Array\n      expect(notifications.first).to be_an Hash\n      expect(notifications[0][\"notification_type\"]).to eq(9)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/params_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Params do\n  def params_for(h)\n    DiscourseApi::API::Params.new(h).required(:r1).optional(:o1, :o2).default(d1: \"default\")\n  end\n\n  it \"should raise on missing required params\" do\n    expect { params_for({ o1: \"test\" }).to_h }.to raise_error(ArgumentError)\n  end\n\n  it \"should not raise when a required param is false\" do\n    expect { params_for({ r1: false }).to_h }.not_to raise_error\n  end\n\n  it \"should not include optional params when not provided\" do\n    expect(params_for({ r1: \"test\" }).to_h).not_to include(:o1)\n  end\n\n  it \"should include optional params if provided but blank\" do\n    expect(params_for({ r1: \"test\", o2: nil }).to_h).to include(:o2)\n  end\n\n  it \"should include default params when defined but not provided\" do\n    expect(params_for({ r1: \"test\" }).to_h).to include(d1: \"default\")\n  end\n\n  it \"should include default params when defined and provided\" do\n    expect(params_for({ r1: \"test\", d1: \"override\" }).to_h).to include(d1: \"override\")\n  end\n\n  it \"should include optional and default params when defined and provided\" do\n    expect(params_for({ r1: \"test\", o1: \"optional\", d1: \"override\" }).to_h).to include(\n      o1: \"optional\",\n      d1: \"override\",\n    )\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/polls_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Polls do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#poll vote\" do\n    before do\n      path = \"#{host}/polls/vote\"\n      stub_put(path).to_return(\n        body: fixture(\"polls_vote.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      options = [\"8b4736b1ae3dfb5a28088530f036f9e5\"]\n      client.poll_vote post_id: 5, poll_name: \"poll\", options: options\n      expect(a_put(\"#{host}/polls/vote\")).to have_been_made\n    end\n\n    it \"returns the expected votes\" do\n      options = [\"8b4736b1ae3dfb5a28088530f036f9e5\"]\n      vote = client.poll_vote post_id: 5, poll_name: \"poll\", options: options\n      expect(vote.body).to be_a Hash\n      expect(vote.body[\"poll\"][\"options\"]).to be_an Array\n      expect(vote.body[\"vote\"]).to eq([\"8b4736b1ae3dfb5a28088530f036f9e5\"])\n    end\n\n    describe \"#poll toggle_status\" do\n      before do\n        path = \"#{host}/polls/toggle_status\"\n        stub_put(path).to_return(\n          body: fixture(\"polls_toggle_status.json\"),\n          headers: {\n            content_type: \"application/json\",\n          },\n        )\n      end\n\n      it \"toggles the poll status to closed\" do\n        client.toggle_poll_status post_id: 5, poll_name: \"poll\", status: \"closed\"\n        expect(a_put(\"#{host}/polls/toggle_status\")).to have_been_made\n      end\n\n      it \"returns the expected results of closed poll\" do\n        returned_poll_status =\n          client.toggle_poll_status post_id: 5, poll_name: \"poll\", status: \"closed\"\n        expect(returned_poll_status.body).to be_a Hash\n        returned_poll_status.body[\"poll\"][\"options\"].each { |g| expect(g).to be_a Hash }\n      end\n    end\n\n    describe \"#poll voters\" do\n      before do\n        stub_get(\"#{host}/polls/voters.json?post_id=5&poll_name=poll\").to_return(\n          body: fixture(\"polls_voters.json\"),\n          headers: {\n            content_type: \"application/json\",\n          },\n        )\n      end\n\n      it \"requests the correct resource\" do\n        client.poll_voters post_id: 5, poll_name: \"poll\"\n        expect(a_get(\"#{host}/polls/voters.json?post_id=5&poll_name=poll\")).to have_been_made\n      end\n\n      it \"returns the expected votes\" do\n        voters = client.poll_voters post_id: 5, poll_name: \"poll\"\n        expect(voters).to be_a Hash\n        voters.each { |g| expect(g).to be_an Array }\n        expect(voters[\"voters\"][\"e539a9df8700d0d05c69356a07b768cf\"]).to be_an Array\n        expect(voters[\"voters\"][\"e539a9df8700d0d05c69356a07b768cf\"][0][\"id\"]).to eq(356)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/posts_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Posts do\n  let(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#get_post\" do\n    before do\n      stub_get(\"#{host}/posts/11.json\").to_return(\n        body: fixture(\"post.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"fetches a post\" do\n      the_post = client.get_post(11)\n      expect(the_post).to be_a Hash\n      expect(the_post[\"id\"]).to eq(11)\n    end\n  end\n\n  describe \"#posts\" do\n    before do\n      stub_get(\"#{host}/posts.json?before=0\").to_return(\n        body: fixture(\"posts_latest.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n      stub_get(\"#{host}/posts.json?before=14\").to_return(\n        body: fixture(\"posts_before.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"fetches latest posts\" do\n      the_posts = client.posts()\n      expect(the_posts).to be_a Hash\n      expect(the_posts[\"latest_posts\"][0][\"id\"]).to eq(15)\n    end\n\n    it \"fetches posts before 14\" do\n      the_posts = client.posts(before: 14)\n      expect(the_posts).to be_a Hash\n      expect(the_posts[\"latest_posts\"][0][\"id\"]).to eq(14)\n    end\n  end\n\n  describe \"#wikify_post\" do\n    before { stub_put(\"#{host}/posts/9999/wiki\") }\n\n    it \"fails on unknown post\" do\n      client.wikify_post(9999)\n      expect(a_put(\"#{host}/posts/9999/wiki\")).to have_been_made\n    end\n  end\n\n  describe \"#delete_post\" do\n    before { stub_delete(\"#{host}/posts/9999.json\") }\n\n    it \"deletes a post\" do\n      client.delete_post(9999)\n      expect(a_delete(\"#{host}/posts/9999.json\")).to have_been_made\n    end\n  end\n\n  describe \"#post_action_users\" do\n    before do\n      stub_get(\"#{host}/post_action_users.json?id=11&post_action_type_id=2\").to_return(\n        body: fixture(\"post_action_users.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"fetches post_action_users\" do\n      post_action_users = client.post_action_users(11, 2)\n      expect(post_action_users).to be_a Hash\n      expect(post_action_users[\"post_action_users\"][0][\"id\"]).to eq(1286)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/private_messages_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::PrivateMessages do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#private_messages\" do\n    before do\n      stub_get(\"#{host}/topics/private-messages/test_user.json\").to_return(\n        body: fixture(\"private_messages.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.private_messages(\"test_user\")\n      expect(a_get(\"#{host}/topics/private-messages/test_user.json\")).to have_been_made\n    end\n\n    it \"returns the requested private messages\" do\n      private_messages = client.private_messages(\"test_user\")\n      expect(private_messages).to be_an Array\n    end\n  end\n\n  describe \"#sent_private_messages\" do\n    before do\n      stub_get(\"#{host}/topics/private-messages-sent/test_user.json\").to_return(\n        body: fixture(\"private_messages.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.sent_private_messages(\"test_user\")\n      expect(a_get(\"#{host}/topics/private-messages-sent/test_user.json\")).to have_been_made\n    end\n\n    it \"returns the requested sent private messages\" do\n      private_messages = client.sent_private_messages(\"test_user\")\n      expect(private_messages).to be_an Array\n    end\n  end\n\n  describe \"#create_pm\" do\n    before do\n      stub_post(\"#{host}/posts\")\n      client.create_pm(\n        title: \"Confidential: Hello World!\",\n        raw: \"This is the raw markdown for my private message\",\n        target_recipients: \"user1,user2\",\n      )\n    end\n\n    it \"makes a create private message request\" do\n      expect(\n        a_post(\"#{host}/posts\").with(\n          body:\n            \"archetype=private_message&raw=This+is+the+raw+markdown+for+my+private+message&target_recipients=user1%2Cuser2&title=Confidential%3A+Hello+World%21\",\n        ),\n      ).to have_been_made\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/search_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Search do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#search\" do\n    before do\n      stub_get(\"#{host}/search\").with(query: { q: \"test\" }).to_return(\n        body: fixture(\"search.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.search(\"test\")\n      expect(a_get(\"#{host}/search\").with(query: { q: \"test\" })).to have_been_made\n    end\n\n    it \"returns the requested search\" do\n      results = client.search(\"test\")\n      expect(results).to be_an Array\n      expect(results.first).to be_a Hash\n    end\n\n    it \"raises an ArgumentError for nil\" do\n      expect { client.search(nil) }.to raise_error(ArgumentError)\n    end\n\n    it \"raises an ArgumentError for empty string\" do\n      expect { client.search(\"\") }.to raise_error(ArgumentError)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/site_settings_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::SiteSettings do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#site_setting_update\" do\n    before do\n      stub_put(\"#{host}/admin/site_settings/foo\")\n      client.site_setting_update(name: \"foo\", value: \"bar\")\n    end\n\n    it \"makes a site_settings_update request\" do\n      expect(a_put(\"#{host}/admin/site_settings/foo\").with(body: \"foo=bar\")).to have_been_made\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/sso_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::SSO do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  let(:params) do\n    {\n      :sso_secret => \"abc\",\n      :sso_url => \"www.google.com\",\n      :name => \"Some User\",\n      :username => \"some_user\",\n      :email => \"some@email.com\",\n      :external_id => \"abc\",\n      :suppress_welcome_message => false,\n      :avatar_url => \"https://www.website.com\",\n      :title => \"ruby\",\n      :avatar_force_update => false,\n      :add_groups => %w[a b],\n      :remove_groups => %w[c d],\n      # old format (which results in custom.custom.field_1 in unsigned_payload)\n      \"custom.field_1\" => \"tomato\",\n      # new format\n      :custom_fields => {\n        field_2: \"potato\",\n      },\n    }\n  end\n  let(:expected_unsigned_payload) do\n    \"add_groups=a&add_groups=b&avatar_url=https%3A%2F%2Fwww.website.com\" \\\n      \"&email=some%40email.com&external_id=abc&name=Some+User&remove_groups=c\" \\\n      \"&remove_groups=d&title=ruby&username=some_user&custom.field_2=potato\" \\\n      \"&custom.custom.field_1=tomato\"\n  end\n  let(:sso_double) { DiscourseApi::SingleSignOn.parse_hash(params) }\n\n  describe \"#sync_sso\" do\n    before do\n      stub_post(/.*sync_sso.*/).to_return(\n        body: fixture(\"user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"assigns params to sso instance\" do\n      allow(DiscourseApi::SingleSignOn).to(receive(:parse_hash).with(params).and_return(sso_double))\n\n      client.sync_sso(params)\n\n      expect(sso_double.custom_fields).to eql(\n        { \"custom.field_1\" => \"tomato\", :field_2 => \"potato\" },\n      )\n      expect(sso_double.unsigned_payload).to eql(expected_unsigned_payload)\n    end\n\n    it \"requests the correct resource\" do\n      client.sync_sso({ :sso_secret => \"test_d7fd0429940\", \"custom.riffle_url\" => \"test\" })\n      expect(a_post(/.*sync_sso.*/)).to have_been_made\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/topics_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Topics do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#change_topic_status\" do\n    before do\n      stub_put(\"#{host}/t/57/status\").to_return(\n        body: fixture(\"topic.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"changes the topic status\" do\n      client.update_topic_status(57, { status: \"visible\", enabled: false })\n      expect(a_put(\"#{host}/t/57/status\")).to have_been_made\n    end\n  end\n\n  describe \"#invite_to_topic\" do\n    before do\n      stub_post(\"#{host}/t/12/invite\").to_return(\n        body: fixture(\"topic_invite_user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.invite_to_topic(12, email: \"fake_user@example.com\")\n      expect(a_post(\"#{host}/t/12/invite\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response = client.invite_to_topic(12, email: \"fake_user@example.com\")\n      expect(response).to be_a Hash\n      expect(response[\"success\"]).to be_truthy\n    end\n  end\n\n  describe \"#latest_topics\" do\n    before do\n      stub_get(\"#{host}/latest.json\").to_return(\n        body: fixture(\"latest.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.latest_topics\n      expect(a_get(\"#{host}/latest.json\")).to have_been_made\n    end\n\n    it \"returns the requested topics\" do\n      topics = client.latest_topics\n      expect(topics).to be_an Array\n      expect(topics.first).to be_a Hash\n    end\n\n    it \"can take a hash param\" do\n      topics = client.latest_topics({})\n      expect(topics).to be_an Array\n      expect(topics.first).to be_a Hash\n    end\n  end\n\n  describe \"#top_topics\" do\n    before do\n      stub_get(\"#{host}/top.json\").to_return(\n        body: fixture(\"top.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.top_topics\n      expect(a_get(\"#{host}/top.json\")).to have_been_made\n    end\n\n    it \"returns the requested topics\" do\n      topics = client.top_topics\n      expect(topics).to be_an Array\n      expect(topics.first).to be_a Hash\n    end\n\n    it \"can take a hash param\" do\n      topics = client.top_topics({})\n      expect(topics).to be_an Array\n      expect(topics.first).to be_a Hash\n    end\n  end\n\n  describe \"#new_topics\" do\n    before do\n      stub_get(\"#{host}/new.json\").to_return(\n        body: fixture(\"new.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.new_topics\n      expect(a_get(\"#{host}/new.json\")).to have_been_made\n    end\n\n    it \"returns the requested topics\" do\n      client.api_username = \"test_user\"\n      topics = client.new_topics\n      expect(topics).to be_an Array\n      expect(topics.first).to be_a Hash\n    end\n  end\n\n  describe \"#topic\" do\n    before do\n      stub_get(\"#{host}/t/57.json\").to_return(\n        body: fixture(\"topic.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.topic(57)\n      expect(a_get(\"#{host}/t/57.json\")).to have_been_made\n    end\n\n    it \"returns the requested topic\" do\n      topic = client.topic(57)\n      expect(topic).to be_a Hash\n      expect(topic[\"id\"]).to eq(57)\n    end\n  end\n\n  describe \"#update_topic\" do\n    before do\n      stub_put(\"#{host}/t/57.json\").to_return(\n        body: fixture(\"topic.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"renames the topic\" do\n      client.rename_topic(57, \"A new title!\")\n      expect(a_put(\"#{host}/t/57.json\")).to have_been_made\n    end\n\n    it \"assigns the topic a new category\" do\n      client.recategorize_topic(57, 3)\n      expect(a_put(\"#{host}/t/57.json\")).to have_been_made\n    end\n  end\n\n  describe \"#topics_by\" do\n    before do\n      stub_get(\"#{host}/topics/created-by/test.json\").to_return(\n        body: fixture(\"topics_created_by.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.topics_by(\"test\")\n      expect(a_get(\"#{host}/topics/created-by/test.json\")).to have_been_made\n    end\n\n    it \"returns the requested topics\" do\n      topics = client.topics_by(\"test\")\n      expect(topics).to be_an Array\n      expect(topics.first).to be_a Hash\n    end\n  end\n\n  describe \"#topic_posts\" do\n    before do\n      stub_get(%r{#{host}/t/57/posts\\.json}).to_return(\n        body: fixture(\"topic_posts.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.topic_posts(57)\n      expect(a_get(\"#{host}/t/57/posts.json\")).to have_been_made\n    end\n\n    it \"allows scoping to specific post ids\" do\n      client.topic_posts(57, [123, 456])\n      expect(a_get(\"#{host}/t/57/posts.json?post_ids[]=123&post_ids[]=456\")).to have_been_made\n    end\n\n    it \"returns the requested topic posts\" do\n      body = client.topic_posts(57, [123])\n      expect(body).to be_a Hash\n      expect(body[\"post_stream\"][\"posts\"]).to be_an Array\n      expect(body[\"post_stream\"][\"posts\"].first).to be_a Hash\n    end\n\n    it \"can retrieve a topic posts' raw attribute\" do\n      body = client.topic_posts(57, [123], { include_raw: true })\n      expect(body).to be_a Hash\n      expect(body[\"post_stream\"][\"posts\"]).to be_an Array\n      expect(body[\"post_stream\"][\"posts\"].first).to be_a Hash\n      expect(body[\"post_stream\"][\"posts\"].first[\"raw\"]).to be_an Array\n    end\n  end\n\n  describe \"#create_topic_with_tags\" do\n    before do\n      stub_post(\"#{host}/posts\").to_return(\n        body: fixture(\"create_topic_with_tags.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the post request\" do\n      client.create_topic title: \"Sample Topic Title\",\n                          raw: \"Sample topic content body\",\n                          tags: %w[asdf fdsa]\n      expect(a_post(\"#{host}/posts\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response =\n        client.create_topic title: \"Sample Topic Title\",\n                            raw: \"Sample topic content body\",\n                            tags: %w[asdf fdsa]\n      expect(response).to be_a Hash\n      expect(response[\"topic_id\"]).to eq 21\n    end\n  end\n\n  describe \"#topic_set_user_notification_level\" do\n    before do\n      stub_post(\"#{host}/t/1/notifications\").to_return(\n        body: fixture(\"notification_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the post request\" do\n      response = client.topic_set_user_notification_level(1, notification_level: 3)\n      expect(\n        a_post(\"#{host}/t/1/notifications\").with(body: \"notification_level=3\"),\n      ).to have_been_made\n      expect(response[\"success\"]).to eq(\"OK\")\n    end\n  end\n\n  describe \"#bookmark_topic\" do\n    before do\n      stub_put(\"#{host}/t/1/bookmark.json\").to_return(\n        body: \"\",\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the put request\" do\n      response = client.bookmark_topic(1)\n      expect(a_put(\"#{host}/t/1/bookmark.json\")).to have_been_made\n      expect(response.body).to eq(nil)\n    end\n  end\n\n  describe \"#remove_topic_bookmark\" do\n    before do\n      stub_put(\"#{host}/t/1/remove_bookmarks.json\").to_return(\n        body: \"\",\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the put request\" do\n      response = client.remove_topic_bookmark(1)\n      expect(a_put(\"#{host}/t/1/remove_bookmarks.json\")).to have_been_made\n      expect(response.body).to eq(nil)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/uploads_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Uploads do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#upload_file\" do\n    before do\n      stub_post(\"#{host}/uploads\").to_return(\n        body: fixture(\"upload_file.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"uploads an image via URL\" do\n      image =\n        \"https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/sam/120/5243.png\"\n      args = { url: image }\n      response = client.upload_file(args)\n      expect(response[\"url\"]).to eq(\n        \"/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg\",\n      )\n    end\n\n    it \"uploads a file\" do\n      file = Faraday::UploadIO.new(\"spec/fixtures/upload_file.json\", \"application/json\")\n      args = { file: file }\n      response = client.upload_file(args)\n      expect(response[\"url\"]).to eq(\n        \"/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg\",\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/user_actions_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::UserActions do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#user_replies\" do\n    before do\n      stub_get(\"#{host}/user_actions.json?username=testuser&filter=5\").to_return(\n        body: fixture(\"replies.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.user_replies(\"testuser\")\n      expect(a_get(\"#{host}/user_actions.json?username=testuser&filter=5\")).to have_been_made\n    end\n\n    it \"returns the requested user\" do\n      replies = client.user_replies(\"testuser\")\n      expect(replies).to be_an Array\n    end\n  end\n\n  describe \"#user_topics_and_replies\" do\n    before do\n      stub_get(\"#{host}/user_actions.json?username=testuser&filter=4,5\").to_return(\n        body: fixture(\"replies_and_topics.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.user_topics_and_replies(\"testuser\")\n      expect(a_get(\"#{host}/user_actions.json?username=testuser&filter=4,5\")).to have_been_made\n    end\n\n    it \"returns the requested user\" do\n      replies = client.user_topics_and_replies(\"testuser\")\n      expect(replies).to be_an Array\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/api/users_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::API::Users do\n  subject(:client) { DiscourseApi::Client.new(\"#{host}\", \"test_d7fd0429940\", \"test_user\") }\n\n  describe \"#user\" do\n    before do\n      stub_get(\"#{host}/users/test.json\").to_return(\n        body: fixture(\"user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.user(\"test\")\n      expect(a_get(\"#{host}/users/test.json\")).to have_been_made\n    end\n\n    it \"returns the requested user\" do\n      user = client.user(\"test\")\n      expect(user).to be_a Hash\n    end\n\n    it \"works with optional params\" do\n      user = client.user(\"test\", {})\n      expect(user).to be_a Hash\n    end\n  end\n\n  describe \"#user_sso\" do\n    before do\n      stub_get(\"#{host}/admin/users/15.json\").to_return(\n        body: fixture(\"admin_user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.user_sso(15)\n      expect(a_get(\"#{host}/admin/users/15.json\")).to have_been_made\n    end\n\n    it \"has single_sign_on_record\" do\n      user_sso = client.user_sso(15)\n      expect(user_sso).to be_a Hash\n      expect(user_sso).to have_key(\"external_id\")\n    end\n  end\n\n  describe \"#update_avatar\" do\n    before do\n      stub_post(\"#{host}/uploads\").to_return(\n        body: fixture(\"upload_avatar.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n      stub_put(\"#{host}/u/test_user/preferences/avatar/pick\").to_return(\n        body: fixture(\"user_update_avatar_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"uploads an image\" do\n      sam =\n        \"https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/sam/120/5243.png\"\n      args = { url: sam }\n      response = client.update_avatar(\"test_user\", args)\n      expect(response[:body][\"success\"]).to eq(\"OK\")\n    end\n  end\n\n  describe \"#update_email\" do\n    before do\n      stub_put(\"#{host}/u/fake_user/preferences/email\").to_return(\n        body: fixture(\"user_update_user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the put request\" do\n      client.update_email(\"fake_user\", \"fake_user_2@example.com\")\n      expect(a_put(\"#{host}/u/fake_user/preferences/email\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response = client.update_email(\"fake_user\", \"fake_user_2@example.com\")\n      expect(response[:body][\"success\"]).to be_truthy\n    end\n  end\n\n  describe \"#update_user\" do\n    before do\n      stub_put(\"#{host}/u/fake_user\").to_return(\n        body: fixture(\"user_update_user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the put request\" do\n      client.api_key = \"test_d7fd0429940\"\n      client.api_username = \"test_user\"\n      client.update_user(\"fake_user\", name: \"Fake User 2\")\n      expect(a_put(\"#{host}/u/fake_user\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      client.api_key = \"test_d7fd0429940\"\n      client.api_username = \"test_user\"\n      response = client.update_user(\"fake_user\", name: \"Fake User 2\")\n      expect(response[:body][\"success\"]).to be_truthy\n    end\n  end\n\n  describe \"#update_username\" do\n    before do\n      stub_put(\"#{host}/u/fake_user/preferences/username\").to_return(\n        body: fixture(\"user_update_username.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the put request\" do\n      client.update_username(\"fake_user\", \"fake_user_2\")\n      expect(a_put(\"#{host}/u/fake_user/preferences/username\")).to have_been_made\n    end\n\n    it \"returns the updated username\" do\n      response = client.update_username(\"fake_user\", \"fake_user_2\")\n      expect(response[:body][\"username\"]).to eq(\"fake_user_2\")\n    end\n  end\n\n  describe \"#create_user\" do\n    before do\n      stub_post(\"#{host}/users\").to_return(\n        body: fixture(\"user_create_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the post request\" do\n      client.create_user name: \"Test User\",\n                         email: \"test2@example.com\",\n                         password: \"P@ssword\",\n                         username: \"test2\"\n      expect(a_post(\"#{host}/users\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response =\n        client.create_user name: \"Test User\",\n                           email: \"test2@example.com\",\n                           password: \"P@ssword\",\n                           username: \"test2\"\n      expect(response).to be_a Hash\n      expect(response[\"success\"]).to be_truthy\n    end\n  end\n\n  describe \"#activate_user\" do\n    before do\n      stub_put(\"#{host}/admin/users/15/activate\").to_return(\n        body: fixture(\"user_activate_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the put request\" do\n      client.activate(15)\n      expect(a_put(\"#{host}/admin/users/15/activate\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response = client.activate(15)\n      expect(response[:body][\"success\"]).to eq(\"OK\")\n    end\n  end\n\n  describe \"#log_out_success\" do\n    before do\n      stub_post(\"#{host}/admin/users/4/log_out\").to_return(\n        body: fixture(\"user_log_out_success.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes a post request\" do\n      client.log_out(4)\n      expect(a_post(\"#{host}/admin/users/4/log_out\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response = client.log_out(4)\n      expect(response).to be_a Hash\n      expect(response[\"success\"]).to eq(\"OK\")\n    end\n  end\n\n  describe \"#log_out_unsuccessful\" do\n    before do\n      stub_post(\"#{host}/admin/users/90/log_out\").to_return(\n        status: 404,\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"Raises API Error\" do\n      expect { client.log_out(90) }.to raise_error DiscourseApi::NotFoundError\n    end\n  end\n\n  describe \"#list_users\" do\n    before do\n      stub_get(\"#{host}/admin/users/list/active.json\").to_return(\n        body: fixture(\"user_list.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.list_users(\"active\")\n      expect(a_get(\"#{host}/admin/users/list/active.json\")).to have_been_made\n    end\n\n    it \"returns the requested users\" do\n      users = client.list_users(\"active\")\n      expect(users).to be_an Array\n      expect(users.first).to be_a Hash\n    end\n  end\n\n  describe \"#update_trust_level\" do\n    before do\n      url = \"#{host}/admin/users/2/trust_level\"\n      stub_put(url).to_return(\n        body: fixture(\"update_trust_level.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the correct put request\" do\n      params = { level: 2 }\n      client.update_trust_level(2, params)\n      url = \"#{host}/admin/users/2/trust_level\"\n      expect(a_put(url)).to have_been_made\n    end\n\n    it \"updates the trust_level\" do\n      params = { level: 2 }\n      admin_user = client.update_trust_level(2, params)\n      expect(admin_user).to be_a Hash\n      expect(admin_user[\"admin_user\"]).to have_key(\"trust_level\")\n    end\n  end\n\n  describe \"#grant admin\" do\n    before do\n      url = \"#{host}/admin/users/11/grant_admin\"\n      stub_put(url).to_return(\n        body: fixture(\"user_grant_admin.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the correct put request\" do\n      client.grant_admin(11)\n      url = \"#{host}/admin/users/11/grant_admin\"\n      expect(a_put(url)).to have_been_made\n    end\n\n    it \"makes the user an admin\" do\n      result = client.grant_admin(11)\n      expect(result).to be_a Hash\n      expect(result[\"admin_user\"][\"admin\"]).to eq(true)\n    end\n  end\n\n  describe \"#grant moderation\" do\n    before do\n      url = \"#{host}/admin/users/11/grant_moderation\"\n      stub_put(url).to_return(\n        body: fixture(\"user_grant_moderator.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"makes the correct put request\" do\n      client.grant_moderation(11)\n      url = \"#{host}/admin/users/11/grant_moderation\"\n      expect(a_put(url)).to have_been_made\n    end\n\n    it \"makes the user a moderator\" do\n      result = client.grant_moderation(11)\n      expect(result).to be_a Hash\n      expect(result[\"admin_user\"][\"moderator\"]).to eq(true)\n    end\n  end\n\n  describe \"#revoke moderation\" do\n    before do\n      url = \"#{host}/admin/users/11/revoke_moderation\"\n      stub_put(url).to_return(body: \"\", status: 200)\n    end\n\n    it \"makes the correct put request\" do\n      result = client.revoke_moderation(11)\n      url = \"#{host}/admin/users/11/revoke_moderation\"\n      expect(a_put(url)).to have_been_made\n      expect(result.status).to eq(200)\n    end\n  end\n\n  describe \"#by_external_id\" do\n    before do\n      stub_get(\"#{host}/users/by-external/1\").to_return(\n        body: fixture(\"user.json\"),\n        headers: {\n          content_type: \"application/json\",\n        },\n      )\n    end\n\n    it \"requests the correct resource\" do\n      client.by_external_id(1)\n      expect(a_get(\"#{host}/users/by-external/1\")).to have_been_made\n    end\n\n    it \"returns the requested user\" do\n      user = client.by_external_id(1)\n      expect(user[\"id\"]).to eq 1\n    end\n  end\n\n  describe \"#suspend\" do\n    before do\n      url = \"#{host}/admin/users/11/suspend\"\n      stub_put(url).to_return(body: \"\", status: 200)\n    end\n\n    it \"makes the correct put request\" do\n      result = client.suspend(11, \"2030-01-01\", \"no reason\")\n      url = \"#{host}/admin/users/11/suspend\"\n      expect(a_put(url)).to have_been_made\n      expect(result.status).to eq(200)\n    end\n  end\n\n  describe \"#unsuspend\" do\n    before do\n      url = \"#{host}/admin/users/11/unsuspend\"\n      stub_put(url).to_return(body: \"\", status: 200)\n    end\n\n    it \"makes the correct put request\" do\n      result = client.unsuspend(11)\n      url = \"#{host}/admin/users/11/unsuspend\"\n      expect(a_put(url)).to have_been_made\n      expect(result.status).to eq(200)\n    end\n  end\n\n  describe \"#anonymize\" do\n    before do\n      url = \"#{host}/admin/users/11/anonymize\"\n      stub_put(url).to_return(body: \"\", status: 200)\n    end\n\n    it \"makes the correct put request\" do\n      result = client.anonymize(11)\n      url = \"#{host}/admin/users/11/anonymize\"\n      expect(a_put(url)).to have_been_made\n      expect(result.status).to eq(200)\n    end\n  end\n\n  describe \"#delete_user\" do\n    before do\n      url = \"#{host}/admin/users/11.json?delete_posts=true\"\n      stub_delete(url).to_return(body: '{\"deleted\": true}', status: 200)\n    end\n\n    it \"makes the correct delete request\" do\n      result = client.delete_user(11, true)\n      url = \"#{host}/admin/users/11.json?delete_posts=true\"\n      expect(a_delete(url)).to have_been_made\n      expect(result.body).to eq('{\"deleted\": true}')\n      expect(result.status).to eq(200)\n    end\n  end\n\n  describe \"#check_username\" do\n    let(:url) { \"#{host}/users/check_username.json?username=sparrow\" }\n    let(:body) { '{\"available\":false,\"suggestion\":\"sparrow1\"}' }\n\n    before { stub_get(url).to_return(body: body, headers: { content_type: \"application/json\" }) }\n\n    it \"requests the correct resource\" do\n      client.check_username(\"sparrow\")\n      expect(a_get(url)).to have_been_made\n    end\n\n    it \"returns the result\" do\n      result = client.check_username(\"sparrow\")\n      expect(result[\"available\"]).to eq false\n    end\n\n    context \"when non-URI characters are used\" do\n      let(:url) { \"#{host}/users/check_username.json?username=1_%5B4%5D%21+%40the%24%23%3F\" }\n      let(:body) { '{\"errors\":[\"must only include numbers, letters, dashes, and underscores\"]}' }\n\n      it \"escapes them\" do\n        client.check_username(\"1_[4]! @the$#?\")\n        expect(a_get(url)).to have_been_made\n      end\n\n      it \"returns the result\" do\n        result = client.check_username(\"1_[4]! @the$#?\")\n        expect(\n          result[\"errors\"].first,\n        ).to eq \"must only include numbers, letters, dashes, and underscores\"\n      end\n    end\n  end\n\n  describe \"#deactivate\" do\n    before { stub_put(\"#{host}/admin/users/15/deactivate\").to_return(body: nil) }\n\n    it \"makes the put request\" do\n      client.deactivate(15)\n      expect(a_put(\"#{host}/admin/users/15/deactivate\")).to have_been_made\n    end\n\n    it \"returns success\" do\n      response = client.deactivate(15)\n      expect(response.status).to eq(200)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/client_spec.rb",
    "content": "# frozen_string_literal: true\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::Client do\n  subject(:client) { DiscourseApi::Client.new(host) }\n\n  describe \".new\" do\n    it \"requires a host argument\" do\n      expect { DiscourseApi::Client.new }.to raise_error ArgumentError\n    end\n\n    it \"defaults api key to nil\" do\n      expect(client.api_key).to be_nil\n    end\n\n    it \"defaults api username to nil\" do\n      expect(client.api_username).to be_nil\n    end\n\n    it \"accepts an api key argument\" do\n      client = DiscourseApi::Client.new(host, \"test\")\n      expect(client.api_key).to eq(\"test\")\n    end\n\n    it \"accepts an api username argument\" do\n      client = DiscourseApi::Client.new(host, \"test\", \"test_user\")\n      expect(client.api_username).to eq(\"test_user\")\n    end\n  end\n\n  describe \"#timeout\" do\n    context \"with a custom timeout\" do\n      it \"is set to Faraday connection\" do\n        expect(client.send(:connection).options.timeout).to eq(30)\n      end\n    end\n\n    context \"with the default timeout\" do\n      it \"is set to Faraday connection\" do\n        client.timeout = 25\n        expect(client.send(:connection).options.timeout).to eq(25)\n      end\n    end\n\n    it \"raises DiscourseApi::Timeout\" do\n      stub_get(\"#{host}/t/1.json\").to_timeout\n\n      expect { client.topic(1) }.to raise_error(DiscourseApi::Timeout)\n    end\n  end\n\n  describe \"#api_key\" do\n    it \"is publicly accessible\" do\n      client.api_key = \"test_d7fd0429940\"\n      expect(client.api_key).to eq(\"test_d7fd0429940\")\n    end\n  end\n\n  describe \"#api_username\" do\n    it \"is publicly accessible\" do\n      client.api_username = \"test_user\"\n      expect(client.api_username).to eq(\"test_user\")\n    end\n  end\n\n  describe \"#host\" do\n    it \"is publicly readable\" do\n      expect(client.host).to eq(\"#{host}\")\n    end\n\n    it \"is not publicly writeable\" do\n      expect(client).not_to respond_to(:host=)\n    end\n  end\n\n  describe \"#connection\" do\n    it \"looks like a Faraday connection\" do\n      expect(client.send(:connection)).to respond_to :run_request\n    end\n\n    it \"memorizes the connection\" do\n      c1, c2 = client.send(:connection), client.send(:connection)\n      expect(c1.object_id).to eq(c2.object_id)\n    end\n  end\n\n  describe \"#delete\" do\n    before do\n      stub_delete(\"#{host}/test/delete\").with(query: { deleted: \"object\" })\n      client.api_key = \"test_d7fd0429940\"\n      client.api_username = \"test_user\"\n    end\n\n    it \"allows custom delete requests\" do\n      client.delete(\"/test/delete\", { deleted: \"object\" })\n      expect(a_delete(\"#{host}/test/delete\").with(query: { deleted: \"object\" })).to have_been_made\n    end\n\n    context \"when using a host with a subdirectory\" do\n      subject(:client) { DiscourseApi::Client.new(\"#{host}/forum\") }\n\n      before { stub_delete(\"#{host}/forum/test/delete\").with(query: { deleted: \"object\" }) }\n\n      it \"allows custom delete requests\" do\n        client.delete(\"/test/delete\", { deleted: \"object\" })\n        expect(\n          a_delete(\"#{host}/forum/test/delete\").with(query: { deleted: \"object\" }),\n        ).to have_been_made\n      end\n    end\n  end\n\n  describe \"#post\" do\n    before do\n      stub_post(\"#{host}/test/post\").with(body: { created: \"object\" })\n      client.api_key = \"test_d7fd0429940\"\n      client.api_username = \"test_user\"\n    end\n\n    it \"allows custom post requests\" do\n      client.post(\"/test/post\", { created: \"object\" })\n      expect(a_post(\"#{host}/test/post\").with(body: { created: \"object\" })).to have_been_made\n    end\n\n    context \"when using a host with a subdirectory\" do\n      subject(:client) { DiscourseApi::Client.new(\"#{host}/forum\") }\n\n      before { stub_post(\"#{host}/forum/test/post\").with(body: { created: \"object\" }) }\n\n      it \"allows custom post requests\" do\n        client.post(\"/test/post\", { created: \"object\" })\n        expect(\n          a_post(\"#{host}/forum/test/post\").with(body: { created: \"object\" }),\n        ).to have_been_made\n      end\n    end\n  end\n\n  describe \"#put\" do\n    before do\n      stub_put(\"#{host}/test/put\").with(body: { updated: \"object\" })\n      client.api_key = \"test_d7fd0429940\"\n      client.api_username = \"test_user\"\n    end\n\n    it \"allows custom put requests\" do\n      client.put(\"/test/put\", { updated: \"object\" })\n      expect(a_put(\"#{host}/test/put\").with(body: { updated: \"object\" })).to have_been_made\n    end\n\n    context \"when using a host with a subdirectory\" do\n      subject(:client) { DiscourseApi::Client.new(\"#{host}/forum\") }\n\n      before { stub_put(\"#{host}/forum/test/put\").with(body: { updated: \"object\" }) }\n\n      it \"allows custom post requests\" do\n        client.put(\"/test/put\", { updated: \"object\" })\n        expect(a_put(\"#{host}/forum/test/put\").with(body: { updated: \"object\" })).to have_been_made\n      end\n    end\n  end\n\n  describe \"#request\" do\n    it \"catches 500 errors\" do\n      connection = instance_double(Faraday::Connection)\n      allow(connection).to receive(:get).and_return(\n        OpenStruct.new(env: { body: \"error page html\" }, status: 500),\n      )\n      allow(Faraday).to receive(:new).and_return(connection)\n      expect { client.send(:request, :get, \"/test\") }.to raise_error DiscourseApi::Error\n    end\n\n    it \"catches Faraday errors\" do\n      allow(Faraday).to receive(:new).and_raise(Faraday::ClientError.new(\"BOOM!\"))\n      expect { client.send(:request, :get, \"/test\") }.to raise_error DiscourseApi::Error\n    end\n\n    it \"catches JSON::ParserError errors\" do\n      allow(Faraday).to receive(:new).and_raise(JSON::ParserError.new(\"unexpected token\"))\n      expect { client.send(:request, :get, \"/test\") }.to raise_error DiscourseApi::Error\n    end\n  end\nend\n"
  },
  {
    "path": "spec/discourse_api/single_sign_on_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire \"spec_helper\"\n\ndescribe DiscourseApi::SingleSignOn do\n  describe DiscourseApi::SingleSignOn::MissingConfigError do\n    it \"inherits from RuntimeError for backward compatibility\" do\n      expect(DiscourseApi::SingleSignOn::MissingConfigError).to be < RuntimeError\n    end\n  end\n\n  describe DiscourseApi::SingleSignOn::ParseError do\n    it \"inherits from RuntimeError for backward compatibility\" do\n      expect(DiscourseApi::SingleSignOn::ParseError).to be < RuntimeError\n    end\n  end\n\n  describe \".sso_secret\" do\n    it \"raises MissingConfigError when sso_secret is not present\" do\n      expect { described_class.sso_secret }.to raise_error(\n        DiscourseApi::SingleSignOn::MissingConfigError,\n      )\n    end\n  end\n\n  describe \".sso_url\" do\n    it \"raises MissingConfigError when sso_url is not present\" do\n      expect { described_class.sso_url }.to raise_error(\n        DiscourseApi::SingleSignOn::MissingConfigError,\n      )\n    end\n  end\n\n  describe \".parse\" do\n    context \"when sso is present\" do\n      it \"raises ParseError when there's a signature mismatch\" do\n        sso = described_class.new\n        sso.sso_secret = \"abcd\"\n        expect { described_class.parse(sso.payload, \"dcba\") }.to raise_error(\n          DiscourseApi::SingleSignOn::ParseError,\n        )\n      end\n    end\n\n    context \"when sso is missing\" do\n      it \"raises ParseError when there's a signature mismatch\" do\n        sso = described_class.new\n        sso.sso_secret = \"abcd\"\n        missing_sso = Rack::Utils.parse_query(sso.payload)\n        missing_sso.delete(\"sso\")\n        malformed_query = Rack::Utils.build_query(missing_sso)\n\n        expect { described_class.parse(malformed_query, \"dcba\") }.to raise_error(\n          DiscourseApi::SingleSignOn::ParseError,\n          /The SSO field should/i,\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/fixtures/admin_user.json",
    "content": "{\n  \"id\": 4,\n  \"username\": \"47f6974294f6e3eb9686\",\n  \"avatar_template\": \"/letter_avatar_proxy/v2/letter/4/8e8cbc/{size}.png\",\n  \"active\": false,\n  \"admin\": false,\n  \"moderator\": false,\n  \"last_seen_at\": null,\n  \"last_emailed_at\": null,\n  \"created_at\": \"2017-06-23T16:30:22.539Z\",\n  \"last_seen_age\": null,\n  \"last_emailed_age\": null,\n  \"created_at_age\": \"17m\",\n  \"username_lower\": \"47f6974294f6e3eb9686\",\n  \"trust_level\": 0,\n  \"trust_level_locked\": false,\n  \"flag_level\": 0,\n  \"title\": null,\n  \"suspended_at\": null,\n  \"suspended_till\": null,\n  \"suspended\": null,\n  \"blocked\": false,\n  \"time_read\": \"< 1m\",\n  \"staged\": false,\n  \"days_visited\": 0,\n  \"posts_read_count\": 0,\n  \"topics_entered\": 0,\n  \"post_count\": 0,\n  \"name\": \"47f6974294f6e3eb9686f898f98a67d7\",\n  \"can_send_activation_email\": true,\n  \"can_activate\": true,\n  \"can_deactivate\": true,\n  \"ip_address\": \"192.168.56.1\",\n  \"registration_ip_address\": null,\n  \"can_grant_admin\": true,\n  \"can_revoke_admin\": false,\n  \"can_grant_moderation\": true,\n  \"can_revoke_moderation\": false,\n  \"can_impersonate\": true,\n  \"like_count\": 0,\n  \"like_given_count\": 0,\n  \"topic_count\": 0,\n  \"flags_given_count\": 0,\n  \"flags_received_count\": 0,\n  \"private_topics_count\": 0,\n  \"can_delete_all_posts\": true,\n  \"can_be_deleted\": true,\n  \"can_be_anonymized\": true,\n  \"suspend_reason\": null,\n  \"primary_group_id\": null,\n  \"badge_count\": 0,\n  \"warnings_received_count\": 0,\n  \"bounce_score\": 0,\n  \"reset_bounce_score_after\": null,\n  \"can_view_action_logs\": true,\n  \"single_sign_on_record\": {\n    \"user_id\": 4,\n    \"external_id\": \"847\",\n    \"last_payload\": \"nonce=bcbe1f1b1202fe597feaa2e3f23d566c&name=47f6974294f6e3eb9686f898f98a67d7&username=47f6974294f6e3eb9686f898f98a67d7&email=47f6974294f6e3eb9686f898f98a67d7%40test.com&require_activation=true&external_id=847\",\n    \"created_at\": \"2017-06-23T16:30:22.658Z\",\n    \"updated_at\": \"2017-06-23T16:30:22.658Z\",\n    \"external_username\": \"47f6974294f6e3eb9686f898f98a67d7\",\n    \"external_email\": \"47f6974294f6e3eb9686f898f98a67d7@test.com\",\n    \"external_name\": \"47f6974294f6e3eb9686f898f98a67d7\",\n    \"external_avatar_url\": null\n  },\n  \"approved_by\": null,\n  \"suspended_by\": null,\n  \"groups\": [\n    {\n      \"id\": 10,\n      \"automatic\": true,\n      \"name\": \"trust_level_0\",\n      \"display_name\": \"trust_level_0\",\n      \"user_count\": 6,\n      \"alias_level\": 0,\n      \"visible\": true,\n      \"automatic_membership_email_domains\": null,\n      \"automatic_membership_retroactive\": false,\n      \"primary_group\": false,\n      \"title\": null,\n      \"grant_trust_level\": null,\n      \"incoming_email\": null,\n      \"has_messages\": false,\n      \"flair_url\": null,\n      \"flair_bg_color\": null,\n      \"flair_color\": null,\n      \"bio_raw\": null,\n      \"bio_cooked\": null,\n      \"public\": false,\n      \"allow_membership_requests\": false,\n      \"full_name\": null,\n      \"default_notification_level\": 3\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/api_key.json",
    "content": "{\n  \"key\": {\n    \"id\": 5,\n    \"key\": \"e722e04df8cf6d097d565ca04eea1ff8e9e8f09beb405bae6f0c79852916f334\",\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/backups.json",
    "content": "[\n  {\n    \"filename\": \"discourse-2015-01-10-065015.tar.gz\",\n    \"size\": 557075,\n    \"link\": \"//localhost:3000/admin/backups/discourse-2015-01-10-065015.tar.gz\"\n  },\n  {\n    \"filename\": \"2014-02-10-065935.tar.gz\",\n    \"size\": 5,\n    \"link\": \"//localhost:3000/admin/backups/2014-02-10-065935.tar.gz\"\n  }\n]\n"
  },
  {
    "path": "spec/fixtures/badges.json",
    "content": "{\n  \"badges\": [\n    {\n      \"id\": 9,\n      \"name\": \"Autobiographer\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT u.id user_id, current_timestamp granted_at\\n    FROM users u\\n    JOIN user_profiles up on u.id = up.user_id\\n    WHERE bio_raw IS NOT NULL AND LENGTH(TRIM(bio_raw)) > 10 AND\\n          uploaded_avatar_id IS NOT NULL AND\\n          (:backfill OR u.id IN (:user_ids) )\\n\",\n      \"trigger\": 8,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 11,\n      \"name\": \"First Like\",\n      \"description\": null,\n      \"grant_count\": 1,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT pa1.user_id, pa1.created_at granted_at, pa1.post_id\\n    FROM (\\n      SELECT pa.user_id, min(pa.id) id\\n      FROM post_actions pa\\n      JOIN badge_posts p on p.id = pa.post_id\\n      WHERE post_action_type_id = 2 AND\\n        (:backfill OR pa.post_id IN (:post_ids) )\\n      GROUP BY pa.user_id\\n    ) x\\n    JOIN post_actions pa1 on pa1.id = x.id\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 14,\n      \"name\": \"First Link\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT l.user_id, l.post_id, l.created_at granted_at\\n    FROM\\n    (\\n      SELECT MIN(l1.id) id\\n      FROM topic_links l1\\n      JOIN badge_posts p1 ON p1.id = l1.post_id\\n      JOIN badge_posts p2 ON p2.id = l1.link_post_id\\n      WHERE NOT reflection AND p1.topic_id <> p2.topic_id AND not quote AND\\n        (:backfill OR ( p1.id in (:post_ids) ))\\n      GROUP BY l1.user_id\\n    ) ids\\n    JOIN topic_links l ON l.id = ids.id\\n\",\n      \"trigger\": 2,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 15,\n      \"name\": \"First Quote\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT ids.user_id, q.post_id, q.created_at granted_at\\n    FROM\\n    (\\n      SELECT p1.user_id, MIN(q1.id) id\\n      FROM quoted_posts q1\\n      JOIN badge_posts p1 ON p1.id = q1.post_id\\n      JOIN badge_posts p2 ON p2.id = q1.quoted_post_id\\n      WHERE (:backfill OR ( p1.id IN (:post_ids) ))\\n      GROUP BY p1.user_id\\n    ) ids\\n    JOIN quoted_posts q ON q.id = ids.id\\n\",\n      \"trigger\": 2,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 12,\n      \"name\": \"First Share\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT views.user_id, i2.post_id, i2.created_at granted_at\\n    FROM\\n    (\\n      SELECT i.user_id, MIN(i.id) i_id\\n      FROM incoming_links i\\n      JOIN badge_posts p on p.id = i.post_id\\n      WHERE i.user_id IS NOT NULL\\n      GROUP BY i.user_id\\n    ) as views\\n    JOIN incoming_links i2 ON i2.id = views.i_id\\n\",\n      \"trigger\": 0,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 16,\n      \"name\": \"Read Guidelines\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT user_id, read_faq granted_at\\n    FROM user_stats\\n    WHERE read_faq IS NOT NULL AND (user_id IN (:user_ids) OR :backfill)\\n\",\n      \"trigger\": 8,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 17,\n      \"name\": \"Reader\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 1,\n      \"system\": true,\n      \"query\": \"    SELECT id user_id, current_timestamp granted_at\\n    FROM users\\n    WHERE id IN\\n    (\\n      SELECT pt.user_id\\n      FROM post_timings pt\\n      JOIN badge_posts b ON b.post_number = pt.post_number AND\\n                            b.topic_id = pt.topic_id\\n      JOIN topics t ON t.id = pt.topic_id\\n      LEFT JOIN user_badges ub ON ub.badge_id = 17 AND ub.user_id = pt.user_id\\n      WHERE ub.id IS NULL AND t.posts_count > 100\\n      GROUP BY pt.user_id, pt.topic_id, t.posts_count\\n      HAVING count(*) >= t.posts_count\\n    )\\n\",\n      \"trigger\": null,\n      \"target_posts\": false,\n      \"auto_revoke\": false,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 23,\n      \"name\": \"Great Share\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"query\": \"    SELECT views.user_id, i2.post_id, i2.created_at granted_at\\n    FROM\\n    (\\n      SELECT i.user_id, MIN(i.id) i_id\\n      FROM incoming_links i\\n      JOIN badge_posts p on p.id = i.post_id\\n      WHERE i.user_id IS NOT NULL\\n      GROUP BY i.user_id,i.post_id\\n      HAVING COUNT(*) > 1000\\n    ) as views\\n    JOIN incoming_links i2 ON i2.id = views.i_id\\n\",\n      \"trigger\": 0,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 1\n    },\n    {\n      \"id\": 22,\n      \"name\": \"Good Share\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"query\": \"    SELECT views.user_id, i2.post_id, i2.created_at granted_at\\n    FROM\\n    (\\n      SELECT i.user_id, MIN(i.id) i_id\\n      FROM incoming_links i\\n      JOIN badge_posts p on p.id = i.post_id\\n      WHERE i.user_id IS NOT NULL\\n      GROUP BY i.user_id,i.post_id\\n      HAVING COUNT(*) > 300\\n    ) as views\\n    JOIN incoming_links i2 ON i2.id = views.i_id\\n\",\n      \"trigger\": 0,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 2\n    },\n    {\n      \"id\": 10,\n      \"name\": \"Editor\",\n      \"description\": null,\n      \"grant_count\": 1,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"query\": \"    SELECT p.user_id, min(p.id) post_id, min(p.created_at) granted_at\\n    FROM badge_posts p\\n    WHERE p.self_edits > 0 AND\\n        (:backfill OR p.id IN (:post_ids) )\\n    GROUP BY p.user_id\\n\",\n      \"trigger\": 2,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 13,\n      \"name\": \"First Flag\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"query\": \"    SELECT pa1.user_id, pa1.created_at granted_at, pa1.post_id\\n    FROM (\\n      SELECT pa.user_id, min(pa.id) id\\n      FROM post_actions pa\\n      JOIN badge_posts p on p.id = pa.post_id\\n      WHERE post_action_type_id IN (3,4,7,8) AND\\n        (:backfill OR pa.post_id IN (:post_ids) )\\n      GROUP BY pa.user_id\\n    ) x\\n    JOIN post_actions pa1 on pa1.id = x.id\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": false,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 21,\n      \"name\": \"Nice Share\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"query\": \"    SELECT views.user_id, i2.post_id, i2.created_at granted_at\\n    FROM\\n    (\\n      SELECT i.user_id, MIN(i.id) i_id\\n      FROM incoming_links i\\n      JOIN badge_posts p on p.id = i.post_id\\n      WHERE i.user_id IS NOT NULL\\n      GROUP BY i.user_id,i.post_id\\n      HAVING COUNT(*) > 25\\n    ) as views\\n    JOIN incoming_links i2 ON i2.id = views.i_id\\n\",\n      \"trigger\": 0,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 5,\n      \"name\": \"Welcome\",\n      \"description\": null,\n      \"grant_count\": 1,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"query\": \"    SELECT p.user_id, min(post_id) post_id, min(pa.created_at) granted_at\\n    FROM post_actions pa\\n    JOIN badge_posts p on p.id = pa.post_id\\n    WHERE post_action_type_id = 2 AND\\n        (:backfill OR pa.post_id IN (:post_ids) )\\n    GROUP BY p.user_id\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 8,\n      \"name\": \"Great Post\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 3,\n      \"system\": true,\n      \"query\": \"\\n    SELECT p.user_id, p.id post_id, p.updated_at granted_at\\n    FROM badge_posts p\\n    WHERE p.post_number > 1 AND p.like_count >= 50 AND\\n      (:backfill OR p.id IN (:post_ids) )\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 1\n    },\n    {\n      \"id\": 20,\n      \"name\": \"Great Topic\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 3,\n      \"system\": true,\n      \"query\": \"\\n    SELECT p.user_id, p.id post_id, p.updated_at granted_at\\n    FROM badge_posts p\\n    WHERE p.post_number = 1 AND p.like_count >= 50 AND\\n      (:backfill OR p.id IN (:post_ids) )\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 1\n    },\n    {\n      \"id\": 7,\n      \"name\": \"Good Post\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 3,\n      \"system\": true,\n      \"query\": \"\\n    SELECT p.user_id, p.id post_id, p.updated_at granted_at\\n    FROM badge_posts p\\n    WHERE p.post_number > 1 AND p.like_count >= 25 AND\\n      (:backfill OR p.id IN (:post_ids) )\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 2\n    },\n    {\n      \"id\": 19,\n      \"name\": \"Good Topic\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 3,\n      \"system\": true,\n      \"query\": \"\\n    SELECT p.user_id, p.id post_id, p.updated_at granted_at\\n    FROM badge_posts p\\n    WHERE p.post_number = 1 AND p.like_count >= 25 AND\\n      (:backfill OR p.id IN (:post_ids) )\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 2\n    },\n    {\n      \"id\": 6,\n      \"name\": \"Nice Post\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 3,\n      \"system\": true,\n      \"query\": \"\\n    SELECT p.user_id, p.id post_id, p.updated_at granted_at\\n    FROM badge_posts p\\n    WHERE p.post_number > 1 AND p.like_count >= 10 AND\\n      (:backfill OR p.id IN (:post_ids) )\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 18,\n      \"name\": \"Nice Topic\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": true,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 3,\n      \"system\": true,\n      \"query\": \"\\n    SELECT p.user_id, p.id post_id, p.updated_at granted_at\\n    FROM badge_posts p\\n    WHERE p.post_number = 1 AND p.like_count >= 10 AND\\n      (:backfill OR p.id IN (:post_ids) )\\n\",\n      \"trigger\": 1,\n      \"target_posts\": true,\n      \"auto_revoke\": true,\n      \"show_posts\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 4,\n      \"name\": \"Leader\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": true,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-user\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 4,\n      \"system\": true,\n      \"query\": \"\\n    SELECT u.id user_id, current_timestamp granted_at FROM users u\\n    WHERE trust_level >= 4 AND (\\n      :backfill OR u.id IN (:user_ids)\\n    )\\n\",\n      \"trigger\": 4,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 1\n    },\n    {\n      \"id\": 3,\n      \"name\": \"Regular\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": true,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-user\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 4,\n      \"system\": true,\n      \"query\": \"\\n    SELECT u.id user_id, current_timestamp granted_at FROM users u\\n    WHERE trust_level >= 3 AND (\\n      :backfill OR u.id IN (:user_ids)\\n    )\\n\",\n      \"trigger\": 4,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 2\n    },\n    {\n      \"id\": 1,\n      \"name\": \"Basic User\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-user\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 4,\n      \"system\": true,\n      \"query\": \"\\n    SELECT u.id user_id, current_timestamp granted_at FROM users u\\n    WHERE trust_level >= 1 AND (\\n      :backfill OR u.id IN (:user_ids)\\n    )\\n\",\n      \"trigger\": 4,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 2,\n      \"name\": \"Member\",\n      \"description\": null,\n      \"grant_count\": 0,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-user\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 4,\n      \"system\": true,\n      \"query\": \"\\n    SELECT u.id user_id, current_timestamp granted_at FROM users u\\n    WHERE trust_level >= 2 AND (\\n      :backfill OR u.id IN (:user_ids)\\n    )\\n\",\n      \"trigger\": 4,\n      \"target_posts\": false,\n      \"auto_revoke\": true,\n      \"show_posts\": false,\n      \"badge_type_id\": 3\n    }\n  ],\n  \"badge_types\": [\n    {\n      \"id\": 3,\n      \"name\": \"Bronze\",\n      \"sort_order\": 7\n    },\n    {\n      \"id\": 1,\n      \"name\": \"Gold\",\n      \"sort_order\": 9\n    },\n    {\n      \"id\": 2,\n      \"name\": \"Silver\",\n      \"sort_order\": 8\n    }\n  ],\n  \"badge_groupings\": [\n    {\n      \"id\": 1,\n      \"name\": \"Getting Started\",\n      \"description\": null,\n      \"position\": 10\n    },\n    {\n      \"id\": 2,\n      \"name\": \"Community\",\n      \"description\": null,\n      \"position\": 11\n    },\n    {\n      \"id\": 3,\n      \"name\": \"Posting\",\n      \"description\": null,\n      \"position\": 12\n    },\n    {\n      \"id\": 4,\n      \"name\": \"Trust Level\",\n      \"description\": null,\n      \"position\": 13\n    },\n    {\n      \"id\": 5,\n      \"name\": \"Other\",\n      \"description\": null,\n      \"position\": 14\n    }\n  ],\n  \"admin_badges\": {\n    \"protected_system_fields\": [\n      \"badge_type_id\",\n      \"multiple_grant\",\n      \"target_posts\",\n      \"show_posts\",\n      \"query\",\n      \"trigger\",\n      \"auto_revoke\",\n      \"listable\"\n    ],\n    \"triggers\": {\n      \"none\": 0,\n      \"post_action\": 1,\n      \"post_revision\": 2,\n      \"trust_level_change\": 4,\n      \"user_change\": 8\n    },\n    \"badge_ids\": [\n      9,\n      11,\n      14,\n      15,\n      12,\n      16,\n      17,\n      23,\n      22,\n      10,\n      13,\n      21,\n      5,\n      8,\n      20,\n      7,\n      19,\n      6,\n      18,\n      4,\n      3,\n      1,\n      2\n    ],\n    \"badge_grouping_ids\": [\n      1,\n      2,\n      3,\n      4,\n      5\n    ],\n    \"badge_type_ids\": [\n      1,\n      2,\n      3\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/categories.json",
    "content": "{\n  \"featured_users\": [],\n  \"category_list\": {\n    \"can_create_category\": false,\n    \"can_create_topic\": false,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": null,\n    \"categories\": [\n      {\n        \"id\": 1,\n        \"name\": \"test1\",\n        \"color\": \"0000FF\",\n        \"text_color\": \"FFFFFF\",\n        \"slug\": \"test1\",\n        \"topic_count\": 0,\n        \"description\": \"Test category #1.\",\n        \"topic_url\": \"/t/category-definition-for-test1/1\",\n        \"hotness\": 5,\n        \"read_restricted\": false,\n        \"permission\": null,\n        \"post_count\": 0,\n        \"topics_week\": 0,\n        \"topics_month\": 0,\n        \"topics_year\": 0,\n        \"description_excerpt\": \"Test category #1.\",\n        \"featured_user_id    s\": [],\n        \"topics\": []\n      },\n      {\n        \"id\": 2,\n        \"name\": \"test2\",\n        \"color\": \"00FF00\",\n        \"text_color\": \"FFFFFF\",\n        \"slug\": \"test2\",\n        \"topic_count\": 0,\n        \"description\": \"Test category #2.\",\n        \"topic_url\": \"/t/category-definition-for-test2/2\",\n        \"hotness\": 5,\n        \"read_restricted\": false,\n        \"permission\": null,\n        \"post_count\": 0,\n        \"topics_week\": 0,\n        \"topics_month\": 0,\n        \"topics_year\": 0,\n        \"description_excerpt\": \"Test category #2.\",\n        \"featured_user_ids\": [],\n        \"topics\": []\n      },\n      {\n        \"id\": 3,\n        \"name\": \"test3\",\n        \"color\": \"FF0000\",\n        \"text_color\": \"FFFFFF\",\n        \"slug\": \"test3\",\n        \"topic_count\": 0,\n        \"description\": \"Test category #3.\",\n        \"topic_url\": \"/t/category-definition-for-test3/3\",\n        \"hotness\": 5,\n        \"read_restricted\": false,\n        \"permission\": null,\n        \"post_count\": 0,\n        \"topics_week\": 0,\n        \"topics_month\": 0,\n        \"topics_year\": 0,\n        \"description_excerpt\": \"Test category #3.\",\n        \"featured_user_ids\": [],\n        \"topics\": []\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/category_latest_topics.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 3,\n      \"username\": \"fuzzy\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/c0c59575e86a794d733db4ee29b2baf4.png?s={size}&r=pg&d=identicon\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": true,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": 2,\n    \"topics\": [\n      {\n        \"id\": 5,\n        \"title\": \"About the category\",\n        \"fancy_title\": \"About the category\",\n        \"slug\": \"about-the-category\",\n        \"posts_count\": 2,\n        \"reply_count\": 0,\n        \"highest_post_number\": 2,\n        \"image_url\": null,\n        \"created_at\": \"2014-04-16T14:10:50.950-04:00\",\n        \"last_posted_at\": \"2014-04-16T14:31:10.406-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2014-04-17T13:35:01.102-04:00\",\n        \"unseen\": false,\n        \"last_read_post_number\": 2,\n        \"unread\": 0,\n        \"new_posts\": 0,\n        \"pinned\": true,\n        \"unpinned\": null,\n        \"excerpt\": null,\n        \"visible    \": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 0,\n        \"like_count\": 0,\n        \"starred\": false,\n        \"has_summary\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"fuzzy\",\n        \"category_id\": 2,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 3\n          }\n        ]\n      },\n      {\n        \"id\": 4,\n        \"title\": \"This is a markdown post\",\n        \"fancy_title\": \"This is a markdown post\",\n        \"slug\": \"this-is-a-markdown-post\",\n        \"posts_count\": 2,\n        \"reply_count\": 0,\n        \"highest_post_number\": 2,\n        \"image_url\": null,\n        \"created_at\": \"2014-04-16T13:52:58.777-04:00\",\n        \"last_posted_at\": \"2014-04-16T14:31:37.371-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2014-04-16T14:31:37.371-04:00\",\n        \"unseen\": false,\n        \"last_read_post_number\": 2,\n        \"unread\": 0,\n        \"new_posts\": 0,\n        \"pinned\": false,\n        \"unpinned    \": null,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 0,\n        \"like_count\": 0,\n        \"starred\": false,\n        \"has_summary\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"fuzzy\",\n        \"category_id\": 2,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 3\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/category_topics.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 3,\n      \"username\": \"fuzzy\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/c0c59575e86a794d733db4ee29b2baf4.png?s={size}&r=pg&d=identicon\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": true,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": 2,\n    \"topics\": [\n      {\n        \"id\": 5,\n        \"title\": \"About the category\",\n        \"fancy_title\": \"About the category\",\n        \"slug\": \"about-the-category\",\n        \"posts_count\": 2,\n        \"reply_count\": 0,\n        \"highest_post_number\": 2,\n        \"image_url\": null,\n        \"created_at\": \"2014-04-16T14:10:50.950-04:00\",\n        \"last_posted_at\": \"2014-04-16T14:31:10.406-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2014-04-17T13:35:01.102-04:00\",\n        \"unseen\": false,\n        \"last_read_post_number\": 2,\n        \"unread\": 0,\n        \"new_posts\": 0,\n        \"pinned\": true,\n        \"unpinned\": null,\n        \"excerpt\": null,\n        \"visible    \": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 0,\n        \"like_count\": 0,\n        \"starred\": false,\n        \"has_summary\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"fuzzy\",\n        \"category_id\": 2,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 3\n          }\n        ]\n      },\n      {\n        \"id\": 4,\n        \"title\": \"This is a markdown post\",\n        \"fancy_title\": \"This is a markdown post\",\n        \"slug\": \"this-is-a-markdown-post\",\n        \"posts_count\": 2,\n        \"reply_count\": 0,\n        \"highest_post_number\": 2,\n        \"image_url\": null,\n        \"created_at\": \"2014-04-16T13:52:58.777-04:00\",\n        \"last_posted_at\": \"2014-04-16T14:31:37.371-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2014-04-16T14:31:37.371-04:00\",\n        \"unseen\": false,\n        \"last_read_post_number\": 2,\n        \"unread\": 0,\n        \"new_posts\": 0,\n        \"pinned\": false,\n        \"unpinned    \": null,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 0,\n        \"like_count\": 0,\n        \"starred\": false,\n        \"has_summary\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"fuzzy\",\n        \"category_id\": 2,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 3\n          }\n        ]\n      }\n    ]\n  }\n}"
  },
  {
    "path": "spec/fixtures/create_topic_with_tags.json",
    "content": "{\n  \"id\": 29,\n  \"name\": null,\n  \"username\": \"blake\",\n  \"avatar_template\": \"/user_avatar/localhost/blake/{size}/3_2.png\",\n  \"created_at\": \"2020-07-09T15:52:43.802Z\",\n  \"cooked\": \"<p>cb8ee24eb3c5a81139958cf2b6bee9bd 2a7f7ea218e24c9ee6528cd383527f99 5a3006b92918946230385393a83b6005</p>\",\n  \"post_number\": 1,\n  \"post_type\": 1,\n  \"updated_at\": \"2020-07-09T15:52:43.802Z\",\n  \"reply_count\": 0,\n  \"reply_to_post_number\": null,\n  \"quote_count\": 0,\n  \"incoming_link_count\": 0,\n  \"reads\": 0,\n  \"readers_count\": 0,\n  \"score\": 0,\n  \"yours\": true,\n  \"topic_id\": 21,\n  \"topic_slug\": \"1412a036bfe-6480718525e-9f71f98ef46\",\n  \"display_username\": null,\n  \"primary_group_name\": null,\n  \"primary_group_flair_url\": null,\n  \"primary_group_flair_bg_color\": null,\n  \"primary_group_flair_color\": null,\n  \"version\": 1,\n  \"can_edit\": true,\n  \"can_delete\": false,\n  \"can_recover\": false,\n  \"can_wiki\": true,\n  \"user_title\": null,\n  \"actions_summary\": [\n    {\n      \"id\": 3,\n      \"can_act\": true\n    },\n    {\n      \"id\": 4,\n      \"can_act\": true\n    },\n    {\n      \"id\": 8,\n      \"can_act\": true\n    },\n    {\n      \"id\": 7,\n      \"can_act\": true\n    }\n  ],\n  \"moderator\": false,\n  \"admin\": true,\n  \"staff\": true,\n  \"user_id\": 1,\n  \"draft_sequence\": 0,\n  \"hidden\": false,\n  \"trust_level\": 1,\n  \"deleted_at\": null,\n  \"user_deleted\": false,\n  \"edit_reason\": null,\n  \"can_view_edit_history\": true,\n  \"wiki\": false,\n  \"reviewable_id\": null,\n  \"reviewable_score_count\": 0,\n  \"reviewable_score_pending_count\": 0\n}\n"
  },
  {
    "path": "spec/fixtures/email_list_all.json",
    "content": "[\n  {\n    \"id\": 64,\n    \"reply_key\": null,\n    \"to_address\": \"steve7@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 22,\n    \"created_at\": \"2015-01-03T13:33:46.730Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 22,\n      \"username\": \"steve7\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve7/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 63,\n    \"reply_key\": null,\n    \"to_address\": \"steve6@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 21,\n    \"created_at\": \"2015-01-02T17:18:47.098Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 21,\n      \"username\": \"steve6\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve6/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 62,\n    \"reply_key\": null,\n    \"to_address\": \"steve5@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 20,\n    \"created_at\": \"2015-01-02T16:05:38.192Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 20,\n      \"username\": \"steve5\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve5/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 61,\n    \"reply_key\": null,\n    \"to_address\": \"steve4@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 19,\n    \"created_at\": \"2015-01-02T15:59:27.137Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 19,\n      \"username\": \"steve4\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve4/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 60,\n    \"reply_key\": null,\n    \"to_address\": \"steve5@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 20,\n    \"created_at\": \"2015-01-02T15:57:57.061Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 20,\n      \"username\": \"steve5\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve5/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 59,\n    \"reply_key\": null,\n    \"to_address\": \"steve4@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 19,\n    \"created_at\": \"2015-01-02T15:52:36.210Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 19,\n      \"username\": \"steve4\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve4/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 58,\n    \"reply_key\": null,\n    \"to_address\": \"steve3@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 18,\n    \"created_at\": \"2015-01-02T15:50:29.330Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 18,\n      \"username\": \"steve3\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve3/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 57,\n    \"reply_key\": null,\n    \"to_address\": \"steve3@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 18,\n    \"created_at\": \"2015-01-02T14:07:40.357Z\",\n    \"skipped\": true,\n    \"skipped_reason\": \"The notification this email is about has already been read\",\n    \"user\": {\n      \"id\": 18,\n      \"username\": \"steve3\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve3/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 56,\n    \"reply_key\": null,\n    \"to_address\": \"steve3@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 18,\n    \"created_at\": \"2015-01-02T13:53:04.352Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 18,\n      \"username\": \"steve3\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve3/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 55,\n    \"reply_key\": null,\n    \"to_address\": \"steve2@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 17,\n    \"created_at\": \"2015-01-02T13:39:40.455Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 17,\n      \"username\": \"steve2\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve2/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 54,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 1,\n    \"created_at\": \"2015-01-02T13:38:11.418Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"uploaded_avatar_id\": 7,\n      \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\"\n    }\n  },\n  {\n    \"id\": 53,\n    \"reply_key\": null,\n    \"to_address\": \"dave3@gotham.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 10,\n    \"created_at\": \"2015-01-01T13:08:57.914Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 10,\n      \"username\": \"dave3\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave3/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 52,\n    \"reply_key\": null,\n    \"to_address\": \"dave2@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 9,\n    \"created_at\": \"2015-01-01T01:22:45.479Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 9,\n      \"username\": \"dave2\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave2/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 51,\n    \"reply_key\": null,\n    \"to_address\": \"phil@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 7,\n    \"created_at\": \"2015-01-01T01:01:33.316Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 50,\n    \"reply_key\": null,\n    \"to_address\": \"dave@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 8,\n    \"created_at\": \"2015-01-01T01:01:33.222Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 8,\n      \"username\": \"dave\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 49,\n    \"reply_key\": null,\n    \"to_address\": \"steve@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 4,\n    \"created_at\": \"2015-01-01T01:01:33.200Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 4,\n      \"username\": \"steve\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 48,\n    \"reply_key\": null,\n    \"to_address\": \"steve@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 4,\n    \"created_at\": \"2015-01-01T01:01:33.141Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 4,\n      \"username\": \"steve\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 47,\n    \"reply_key\": null,\n    \"to_address\": \"steve999@example.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 4,\n    \"created_at\": \"2015-01-01T01:01:32.846Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 4,\n      \"username\": \"steve\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 46,\n    \"reply_key\": null,\n    \"to_address\": \"steve345@example.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 4,\n    \"created_at\": \"2015-01-01T01:01:32.844Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 4,\n      \"username\": \"steve\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 45,\n    \"reply_key\": null,\n    \"to_address\": \"robin3@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2015-01-01T01:01:24.446Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 44,\n    \"reply_key\": null,\n    \"to_address\": \"robin3@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2015-01-01T01:01:24.445Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 43,\n    \"reply_key\": null,\n    \"to_address\": \"robin3@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2015-01-01T01:01:24.444Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 42,\n    \"reply_key\": null,\n    \"to_address\": \"robin3@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2015-01-01T01:01:24.441Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 41,\n    \"reply_key\": null,\n    \"to_address\": \"no_email_found\",\n    \"email_type\": \"signup\",\n    \"user_id\": null,\n    \"created_at\": \"2014-12-31T18:12:05.707Z\",\n    \"skipped\": true,\n    \"skipped_reason\": \"Can't find user with id 6\",\n    \"user\": null\n  },\n  {\n    \"id\": 40,\n    \"reply_key\": null,\n    \"to_address\": \"foo@bar.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 8,\n    \"created_at\": \"2014-12-31T17:56:39.302Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 8,\n      \"username\": \"dave\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 39,\n    \"reply_key\": null,\n    \"to_address\": \"dave2@example.com\",\n    \"email_type\": \"user_private_message\",\n    \"user_id\": 9,\n    \"created_at\": \"2014-12-31T17:56:21.004Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 9,\n      \"username\": \"dave2\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave2/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 38,\n    \"reply_key\": null,\n    \"to_address\": \"fake@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 16,\n    \"created_at\": \"2014-12-31T17:54:32.454Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 16,\n      \"username\": \"carl\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/carl/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 37,\n    \"reply_key\": null,\n    \"to_address\": \"test_user2@example.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 1,\n    \"created_at\": \"2014-12-31T17:53:48.162Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"uploaded_avatar_id\": 7,\n      \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\"\n    }\n  },\n  {\n    \"id\": 36,\n    \"reply_key\": null,\n    \"to_address\": \"foo@bar.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 7,\n    \"created_at\": \"2014-12-31T17:13:15.797Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 35,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 7,\n    \"created_at\": \"2014-12-31T17:13:06.259Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 34,\n    \"reply_key\": null,\n    \"to_address\": \"dave7@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 14,\n    \"created_at\": \"2014-12-31T17:12:43.031Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 14,\n      \"username\": \"dave7\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave7/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 33,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2014-12-31T17:12:43.017Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 32,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2014-12-31T17:12:43.010Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 31,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 1,\n    \"created_at\": \"2014-12-31T17:12:43.007Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"uploaded_avatar_id\": 7,\n      \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\"\n    }\n  },\n  {\n    \"id\": 30,\n    \"reply_key\": null,\n    \"to_address\": \"foo@bar.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 7,\n    \"created_at\": \"2014-12-31T17:12:42.959Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 29,\n    \"reply_key\": null,\n    \"to_address\": \"dave4@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 11,\n    \"created_at\": \"2014-12-31T17:11:51.195Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 11,\n      \"username\": \"dave4\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave4/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 28,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2014-12-31T17:11:51.193Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 27,\n    \"reply_key\": null,\n    \"to_address\": \"dave5@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 12,\n    \"created_at\": \"2014-12-31T17:11:51.192Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 12,\n      \"username\": \"dave5\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave5/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 26,\n    \"reply_key\": null,\n    \"to_address\": \"foo@bar.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 8,\n    \"created_at\": \"2014-12-31T17:11:51.144Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 8,\n      \"username\": \"dave\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 25,\n    \"reply_key\": null,\n    \"to_address\": \"dave6@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 13,\n    \"created_at\": \"2014-12-31T17:11:14.401Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 13,\n      \"username\": \"dave6\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave6/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 24,\n    \"reply_key\": null,\n    \"to_address\": \"test_user2@example.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 1,\n    \"created_at\": \"2014-12-31T17:11:14.399Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"uploaded_avatar_id\": 7,\n      \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\"\n    }\n  },\n  {\n    \"id\": 23,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 7,\n    \"created_at\": \"2014-12-31T17:11:14.398Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 22,\n    \"reply_key\": null,\n    \"to_address\": \"foo@bar.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 7,\n    \"created_at\": \"2014-12-31T17:10:28.274Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 21,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2014-12-31T17:10:28.257Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 20,\n    \"reply_key\": null,\n    \"to_address\": \"steve123@example.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 4,\n    \"created_at\": \"2014-12-31T17:10:28.252Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 4,\n      \"username\": \"steve\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 19,\n    \"reply_key\": null,\n    \"to_address\": \"steve123@example.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 4,\n    \"created_at\": \"2014-12-31T17:10:03.163Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 4,\n      \"username\": \"steve\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 18,\n    \"reply_key\": null,\n    \"to_address\": \"dave8@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 15,\n    \"created_at\": \"2014-12-31T17:09:30.028Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 15,\n      \"username\": \"dave8\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave8/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 17,\n    \"reply_key\": null,\n    \"to_address\": \"dave3@example.com\",\n    \"email_type\": \"signup\",\n    \"user_id\": 10,\n    \"created_at\": \"2014-12-31T17:09:30.011Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 10,\n      \"username\": \"dave3\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/dave3/{size}/2.png\"\n    }\n  },\n  {\n    \"id\": 16,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 2,\n    \"created_at\": \"2014-12-31T17:09:30.005Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 2,\n      \"username\": \"robin\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\"\n    }\n  },\n  {\n    \"id\": 15,\n    \"reply_key\": null,\n    \"to_address\": \"batman@gotham.com\",\n    \"email_type\": \"authorize_email\",\n    \"user_id\": 7,\n    \"created_at\": \"2014-12-31T17:09:29.997Z\",\n    \"skipped\": false,\n    \"user\": {\n      \"id\": 7,\n      \"username\": \"phil12\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\"\n    }\n  }\n]\n"
  },
  {
    "path": "spec/fixtures/email_settings.json",
    "content": "{\n  \"delivery_method\": \"smtp\",\n  \"settings\": [\n    {\n      \"name\": \"address\",\n      \"value\": \"localhost\"\n    },\n    {\n      \"name\": \"port\",\n      \"value\": 1025\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/group.json",
    "content": "{\"basic_group\": {\n        \"id\": 101,\n        \"name\": \"group_1\",\n        \"user_count\": 17,\n        \"automatic\": false,\n        \"alias_level\": 0,\n        \"visible\": true\n    }\n}\n"
  },
  {
    "path": "spec/fixtures/groups.json",
    "content": "[\n    {\n        \"id\": 101,\n        \"name\": \"group_1\",\n        \"user_count\": 17,\n        \"automatic\": false,\n        \"alias_level\": 0,\n        \"visible\": true\n    },\n    {\n        \"id\": 102,\n        \"name\": \"group_2\",\n        \"user_count\": 4,\n        \"automatic\": false,\n        \"alias_level\": 0,\n        \"visible\": true\n    }\n]\n"
  },
  {
    "path": "spec/fixtures/hot.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 1,\n      \"username\": \"test_users\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": false,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": null,\n    \"topics\": [\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #1\",\n        \"fancy_title\": \"Test topic #1\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T14:38:46.480-05:00\",\n        \"last_posted_at\": \"2013-09-13T16:46:59.783-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-09-13T16:46:59.783-04:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 1,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 2,\n        \"title\": \"Test topic #2\",\n        \"fancy_title\": \"Test topic #2\",\n        \"slug\": \"test-topic-2\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T14:38:46.480-05:00\",\n        \"last_posted_at\": \"2013-09-13T16:46:59.783-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-09-13T16:46:59.783-04:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 1,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 3,\n        \"title\": \"Test topic #3\",\n        \"fancy_title\": \"Test topic #3\",\n        \"slug\": \"test-topic-3\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T14:38:46.480-05:00\",\n        \"last_posted_at\": \"2013-09-13T16:46:59.783-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-09-13T16:46:59.783-04:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 1,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": \"latest\",\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/latest.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 1,\n      \"username\": \"test_users\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": false,\n    \"more_topics_url\": \"/latest.json?page=1\",\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": null,\n    \"topics\": [\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #1\",\n        \"fancy_title\": \"Test topic #1\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T15:26:25.977-05:00\",\n        \"last_posted_at\": \"2013-02-05T12:14:17.364-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-02-07T13:43:11.191-05:00\",\n        \"unseen\": false,\n        \"pinned\": true,\n        \"excerpt\": \"Welcome! \\n\\nTry is a sandbox, a safe place to play with Discourse and its features.\\n\\nThis site is reset every day (or even more often). Any accounts or posts you create here will not exist tomorrow! \\n\\nFeel free to experim&hellip;\",\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 268,\n        \"like_count\": 14,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #2\",\n        \"fancy_title\": \"Test topic #2\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 2,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-06T13:57:47.984-05:00\",\n        \"last_posted_at\": \"2013-11-06T13:57:48.111-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-06T13:57:48.111-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 1,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #3\",\n        \"fancy_title\": \"Test topic #3\",\n        \"slug\": \"test-topic-3\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T21:46:58.194-05:00\",\n        \"last_posted_at\": \"2013-11-06T13:56:27.753-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-06T13:56:27.753-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 82,\n        \"like_count\": 10,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/list_api_keys.json",
    "content": "{\n  \"keys\": [\n    {\n      \"id\": 1,\n      \"key\": \"test_d7fd0429940\",\n      \"user\": {\n        \"id\": 1,\n        \"username\": \"test_user\",\n        \"uploaded_avatar_id\": 7,\n        \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/members_0.json",
    "content": "{\n    \"members\": [{\n        \"id\": 1,\n        \"name\": \"Test 0\",\n        \"username\": \"Test_0\"\n    }, {\n        \"id\": 2,\n        \"name\": \"Test 1\",\n        \"username\": \"Test_1\"\n    }, {\n        \"id\": 3,\n        \"name\": \"Test 2\",\n        \"username\": \"Test_2\"\n    }, {\n        \"id\": 4,\n        \"name\": \"Test 3\",\n        \"username\": \"Test_3\"\n    }, {\n        \"id\": 5,\n        \"name\": \"Test 4\",\n        \"username\": \"Test_4\"\n    }, {\n        \"id\": 6,\n        \"name\": \"Test 5\",\n        \"username\": \"Test_5\"\n    }, {\n        \"id\": 7,\n        \"name\": \"Test 6\",\n        \"username\": \"Test_6\"\n    }, {\n        \"id\": 8,\n        \"name\": \"Test 7\",\n        \"username\": \"Test_7\"\n    }, {\n        \"id\": 9,\n        \"name\": \"Test 8\",\n        \"username\": \"Test_8\"\n    }, {\n        \"id\": 10,\n        \"name\": \"Test 9\",\n        \"username\": \"Test_9\"\n    }, {\n        \"id\": 11,\n        \"name\": \"Test 10\",\n        \"username\": \"Test_10\"\n    }, {\n        \"id\": 12,\n        \"name\": \"Test 11\",\n        \"username\": \"Test_11\"\n    }, {\n        \"id\": 13,\n        \"name\": \"Test 12\",\n        \"username\": \"Test_12\"\n    }, {\n        \"id\": 14,\n        \"name\": \"Test 13\",\n        \"username\": \"Test_13\"\n    }, {\n        \"id\": 15,\n        \"name\": \"Test 14\",\n        \"username\": \"Test_14\"\n    }, {\n        \"id\": 16,\n        \"name\": \"Test 15\",\n        \"username\": \"Test_15\"\n    }, {\n        \"id\": 17,\n        \"name\": \"Test 16\",\n        \"username\": \"Test_16\"\n    }, {\n        \"id\": 18,\n        \"name\": \"Test 17\",\n        \"username\": \"Test_17\"\n    }, {\n        \"id\": 19,\n        \"name\": \"Test 18\",\n        \"username\": \"Test_18\"\n    }, {\n        \"id\": 20,\n        \"name\": \"Test 19\",\n        \"username\": \"Test_19\"\n    }, {\n        \"id\": 21,\n        \"name\": \"Test 20\",\n        \"username\": \"Test_20\"\n    }, {\n        \"id\": 22,\n        \"name\": \"Test 21\",\n        \"username\": \"Test_21\"\n    }, {\n        \"id\": 23,\n        \"name\": \"Test 22\",\n        \"username\": \"Test_22\"\n    }, {\n        \"id\": 24,\n        \"name\": \"Test 23\",\n        \"username\": \"Test_23\"\n    }, {\n        \"id\": 25,\n        \"name\": \"Test 24\",\n        \"username\": \"Test_24\"\n    }, {\n        \"id\": 26,\n        \"name\": \"Test 25\",\n        \"username\": \"Test_25\"\n    }, {\n        \"id\": 27,\n        \"name\": \"Test 26\",\n        \"username\": \"Test_26\"\n    }, {\n        \"id\": 28,\n        \"name\": \"Test 27\",\n        \"username\": \"Test_27\"\n    }, {\n        \"id\": 29,\n        \"name\": \"Test 28\",\n        \"username\": \"Test_28\"\n    }, {\n        \"id\": 30,\n        \"name\": \"Test 29\",\n        \"username\": \"Test_29\"\n    }, {\n        \"id\": 31,\n        \"name\": \"Test 30\",\n        \"username\": \"Test_30\"\n    }, {\n        \"id\": 32,\n        \"name\": \"Test 31\",\n        \"username\": \"Test_31\"\n    }, {\n        \"id\": 33,\n        \"name\": \"Test 32\",\n        \"username\": \"Test_32\"\n    }, {\n        \"id\": 34,\n        \"name\": \"Test 33\",\n        \"username\": \"Test_33\"\n    }, {\n        \"id\": 35,\n        \"name\": \"Test 34\",\n        \"username\": \"Test_34\"\n    }, {\n        \"id\": 36,\n        \"name\": \"Test 35\",\n        \"username\": \"Test_35\"\n    }, {\n        \"id\": 37,\n        \"name\": \"Test 36\",\n        \"username\": \"Test_36\"\n    }, {\n        \"id\": 38,\n        \"name\": \"Test 37\",\n        \"username\": \"Test_37\"\n    }, {\n        \"id\": 39,\n        \"name\": \"Test 38\",\n        \"username\": \"Test_38\"\n    }, {\n        \"id\": 40,\n        \"name\": \"Test 39\",\n        \"username\": \"Test_39\"\n    }, {\n        \"id\": 41,\n        \"name\": \"Test 40\",\n        \"username\": \"Test_40\"\n    }, {\n        \"id\": 42,\n        \"name\": \"Test 41\",\n        \"username\": \"Test_41\"\n    }, {\n        \"id\": 43,\n        \"name\": \"Test 42\",\n        \"username\": \"Test_42\"\n    }, {\n        \"id\": 44,\n        \"name\": \"Test 43\",\n        \"username\": \"Test_43\"\n    }, {\n        \"id\": 45,\n        \"name\": \"Test 44\",\n        \"username\": \"Test_44\"\n    }, {\n        \"id\": 46,\n        \"name\": \"Test 45\",\n        \"username\": \"Test_45\"\n    }, {\n        \"id\": 47,\n        \"name\": \"Test 46\",\n        \"username\": \"Test_46\"\n    }, {\n        \"id\": 48,\n        \"name\": \"Test 47\",\n        \"username\": \"Test_47\"\n    }, {\n        \"id\": 49,\n        \"name\": \"Test 48\",\n        \"username\": \"Test_48\"\n    }, {\n        \"id\": 50,\n        \"name\": \"Test 49\",\n        \"username\": \"Test_49\"\n    }, {\n        \"id\": 51,\n        \"name\": \"Test 50\",\n        \"username\": \"Test_50\"\n    }, {\n        \"id\": 52,\n        \"name\": \"Test 51\",\n        \"username\": \"Test_51\"\n    }, {\n        \"id\": 53,\n        \"name\": \"Test 52\",\n        \"username\": \"Test_52\"\n    }, {\n        \"id\": 54,\n        \"name\": \"Test 53\",\n        \"username\": \"Test_53\"\n    }, {\n        \"id\": 55,\n        \"name\": \"Test 54\",\n        \"username\": \"Test_54\"\n    }, {\n        \"id\": 56,\n        \"name\": \"Test 55\",\n        \"username\": \"Test_55\"\n    }, {\n        \"id\": 57,\n        \"name\": \"Test 56\",\n        \"username\": \"Test_56\"\n    }, {\n        \"id\": 58,\n        \"name\": \"Test 57\",\n        \"username\": \"Test_57\"\n    }, {\n        \"id\": 59,\n        \"name\": \"Test 58\",\n        \"username\": \"Test_58\"\n    }, {\n        \"id\": 60,\n        \"name\": \"Test 59\",\n        \"username\": \"Test_59\"\n    }, {\n        \"id\": 61,\n        \"name\": \"Test 60\",\n        \"username\": \"Test_60\"\n    }, {\n        \"id\": 62,\n        \"name\": \"Test 61\",\n        \"username\": \"Test_61\"\n    }, {\n        \"id\": 63,\n        \"name\": \"Test 62\",\n        \"username\": \"Test_62\"\n    }, {\n        \"id\": 64,\n        \"name\": \"Test 63\",\n        \"username\": \"Test_63\"\n    }, {\n        \"id\": 65,\n        \"name\": \"Test 64\",\n        \"username\": \"Test_64\"\n    }, {\n        \"id\": 66,\n        \"name\": \"Test 65\",\n        \"username\": \"Test_65\"\n    }, {\n        \"id\": 67,\n        \"name\": \"Test 66\",\n        \"username\": \"Test_66\"\n    }, {\n        \"id\": 68,\n        \"name\": \"Test 67\",\n        \"username\": \"Test_67\"\n    }, {\n        \"id\": 69,\n        \"name\": \"Test 68\",\n        \"username\": \"Test_68\"\n    }, {\n        \"id\": 70,\n        \"name\": \"Test 69\",\n        \"username\": \"Test_69\"\n    }, {\n        \"id\": 71,\n        \"name\": \"Test 70\",\n        \"username\": \"Test_70\"\n    }, {\n        \"id\": 72,\n        \"name\": \"Test 71\",\n        \"username\": \"Test_71\"\n    }, {\n        \"id\": 73,\n        \"name\": \"Test 72\",\n        \"username\": \"Test_72\"\n    }, {\n        \"id\": 74,\n        \"name\": \"Test 73\",\n        \"username\": \"Test_73\"\n    }, {\n        \"id\": 75,\n        \"name\": \"Test 74\",\n        \"username\": \"Test_74\"\n    }, {\n        \"id\": 76,\n        \"name\": \"Test 75\",\n        \"username\": \"Test_75\"\n    }, {\n        \"id\": 77,\n        \"name\": \"Test 76\",\n        \"username\": \"Test_76\"\n    }, {\n        \"id\": 78,\n        \"name\": \"Test 77\",\n        \"username\": \"Test_77\"\n    }, {\n        \"id\": 79,\n        \"name\": \"Test 78\",\n        \"username\": \"Test_78\"\n    }, {\n        \"id\": 80,\n        \"name\": \"Test 79\",\n        \"username\": \"Test_79\"\n    }, {\n        \"id\": 81,\n        \"name\": \"Test 80\",\n        \"username\": \"Test_80\"\n    }, {\n        \"id\": 82,\n        \"name\": \"Test 81\",\n        \"username\": \"Test_81\"\n    }, {\n        \"id\": 83,\n        \"name\": \"Test 82\",\n        \"username\": \"Test_82\"\n    }, {\n        \"id\": 84,\n        \"name\": \"Test 83\",\n        \"username\": \"Test_83\"\n    }, {\n        \"id\": 85,\n        \"name\": \"Test 84\",\n        \"username\": \"Test_84\"\n    }, {\n        \"id\": 86,\n        \"name\": \"Test 85\",\n        \"username\": \"Test_85\"\n    }, {\n        \"id\": 87,\n        \"name\": \"Test 86\",\n        \"username\": \"Test_86\"\n    }, {\n        \"id\": 88,\n        \"name\": \"Test 87\",\n        \"username\": \"Test_87\"\n    }, {\n        \"id\": 89,\n        \"name\": \"Test 88\",\n        \"username\": \"Test_88\"\n    }, {\n        \"id\": 90,\n        \"name\": \"Test 89\",\n        \"username\": \"Test_89\"\n    }, {\n        \"id\": 91,\n        \"name\": \"Test 90\",\n        \"username\": \"Test_90\"\n    }, {\n        \"id\": 92,\n        \"name\": \"Test 91\",\n        \"username\": \"Test_91\"\n    }, {\n        \"id\": 93,\n        \"name\": \"Test 92\",\n        \"username\": \"Test_92\"\n    }, {\n        \"id\": 94,\n        \"name\": \"Test 93\",\n        \"username\": \"Test_93\"\n    }, {\n        \"id\": 95,\n        \"name\": \"Test 94\",\n        \"username\": \"Test_94\"\n    }, {\n        \"id\": 96,\n        \"name\": \"Test 95\",\n        \"username\": \"Test_95\"\n    }, {\n        \"id\": 97,\n        \"name\": \"Test 96\",\n        \"username\": \"Test_96\"\n    }, {\n        \"id\": 98,\n        \"name\": \"Test 97\",\n        \"username\": \"Test_97\"\n    }, {\n        \"id\": 99,\n        \"name\": \"Test 98\",\n        \"username\": \"Test_98\"\n    }, {\n        \"id\": 100,\n        \"name\": \"Test 99\",\n        \"username\": \"Test_99\"\n    }]\n}\n"
  },
  {
    "path": "spec/fixtures/members_1.json",
    "content": "{\n    \"members\": [{\n        \"id\": 101,\n        \"name\": \"Test 100\",\n        \"username\": \"Test_100\"\n    }, {\n        \"id\": 102,\n        \"name\": \"Test 101\",\n        \"username\": \"Test_101\"\n    }, {\n        \"id\": 103,\n        \"name\": \"Test 102\",\n        \"username\": \"Test_102\"\n    }, {\n        \"id\": 104,\n        \"name\": \"Test 103\",\n        \"username\": \"Test_103\"\n    }, {\n        \"id\": 105,\n        \"name\": \"Test 104\",\n        \"username\": \"Test_104\"\n    }, {\n        \"id\": 106,\n        \"name\": \"Test 105\",\n        \"username\": \"Test_105\"\n    }, {\n        \"id\": 107,\n        \"name\": \"Test 106\",\n        \"username\": \"Test_106\"\n    }, {\n        \"id\": 108,\n        \"name\": \"Test 107\",\n        \"username\": \"Test_107\"\n    }, {\n        \"id\": 109,\n        \"name\": \"Test 108\",\n        \"username\": \"Test_108\"\n    }, {\n        \"id\": 110,\n        \"name\": \"Test 109\",\n        \"username\": \"Test_109\"\n    }, {\n        \"id\": 111,\n        \"name\": \"Test 110\",\n        \"username\": \"Test_110\"\n    }, {\n        \"id\": 112,\n        \"name\": \"Test 111\",\n        \"username\": \"Test_111\"\n    }, {\n        \"id\": 113,\n        \"name\": \"Test 112\",\n        \"username\": \"Test_112\"\n    }, {\n        \"id\": 114,\n        \"name\": \"Test 113\",\n        \"username\": \"Test_113\"\n    }, {\n        \"id\": 115,\n        \"name\": \"Test 114\",\n        \"username\": \"Test_114\"\n    }, {\n        \"id\": 116,\n        \"name\": \"Test 115\",\n        \"username\": \"Test_115\"\n    }, {\n        \"id\": 117,\n        \"name\": \"Test 116\",\n        \"username\": \"Test_116\"\n    }, {\n        \"id\": 118,\n        \"name\": \"Test 117\",\n        \"username\": \"Test_117\"\n    }, {\n        \"id\": 119,\n        \"name\": \"Test 118\",\n        \"username\": \"Test_118\"\n    }, {\n        \"id\": 120,\n        \"name\": \"Test 119\",\n        \"username\": \"Test_119\"\n    }, {\n        \"id\": 121,\n        \"name\": \"Test 120\",\n        \"username\": \"Test_120\"\n    }, {\n        \"id\": 122,\n        \"name\": \"Test 121\",\n        \"username\": \"Test_121\"\n    }, {\n        \"id\": 123,\n        \"name\": \"Test 122\",\n        \"username\": \"Test_122\"\n    }, {\n        \"id\": 124,\n        \"name\": \"Test 123\",\n        \"username\": \"Test_123\"\n    }, {\n        \"id\": 125,\n        \"name\": \"Test 124\",\n        \"username\": \"Test_124\"\n    }, {\n        \"id\": 126,\n        \"name\": \"Test 125\",\n        \"username\": \"Test_125\"\n    }, {\n        \"id\": 127,\n        \"name\": \"Test 126\",\n        \"username\": \"Test_126\"\n    }, {\n        \"id\": 128,\n        \"name\": \"Test 127\",\n        \"username\": \"Test_127\"\n    }, {\n        \"id\": 129,\n        \"name\": \"Test 128\",\n        \"username\": \"Test_128\"\n    }, {\n        \"id\": 130,\n        \"name\": \"Test 129\",\n        \"username\": \"Test_129\"\n    }, {\n        \"id\": 131,\n        \"name\": \"Test 130\",\n        \"username\": \"Test_130\"\n    }, {\n        \"id\": 132,\n        \"name\": \"Test 131\",\n        \"username\": \"Test_131\"\n    }, {\n        \"id\": 133,\n        \"name\": \"Test 132\",\n        \"username\": \"Test_132\"\n    }, {\n        \"id\": 134,\n        \"name\": \"Test 133\",\n        \"username\": \"Test_133\"\n    }, {\n        \"id\": 135,\n        \"name\": \"Test 134\",\n        \"username\": \"Test_134\"\n    }, {\n        \"id\": 136,\n        \"name\": \"Test 135\",\n        \"username\": \"Test_135\"\n    }, {\n        \"id\": 137,\n        \"name\": \"Test 136\",\n        \"username\": \"Test_136\"\n    }, {\n        \"id\": 138,\n        \"name\": \"Test 137\",\n        \"username\": \"Test_137\"\n    }, {\n        \"id\": 139,\n        \"name\": \"Test 138\",\n        \"username\": \"Test_138\"\n    }, {\n        \"id\": 140,\n        \"name\": \"Test 139\",\n        \"username\": \"Test_139\"\n    }, {\n        \"id\": 141,\n        \"name\": \"Test 140\",\n        \"username\": \"Test_140\"\n    }, {\n        \"id\": 142,\n        \"name\": \"Test 141\",\n        \"username\": \"Test_141\"\n    }, {\n        \"id\": 143,\n        \"name\": \"Test 142\",\n        \"username\": \"Test_142\"\n    }, {\n        \"id\": 144,\n        \"name\": \"Test 143\",\n        \"username\": \"Test_143\"\n    }, {\n        \"id\": 145,\n        \"name\": \"Test 144\",\n        \"username\": \"Test_144\"\n    }, {\n        \"id\": 146,\n        \"name\": \"Test 145\",\n        \"username\": \"Test_145\"\n    }, {\n        \"id\": 147,\n        \"name\": \"Test 146\",\n        \"username\": \"Test_146\"\n    }, {\n        \"id\": 148,\n        \"name\": \"Test 147\",\n        \"username\": \"Test_147\"\n    }, {\n        \"id\": 149,\n        \"name\": \"Test 148\",\n        \"username\": \"Test_148\"\n    }, {\n        \"id\": 150,\n        \"name\": \"Test 149\",\n        \"username\": \"Test_149\"\n    }, {\n        \"id\": 151,\n        \"name\": \"Test 150\",\n        \"username\": \"Test_150\"\n    }, {\n        \"id\": 152,\n        \"name\": \"Test 151\",\n        \"username\": \"Test_151\"\n    }, {\n        \"id\": 153,\n        \"name\": \"Test 152\",\n        \"username\": \"Test_152\"\n    }, {\n        \"id\": 154,\n        \"name\": \"Test 153\",\n        \"username\": \"Test_153\"\n    }, {\n        \"id\": 155,\n        \"name\": \"Test 154\",\n        \"username\": \"Test_154\"\n    }, {\n        \"id\": 156,\n        \"name\": \"Test 155\",\n        \"username\": \"Test_155\"\n    }, {\n        \"id\": 157,\n        \"name\": \"Test 156\",\n        \"username\": \"Test_156\"\n    }, {\n        \"id\": 158,\n        \"name\": \"Test 157\",\n        \"username\": \"Test_157\"\n    }, {\n        \"id\": 159,\n        \"name\": \"Test 158\",\n        \"username\": \"Test_158\"\n    }, {\n        \"id\": 160,\n        \"name\": \"Test 159\",\n        \"username\": \"Test_159\"\n    }, {\n        \"id\": 161,\n        \"name\": \"Test 160\",\n        \"username\": \"Test_160\"\n    }, {\n        \"id\": 162,\n        \"name\": \"Test 161\",\n        \"username\": \"Test_161\"\n    }, {\n        \"id\": 163,\n        \"name\": \"Test 162\",\n        \"username\": \"Test_162\"\n    }, {\n        \"id\": 164,\n        \"name\": \"Test 163\",\n        \"username\": \"Test_163\"\n    }, {\n        \"id\": 165,\n        \"name\": \"Test 164\",\n        \"username\": \"Test_164\"\n    }, {\n        \"id\": 166,\n        \"name\": \"Test 165\",\n        \"username\": \"Test_165\"\n    }, {\n        \"id\": 167,\n        \"name\": \"Test 166\",\n        \"username\": \"Test_166\"\n    }, {\n        \"id\": 168,\n        \"name\": \"Test 167\",\n        \"username\": \"Test_167\"\n    }, {\n        \"id\": 169,\n        \"name\": \"Test 168\",\n        \"username\": \"Test_168\"\n    }, {\n        \"id\": 170,\n        \"name\": \"Test 169\",\n        \"username\": \"Test_169\"\n    }, {\n        \"id\": 171,\n        \"name\": \"Test 170\",\n        \"username\": \"Test_170\"\n    }, {\n        \"id\": 172,\n        \"name\": \"Test 171\",\n        \"username\": \"Test_171\"\n    }, {\n        \"id\": 173,\n        \"name\": \"Test 172\",\n        \"username\": \"Test_172\"\n    }, {\n        \"id\": 174,\n        \"name\": \"Test 173\",\n        \"username\": \"Test_173\"\n    }, {\n        \"id\": 175,\n        \"name\": \"Test 174\",\n        \"username\": \"Test_174\"\n    }, {\n        \"id\": 176,\n        \"name\": \"Test 175\",\n        \"username\": \"Test_175\"\n    }, {\n        \"id\": 177,\n        \"name\": \"Test 176\",\n        \"username\": \"Test_176\"\n    }, {\n        \"id\": 178,\n        \"name\": \"Test 177\",\n        \"username\": \"Test_177\"\n    }, {\n        \"id\": 179,\n        \"name\": \"Test 178\",\n        \"username\": \"Test_178\"\n    }, {\n        \"id\": 180,\n        \"name\": \"Test 179\",\n        \"username\": \"Test_179\"\n    }, {\n        \"id\": 181,\n        \"name\": \"Test 180\",\n        \"username\": \"Test_180\"\n    }, {\n        \"id\": 182,\n        \"name\": \"Test 181\",\n        \"username\": \"Test_181\"\n    }, {\n        \"id\": 183,\n        \"name\": \"Test 182\",\n        \"username\": \"Test_182\"\n    }, {\n        \"id\": 184,\n        \"name\": \"Test 183\",\n        \"username\": \"Test_183\"\n    }, {\n        \"id\": 185,\n        \"name\": \"Test 184\",\n        \"username\": \"Test_184\"\n    }, {\n        \"id\": 186,\n        \"name\": \"Test 185\",\n        \"username\": \"Test_185\"\n    }, {\n        \"id\": 187,\n        \"name\": \"Test 186\",\n        \"username\": \"Test_186\"\n    }, {\n        \"id\": 188,\n        \"name\": \"Test 187\",\n        \"username\": \"Test_187\"\n    }, {\n        \"id\": 189,\n        \"name\": \"Test 188\",\n        \"username\": \"Test_188\"\n    }, {\n        \"id\": 190,\n        \"name\": \"Test 189\",\n        \"username\": \"Test_189\"\n    }]\n}\n"
  },
  {
    "path": "spec/fixtures/members_2.json",
    "content": "{\n    \"members\": [{\n        \"id\": 1,\n        \"name\": \"Test 0\",\n        \"username\": \"Test_0\"\n    }, {\n        \"id\": 2,\n        \"name\": \"Test 1\",\n        \"username\": \"Test_1\"\n    }, {\n        \"id\": 3,\n        \"name\": \"Test 2\",\n        \"username\": \"Test_2\"\n    }, {\n        \"id\": 4,\n        \"name\": \"Test 3\",\n        \"username\": \"Test_3\"\n    }, {\n        \"id\": 5,\n        \"name\": \"Test 4\",\n        \"username\": \"Test_4\"\n    }, {\n        \"id\": 6,\n        \"name\": \"Test 5\",\n        \"username\": \"Test_5\"\n    }, {\n        \"id\": 7,\n        \"name\": \"Test 6\",\n        \"username\": \"Test_6\"\n    }, {\n        \"id\": 8,\n        \"name\": \"Test 7\",\n        \"username\": \"Test_7\"\n    }, {\n        \"id\": 9,\n        \"name\": \"Test 8\",\n        \"username\": \"Test_8\"\n    }, {\n        \"id\": 10,\n        \"name\": \"Test 9\",\n        \"username\": \"Test_9\"\n    }, {\n        \"id\": 11,\n        \"name\": \"Test 10\",\n        \"username\": \"Test_10\"\n    }, {\n        \"id\": 12,\n        \"name\": \"Test 11\",\n        \"username\": \"Test_11\"\n    }, {\n        \"id\": 13,\n        \"name\": \"Test 12\",\n        \"username\": \"Test_12\"\n    }, {\n        \"id\": 14,\n        \"name\": \"Test 13\",\n        \"username\": \"Test_13\"\n    }, {\n        \"id\": 15,\n        \"name\": \"Test 14\",\n        \"username\": \"Test_14\"\n    }, {\n        \"id\": 16,\n        \"name\": \"Test 15\",\n        \"username\": \"Test_15\"\n    }, {\n        \"id\": 17,\n        \"name\": \"Test 16\",\n        \"username\": \"Test_16\"\n    }, {\n        \"id\": 18,\n        \"name\": \"Test 17\",\n        \"username\": \"Test_17\"\n    }, {\n        \"id\": 19,\n        \"name\": \"Test 18\",\n        \"username\": \"Test_18\"\n    }, {\n        \"id\": 20,\n        \"name\": \"Test 19\",\n        \"username\": \"Test_19\"\n    }, {\n        \"id\": 21,\n        \"name\": \"Test 20\",\n        \"username\": \"Test_20\"\n    }, {\n        \"id\": 22,\n        \"name\": \"Test 21\",\n        \"username\": \"Test_21\"\n    }, {\n        \"id\": 23,\n        \"name\": \"Test 22\",\n        \"username\": \"Test_22\"\n    }, {\n        \"id\": 24,\n        \"name\": \"Test 23\",\n        \"username\": \"Test_23\"\n    }, {\n        \"id\": 25,\n        \"name\": \"Test 24\",\n        \"username\": \"Test_24\"\n    }, {\n        \"id\": 26,\n        \"name\": \"Test 25\",\n        \"username\": \"Test_25\"\n    }, {\n        \"id\": 27,\n        \"name\": \"Test 26\",\n        \"username\": \"Test_26\"\n    }, {\n        \"id\": 28,\n        \"name\": \"Test 27\",\n        \"username\": \"Test_27\"\n    }, {\n        \"id\": 29,\n        \"name\": \"Test 28\",\n        \"username\": \"Test_28\"\n    }, {\n        \"id\": 30,\n        \"name\": \"Test 29\",\n        \"username\": \"Test_29\"\n    }, {\n        \"id\": 31,\n        \"name\": \"Test 30\",\n        \"username\": \"Test_30\"\n    }, {\n        \"id\": 32,\n        \"name\": \"Test 31\",\n        \"username\": \"Test_31\"\n    }, {\n        \"id\": 33,\n        \"name\": \"Test 32\",\n        \"username\": \"Test_32\"\n    }, {\n        \"id\": 34,\n        \"name\": \"Test 33\",\n        \"username\": \"Test_33\"\n    }, {\n        \"id\": 35,\n        \"name\": \"Test 34\",\n        \"username\": \"Test_34\"\n    }, {\n        \"id\": 36,\n        \"name\": \"Test 35\",\n        \"username\": \"Test_35\"\n    }, {\n        \"id\": 37,\n        \"name\": \"Test 36\",\n        \"username\": \"Test_36\"\n    }, {\n        \"id\": 38,\n        \"name\": \"Test 37\",\n        \"username\": \"Test_37\"\n    }, {\n        \"id\": 39,\n        \"name\": \"Test 38\",\n        \"username\": \"Test_38\"\n    }, {\n        \"id\": 40,\n        \"name\": \"Test 39\",\n        \"username\": \"Test_39\"\n    }, {\n        \"id\": 41,\n        \"name\": \"Test 40\",\n        \"username\": \"Test_40\"\n    }, {\n        \"id\": 42,\n        \"name\": \"Test 41\",\n        \"username\": \"Test_41\"\n    }, {\n        \"id\": 43,\n        \"name\": \"Test 42\",\n        \"username\": \"Test_42\"\n    }, {\n        \"id\": 44,\n        \"name\": \"Test 43\",\n        \"username\": \"Test_43\"\n    }, {\n        \"id\": 45,\n        \"name\": \"Test 44\",\n        \"username\": \"Test_44\"\n    }, {\n        \"id\": 46,\n        \"name\": \"Test 45\",\n        \"username\": \"Test_45\"\n    }, {\n        \"id\": 47,\n        \"name\": \"Test 46\",\n        \"username\": \"Test_46\"\n    }, {\n        \"id\": 48,\n        \"name\": \"Test 47\",\n        \"username\": \"Test_47\"\n    }, {\n        \"id\": 49,\n        \"name\": \"Test 48\",\n        \"username\": \"Test_48\"\n    }, {\n        \"id\": 50,\n        \"name\": \"Test 49\",\n        \"username\": \"Test_49\"\n    }, {\n        \"id\": 51,\n        \"name\": \"Test 50\",\n        \"username\": \"Test_50\"\n    }, {\n        \"id\": 52,\n        \"name\": \"Test 51\",\n        \"username\": \"Test_51\"\n    }, {\n        \"id\": 53,\n        \"name\": \"Test 52\",\n        \"username\": \"Test_52\"\n    }, {\n        \"id\": 54,\n        \"name\": \"Test 53\",\n        \"username\": \"Test_53\"\n    }, {\n        \"id\": 55,\n        \"name\": \"Test 54\",\n        \"username\": \"Test_54\"\n    }, {\n        \"id\": 56,\n        \"name\": \"Test 55\",\n        \"username\": \"Test_55\"\n    }, {\n        \"id\": 57,\n        \"name\": \"Test 56\",\n        \"username\": \"Test_56\"\n    }, {\n        \"id\": 58,\n        \"name\": \"Test 57\",\n        \"username\": \"Test_57\"\n    }, {\n        \"id\": 59,\n        \"name\": \"Test 58\",\n        \"username\": \"Test_58\"\n    }, {\n        \"id\": 60,\n        \"name\": \"Test 59\",\n        \"username\": \"Test_59\"\n    }, {\n        \"id\": 61,\n        \"name\": \"Test 60\",\n        \"username\": \"Test_60\"\n    }, {\n        \"id\": 62,\n        \"name\": \"Test 61\",\n        \"username\": \"Test_61\"\n    }, {\n        \"id\": 63,\n        \"name\": \"Test 62\",\n        \"username\": \"Test_62\"\n    }, {\n        \"id\": 64,\n        \"name\": \"Test 63\",\n        \"username\": \"Test_63\"\n    }, {\n        \"id\": 65,\n        \"name\": \"Test 64\",\n        \"username\": \"Test_64\"\n    }, {\n        \"id\": 66,\n        \"name\": \"Test 65\",\n        \"username\": \"Test_65\"\n    }, {\n        \"id\": 67,\n        \"name\": \"Test 66\",\n        \"username\": \"Test_66\"\n    }, {\n        \"id\": 68,\n        \"name\": \"Test 67\",\n        \"username\": \"Test_67\"\n    }, {\n        \"id\": 69,\n        \"name\": \"Test 68\",\n        \"username\": \"Test_68\"\n    }, {\n        \"id\": 70,\n        \"name\": \"Test 69\",\n        \"username\": \"Test_69\"\n    }, {\n        \"id\": 71,\n        \"name\": \"Test 70\",\n        \"username\": \"Test_70\"\n    }, {\n        \"id\": 72,\n        \"name\": \"Test 71\",\n        \"username\": \"Test_71\"\n    }, {\n        \"id\": 73,\n        \"name\": \"Test 72\",\n        \"username\": \"Test_72\"\n    }, {\n        \"id\": 74,\n        \"name\": \"Test 73\",\n        \"username\": \"Test_73\"\n    }, {\n        \"id\": 75,\n        \"name\": \"Test 74\",\n        \"username\": \"Test_74\"\n    }, {\n        \"id\": 76,\n        \"name\": \"Test 75\",\n        \"username\": \"Test_75\"\n    }, {\n        \"id\": 77,\n        \"name\": \"Test 76\",\n        \"username\": \"Test_76\"\n    }, {\n        \"id\": 78,\n        \"name\": \"Test 77\",\n        \"username\": \"Test_77\"\n    }, {\n        \"id\": 79,\n        \"name\": \"Test 78\",\n        \"username\": \"Test_78\"\n    }, {\n        \"id\": 80,\n        \"name\": \"Test 79\",\n        \"username\": \"Test_79\"\n    }, {\n        \"id\": 81,\n        \"name\": \"Test 80\",\n        \"username\": \"Test_80\"\n    }, {\n        \"id\": 82,\n        \"name\": \"Test 81\",\n        \"username\": \"Test_81\"\n    }, {\n        \"id\": 83,\n        \"name\": \"Test 82\",\n        \"username\": \"Test_82\"\n    }, {\n        \"id\": 84,\n        \"name\": \"Test 83\",\n        \"username\": \"Test_83\"\n    }, {\n        \"id\": 85,\n        \"name\": \"Test 84\",\n        \"username\": \"Test_84\"\n    }, {\n        \"id\": 86,\n        \"name\": \"Test 85\",\n        \"username\": \"Test_85\"\n    }, {\n        \"id\": 87,\n        \"name\": \"Test 86\",\n        \"username\": \"Test_86\"\n    }, {\n        \"id\": 88,\n        \"name\": \"Test 87\",\n        \"username\": \"Test_87\"\n    }, {\n        \"id\": 89,\n        \"name\": \"Test 88\",\n        \"username\": \"Test_88\"\n    }, {\n        \"id\": 90,\n        \"name\": \"Test 89\",\n        \"username\": \"Test_89\"\n    }, {\n        \"id\": 91,\n        \"name\": \"Test 90\",\n        \"username\": \"Test_90\"\n    }, {\n        \"id\": 92,\n        \"name\": \"Test 91\",\n        \"username\": \"Test_91\"\n    }, {\n        \"id\": 93,\n        \"name\": \"Test 92\",\n        \"username\": \"Test_92\"\n    }, {\n        \"id\": 94,\n        \"name\": \"Test 93\",\n        \"username\": \"Test_93\"\n    }, {\n        \"id\": 95,\n        \"name\": \"Test 94\",\n        \"username\": \"Test_94\"\n    }, {\n        \"id\": 96,\n        \"name\": \"Test 95\",\n        \"username\": \"Test_95\"\n    }, {\n        \"id\": 97,\n        \"name\": \"Test 96\",\n        \"username\": \"Test_96\"\n    }, {\n        \"id\": 98,\n        \"name\": \"Test 97\",\n        \"username\": \"Test_97\"\n    }, {\n        \"id\": 99,\n        \"name\": \"Test 98\",\n        \"username\": \"Test_98\"\n    }, {\n        \"id\": 100,\n        \"name\": \"Test 99\",\n        \"username\": \"Test_99\"\n    }],\n    \"owners\": [{\n        \"id\": 101,\n        \"name\": \"Onwer 0\",\n        \"username\": \"Owner_0\"\n    }, {\n        \"id\": 102,\n        \"name\": \"Onwer 1\",\n        \"username\": \"Owner_1\"\n    }, {\n        \"id\": 103,\n        \"name\": \"Onwer 2\",\n        \"username\": \"Owner_2\"\n    }, {\n        \"id\": 104,\n        \"name\": \"Onwer 3\",\n        \"username\": \"Owner_3\"\n    }, {\n        \"id\": 105,\n        \"name\": \"Onwer 4\",\n        \"username\": \"Owner_4\"\n    }, {\n        \"id\": 106,\n        \"name\": \"Onwer 5\",\n        \"username\": \"Owner_5\"\n    }, {\n        \"id\": 107,\n        \"name\": \"Onwer 6\",\n        \"username\": \"Owner_6\"\n    }],\n    \"meta\": {\n        \"total\": 107,\n        \"limit\": 0,\n        \"offset\": 0\n    }\n}\n"
  },
  {
    "path": "spec/fixtures/new.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": true,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": 0,\n    \"topics\": [\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #1\",\n        \"fancy_title\": \"Test topic #1\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-07T03:02:03.615-05:00\",\n        \"last_posted_at\": \"2013-11-07T09:42:57.094-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-07T09:42:57.094-05:00\",\n        \"unseen\": true,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 20,\n        \"like_count\": 1,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 2,\n        \"title\": \"Test topic #2\",\n        \"fancy_title\": \"Test topic #2\",\n        \"slug\": \"test-topic-2\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-06T12:21:37.811-05:00\",\n        \"last_posted_at\": \"2013-11-07T05:41:55.171-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-07T05:41:55.171-05:00\",\n        \"unseen\": true,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 50,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"cowboytomash\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 3,\n        \"title\": \"Test topic #3\",\n        \"fancy_title\": \"Test topic #3\",\n        \"slug\": \"test-topic-3\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-07T02:43:19.243-05:00\",\n        \"last_posted_at\": \"2013-11-07T02:43:19.389-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-07T02:43:19.389-05:00\",\n        \"unseen\": true,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 14,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"akshar\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/notification_success.json",
    "content": "{\n  \"success\": \"OK\"\n}\n"
  },
  {
    "path": "spec/fixtures/notifications.json",
    "content": "[\n  {\n    \"notification_type\": 9,\n    \"read\": false,\n    \"created_at\": \"2014-04-02T11:58:00.670-04:00\",\n    \"post_number\": 2,\n    \"topic_id\": 14404,\n    \"slug\": \"json-feed-for-notifications\",\n    \"data\": {\n      \"topic_title\": \".json feed for notifications\",\n      \"original_post_id\": 47600,\n      \"original_username\": \"riking\",\n      \"display_username\": \"riking\"\n    }\n  }\n]\n"
  },
  {
    "path": "spec/fixtures/polls_toggle_status.json",
    "content": "{\n  \"poll\": {\n    \"name\": \"poll\",\n    \"type\": \"regular\",\n    \"status\": \"closed\",\n    \"public\": true,\n    \"results\": \"always\",\n    \"options\": [\n      {\n        \"id\": \"8b4736b1ae3dfb5a28088530f036f9e5\",\n        \"html\": \"I will attend the Apr 25, 2019 Launchpad meeting.\",\n        \"votes\": 1\n      },\n      {\n        \"id\": \"e09884fbf9b72d83e804050078847643\",\n        \"html\": \"I want to lead the meeting.\",\n        \"votes\": 0\n      },\n      {\n        \"id\": \"e539a9df8700d0d05c69356a07b768cf\",\n        \"html\": \"I’ll not attend this meeting.\",\n        \"votes\": 2\n      }\n    ],\n    \"voters\": 3,\n    \"preloaded_voters\": {\n      \"8b4736b1ae3dfb5a28088530f036f9e5\": [\n        {\n          \"id\": 17,\n          \"username\": \"Kim_Miller\",\n          \"name\": \"Kim Miller\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/user_one/{size}/3220_2.png\",\n          \"title\": \"New Owners Council\"\n        }\n      ],\n      \"e539a9df8700d0d05c69356a07b768cf\": [\n        {\n          \"id\": 356,\n          \"username\": \"David_Ashby\",\n          \"name\": \"David Ashby\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/user_two/{size}/2403_2.png\",\n          \"title\": \"Team Agape\"\n        },\n        {\n          \"id\": 72,\n          \"username\": \"David_Kirk\",\n          \"name\": \"David Kirk\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/user_three/{size}/236_2.png\",\n          \"title\": \"Foundry\"\n        }\n      ]\n    }\n  }\n}"
  },
  {
    "path": "spec/fixtures/polls_vote.json",
    "content": "{\n  \"poll\": {\n    \"name\": \"poll\",\n    \"type\": \"regular\",\n    \"status\": \"open\",\n    \"public\": true,\n    \"results\": \"always\",\n    \"options\": [\n      {\n        \"id\": \"8b4736b1ae3dfb5a28088530f036f9e5\",\n        \"html\": \"I will attend the Apr 25, 2019 Launchpad meeting.\",\n        \"votes\": 2\n      },\n      {\n        \"id\": \"e09884fbf9b72d83e804050078847643\",\n        \"html\": \"I want to lead the meeting.\",\n        \"votes\": 0\n      },\n      {\n        \"id\": \"e539a9df8700d0d05c69356a07b768cf\",\n        \"html\": \"I’ll not attend this meeting.\",\n        \"votes\": 2\n      }\n    ],\n    \"voters\": 4,\n    \"preloaded_voters\": {\n      \"8b4736b1ae3dfb5a28088530f036f9e5\": [\n        {\n          \"id\": 17,\n          \"username\": \"Kim_Miller\",\n          \"name\": \"Kim Miller\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/kim_miller/{size}/3220_2.png\",\n          \"title\": \"New Owners Council\"\n        },\n        {\n          \"id\": 150,\n          \"username\": \"KM_Admin\",\n          \"name\": \"Kim Miller\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/km_admin/{size}/1232_2.png\",\n          \"title\": \"Admin\"\n        }\n      ],\n      \"e539a9df8700d0d05c69356a07b768cf\": [\n        {\n          \"id\": 356,\n          \"username\": \"David_Ashby\",\n          \"name\": \"David Ashby\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/david_ashby/{size}/2403_2.png\",\n          \"title\": \"Team Agape\"\n        },\n        {\n          \"id\": 72,\n          \"username\": \"David_Kirk\",\n          \"name\": \"David Kirk\",\n          \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/david_kirk/{size}/236_2.png\",\n          \"title\": \"Foundry\"\n        }\n      ]\n    }\n  },\n  \"vote\": [\n    \"8b4736b1ae3dfb5a28088530f036f9e5\"\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/polls_voters.json",
    "content": "{\n  \"voters\": {\n    \"e539a9df8700d0d05c69356a07b768cf\": [\n      {\n        \"id\": 356,\n        \"username\": \"David_Ashby\",\n        \"name\": \"David Ashby\",\n        \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/david_ashby/{size}/2403_2.png\",\n        \"title\": \"Team Agape\"\n      },\n      {\n        \"id\": 72,\n        \"username\": \"David_Kirk\",\n        \"name\": \"David Kirk\",\n        \"avatar_template\": \"/user_avatar/discourse.gomomentum.org/david_kirk/{size}/236_2.png\",\n        \"title\": \"Foundry\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/post.json",
    "content": "{\n  \"id\": 11,\n  \"name\": \"system\",\n  \"username\": \"system\",\n  \"avatar_template\": \"/user_avatar/localhost/system/{size}/1.png\",\n  \"uploaded_avatar_id\": 1,\n  \"created_at\": \"2014-10-31T04:37:32.553Z\",\n  \"cooked\": \"<p>The first paragraph of this pinned topic will be visible as a welcome message to all new visitors on your homepage. It's important!</p>\\n\\n<p><strong>Edit this</strong> into a brief description of your community:</p>\\n\\n<ul><li>Who is it for?</li><li>What can they find here?</li><li>Why should they come here?</li><li>Where can they read more (links, resources, etc)?</li></ul>\\n\\n<p>You may want to close this topic via the admin wrench (at the upper right and bottom), so that replies don't pile up on an announcement.</p>\",\n  \"post_number\": 1,\n  \"post_type\": 1,\n  \"updated_at\": \"2014-10-31T04:37:32.553Z\",\n  \"reply_count\": 0,\n  \"reply_to_post_number\": null,\n  \"quote_count\": 0,\n  \"avg_time\": null,\n  \"incoming_link_count\": 0,\n  \"reads\": 1,\n  \"score\": 0.2,\n  \"yours\": false,\n  \"topic_id\": 8,\n  \"topic_slug\": \"welcome-to-discourse\",\n  \"topic_auto_close_at\": null,\n  \"display_username\": \"system\",\n  \"primary_group_name\": null,\n  \"version\": 1,\n  \"can_edit\": true,\n  \"can_delete\": false,\n  \"can_recover\": true,\n  \"user_title\": null,\n  \"raw\": \"The first paragraph of this pinned topic will be visible as a welcome message to all new visitors on your homepage. It's important!\\n\\n**Edit this** into a brief description of your community:\\n\\n- Who is it for?\\n- What can they find here?\\n- Why should they come here?\\n- Where can they read more (links, resources, etc)?\\n\\nYou may want to close this topic via the admin wrench (at the upper right and bottom), so that replies don't pile up on an announcement.\",\n  \"actions_summary\": [\n    {\n      \"id\": 2,\n      \"count\": 0,\n      \"hidden\": false,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    },\n    {\n      \"id\": 3,\n      \"count\": 0,\n      \"hidden\": false,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    },\n    {\n      \"id\": 4,\n      \"count\": 0,\n      \"hidden\": false,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    },\n    {\n      \"id\": 5,\n      \"count\": 0,\n      \"hidden\": true,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    },\n    {\n      \"id\": 6,\n      \"count\": 0,\n      \"hidden\": false,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    },\n    {\n      \"id\": 7,\n      \"count\": 0,\n      \"hidden\": false,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    },\n    {\n      \"id\": 8,\n      \"count\": 0,\n      \"hidden\": false,\n      \"can_act\": true,\n      \"can_defer_flags\": false\n    }\n  ],\n  \"moderator\": true,\n  \"admin\": true,\n  \"staff\": true,\n  \"user_id\": -1,\n  \"hidden\": false,\n  \"hidden_reason_id\": null,\n  \"trust_level\": 4,\n  \"deleted_at\": null,\n  \"user_deleted\": false,\n  \"edit_reason\": null,\n  \"can_view_edit_history\": true,\n  \"wiki\": false\n}\n"
  },
  {
    "path": "spec/fixtures/post_action_users.json",
    "content": "{\n  \"post_action_users\":  [\n    {\n      \"id\": 1286,\n      \"username\": \"john_smith\",\n      \"avatar_template\": \"/user_avatar/domain/john_smith/size/1609_1.png\",\n      \"post_url\": null,\n      \"username_lower\": \"john_smith\"\n    },\n    {\n      \"id\": 1,\n      \"username\": \"jane_smith\",\n      \"avatar_template\": \"/user_avatar/domain/jane_smith/size/17_1.png\",\n      \"post_url\": null,\n      \"username_lower\": \"jane_smith\"\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/posts_before.json",
    "content": "{\n  \"latest_posts\": [\n    {\n      \"id\": 14,\n      \"name\": null,\n      \"username\": \"johndoe\",\n      \"avatar_template\": \"/letter_avatar_proxy/v3/letter/p/df705f/{size}.png\",\n      \"created_at\": \"2020-01-25T00:58:35.475Z\",\n      \"cooked\": \"<p>hi there.  hi there.  hi there.</p>\",\n      \"post_number\": 1,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-25T00:58:35.475Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 11,\n      \"topic_slug\": \"this-is-an-uncategorized-title\",\n      \"topic_title\": \"This is an uncategorized title\",\n      \"topic_html_title\": \"This is an uncategorized title\",\n      \"category_id\": 1,\n      \"display_username\": null,\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"hi there.  hi there.  hi there.\",\n      \"actions_summary\": [],\n      \"moderator\": false,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": 1,\n      \"hidden\": false,\n      \"trust_level\": 1,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    },\n    {\n      \"id\": 10,\n      \"name\": \"system\",\n      \"username\": \"system\",\n      \"avatar_template\": \"/user_avatar/localhost/system/{size}/1_2.png\",\n      \"created_at\": \"2020-01-24T23:26:09.014Z\",\n      \"cooked\": \"<p>The first paragraph of this pinned topic will be visible as a welcome message to all new visitors on your homepage. It’s important!</p>\\n<p><strong>Edit this</strong> into a brief description of your community:</p>\\n<ul>\\n<li>Who is it for?</li>\\n<li>What can they find here?</li>\\n<li>Why should they come here?</li>\\n<li>Where can they read more (links, resources, etc)?</li>\\n</ul>\\n<p><img src=\\\"//localhost/images/welcome/discourse-edit-post-animated.gif\\\" width=\\\"508\\\" height=\\\"106\\\"></p>\\n<p>You may want to close this topic via the admin <img src=\\\"//localhost/images/emoji/twitter/wrench.png?v=6\\\" title=\\\":wrench:\\\" class=\\\"emoji\\\" alt=\\\":wrench:\\\"> (at the upper right and bottom), so that replies don’t pile up on an announcement.</p>\",\n      \"post_number\": 1,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-24T23:26:09.014Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 7,\n      \"topic_slug\": \"welcome-to-discourse\",\n      \"topic_title\": \"Welcome to Discourse\",\n      \"topic_html_title\": \"Welcome to Discourse\",\n      \"category_id\": 1,\n      \"display_username\": \"system\",\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"\\nThe first paragraph of this pinned topic will be visible as a welcome message to all new visitors on your homepage. It's important!\\n\\n**Edit this** into a brief description of your community:\\n\\n- Who is it for?\\n- What can they find here?\\n- Why should they come here?\\n- Where can they read more (links, resources, etc)?\\n\\n<img src=\\\"/images/welcome/discourse-edit-post-animated.gif\\\" width=\\\"508\\\" height=\\\"106\\\">\\n\\nYou may want to close this topic via the admin :wrench: (at the upper right and bottom), so that replies don't pile up on an announcement.\",\n      \"actions_summary\": [],\n      \"moderator\": true,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": -1,\n      \"hidden\": false,\n      \"trust_level\": 4,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    },\n    {\n      \"id\": 2,\n      \"name\": \"system\",\n      \"username\": \"system\",\n      \"avatar_template\": \"/user_avatar/localhost/system/{size}/1_2.png\",\n      \"created_at\": \"2020-01-24T23:26:05.551Z\",\n      \"cooked\": \"<p>Discussion about this site, its organization, how it works, and how we can improve it.</p>\",\n      \"post_number\": 1,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-24T23:26:05.551Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 2,\n      \"topic_slug\": \"about-the-site-feedback-category\",\n      \"topic_title\": \"About the Site Feedback category\",\n      \"topic_html_title\": \"About the Site Feedback category\",\n      \"category_id\": 3,\n      \"display_username\": \"system\",\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"Discussion about this site, its organization, how it works, and how we can improve it.\",\n      \"actions_summary\": [],\n      \"moderator\": true,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": -1,\n      \"hidden\": false,\n      \"trust_level\": 4,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/posts_latest.json",
    "content": "{\n  \"latest_posts\": [\n    {\n      \"id\": 15,\n      \"name\": null,\n      \"username\": \"johndoe\",\n      \"avatar_template\": \"/letter_avatar_proxy/v3/letter/p/df705f/{size}.png\",\n      \"created_at\": \"2020-01-25T02:10:07.971Z\",\n      \"cooked\": \"<p>holla bullhonky.</p>\",\n      \"post_number\": 2,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-25T02:10:07.971Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 11,\n      \"topic_slug\": \"this-is-an-uncategorized-title\",\n      \"topic_title\": \"This is an uncategorized title\",\n      \"topic_html_title\": \"This is an uncategorized title\",\n      \"category_id\": 1,\n      \"display_username\": null,\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"holla bullhonky.\",\n      \"actions_summary\": [],\n      \"moderator\": false,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": 1,\n      \"hidden\": false,\n      \"trust_level\": 1,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    },\n    {\n      \"id\": 14,\n      \"name\": null,\n      \"username\": \"johndoe\",\n      \"avatar_template\": \"/letter_avatar_proxy/v3/letter/p/df705f/{size}.png\",\n      \"created_at\": \"2020-01-25T00:58:35.475Z\",\n      \"cooked\": \"<p>hi there.  hi there.  hi there.</p>\",\n      \"post_number\": 1,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-25T00:58:35.475Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 11,\n      \"topic_slug\": \"this-is-an-uncategorized-title\",\n      \"topic_title\": \"This is an uncategorized title\",\n      \"topic_html_title\": \"This is an uncategorized title\",\n      \"category_id\": 1,\n      \"display_username\": null,\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"hi there.  hi there.  hi there.\",\n      \"actions_summary\": [],\n      \"moderator\": false,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": 1,\n      \"hidden\": false,\n      \"trust_level\": 1,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    },\n    {\n      \"id\": 10,\n      \"name\": \"system\",\n      \"username\": \"system\",\n      \"avatar_template\": \"/user_avatar/localhost/system/{size}/1_2.png\",\n      \"created_at\": \"2020-01-24T23:26:09.014Z\",\n      \"cooked\": \"<p>The first paragraph of this pinned topic will be visible as a welcome message to all new visitors on your homepage. It’s important!</p>\\n<p><strong>Edit this</strong> into a brief description of your community:</p>\\n<ul>\\n<li>Who is it for?</li>\\n<li>What can they find here?</li>\\n<li>Why should they come here?</li>\\n<li>Where can they read more (links, resources, etc)?</li>\\n</ul>\\n<p><img src=\\\"//localhost/images/welcome/discourse-edit-post-animated.gif\\\" width=\\\"508\\\" height=\\\"106\\\"></p>\\n<p>You may want to close this topic via the admin <img src=\\\"//localhost/images/emoji/twitter/wrench.png?v=6\\\" title=\\\":wrench:\\\" class=\\\"emoji\\\" alt=\\\":wrench:\\\"> (at the upper right and bottom), so that replies don’t pile up on an announcement.</p>\",\n      \"post_number\": 1,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-24T23:26:09.014Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 7,\n      \"topic_slug\": \"welcome-to-discourse\",\n      \"topic_title\": \"Welcome to Discourse\",\n      \"topic_html_title\": \"Welcome to Discourse\",\n      \"category_id\": 1,\n      \"display_username\": \"system\",\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"\\nThe first paragraph of this pinned topic will be visible as a welcome message to all new visitors on your homepage. It's important!\\n\\n**Edit this** into a brief description of your community:\\n\\n- Who is it for?\\n- What can they find here?\\n- Why should they come here?\\n- Where can they read more (links, resources, etc)?\\n\\n<img src=\\\"/images/welcome/discourse-edit-post-animated.gif\\\" width=\\\"508\\\" height=\\\"106\\\">\\n\\nYou may want to close this topic via the admin :wrench: (at the upper right and bottom), so that replies don't pile up on an announcement.\",\n      \"actions_summary\": [],\n      \"moderator\": true,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": -1,\n      \"hidden\": false,\n      \"trust_level\": 4,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    },\n    {\n      \"id\": 2,\n      \"name\": \"system\",\n      \"username\": \"system\",\n      \"avatar_template\": \"/user_avatar/localhost/system/{size}/1_2.png\",\n      \"created_at\": \"2020-01-24T23:26:05.551Z\",\n      \"cooked\": \"<p>Discussion about this site, its organization, how it works, and how we can improve it.</p>\",\n      \"post_number\": 1,\n      \"post_type\": 1,\n      \"updated_at\": \"2020-01-24T23:26:05.551Z\",\n      \"reply_count\": 0,\n      \"reply_to_post_number\": null,\n      \"quote_count\": 0,\n      \"avg_time\": null,\n      \"incoming_link_count\": 0,\n      \"reads\": 1,\n      \"score\": 0.2,\n      \"yours\": false,\n      \"topic_id\": 2,\n      \"topic_slug\": \"about-the-site-feedback-category\",\n      \"topic_title\": \"About the Site Feedback category\",\n      \"topic_html_title\": \"About the Site Feedback category\",\n      \"category_id\": 3,\n      \"display_username\": \"system\",\n      \"primary_group_name\": null,\n      \"primary_group_flair_url\": null,\n      \"primary_group_flair_bg_color\": null,\n      \"primary_group_flair_color\": null,\n      \"version\": 1,\n      \"can_edit\": false,\n      \"can_delete\": false,\n      \"can_recover\": false,\n      \"can_wiki\": false,\n      \"user_title\": null,\n      \"raw\": \"Discussion about this site, its organization, how it works, and how we can improve it.\",\n      \"actions_summary\": [],\n      \"moderator\": true,\n      \"admin\": true,\n      \"staff\": true,\n      \"user_id\": -1,\n      \"hidden\": false,\n      \"trust_level\": 4,\n      \"deleted_at\": null,\n      \"user_deleted\": false,\n      \"edit_reason\": null,\n      \"can_view_edit_history\": true,\n      \"wiki\": false\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/private_messages.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"uploaded_avatar_id\": null,\n      \"avatar_template\": \"/letter_avatar/test_user/{size}/2.png\"\n    },\n    {\n      \"id\": 2,\n      \"username\": \"batman\",\n      \"uploaded_avatar_id\": 3,\n      \"avatar_template\": \"/user_avatar/localhost/batman/{size}/3.png\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": true,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": 1,\n    \"topics\": [\n      {\n        \"id\": 11,\n        \"title\": \"Welcome to Discourse!\",\n        \"fancy_title\": \"Welcome to Discourse!\",\n        \"slug\": \"welcome-to-discourse\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": \"http://www.discourse.org/images/welcome/progress-bar.png\",\n        \"created_at\": \"2014-09-13T10:27:19.714-04:00\",\n        \"last_posted_at\": \"2014-09-13T10:27:20.140-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2014-09-13T10:27:20.140-04:00\",\n        \"unseen\": false,\n        \"last_read_post_number\": 1,\n        \"unread\": 0,\n        \"new_posts\": 0,\n        \"pinned\": false,\n        \"unpinned\": null,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"notification_level\": 3,\n        \"views\": 1,\n        \"like_count\": 0,\n        \"starred\": false,\n        \"has_summary\": false,\n        \"archetype\": \"private_message\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": null,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ],\n        \"participants\": [\n          {\n            \"extras\": null,\n            \"description\": null,\n            \"user_id\": 2\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/regenerate_api_key.json",
    "content": "{\n    \"api_key\": {\n        \"id\": 10,\n        \"key\": \"d8a80230fd14531c9b4809c7800b1fe78073a3771d3d2a56a5671bace55bc393\",\n        \"user\": null\n    }\n}\n"
  },
  {
    "path": "spec/fixtures/replies.json",
    "content": "{\n    \"user_actions\": [\n        {\n            \"action_type\": 5,\n            \"created_at\": \"2016-06-29T20:27:55.767Z\",\n            \"excerpt\": \"Test Reply #1\",\n            \"avatar_template\": \"/letter_avatar_proxy/v2/letter/s/71c47a/{size}.png\",\n            \"acting_avatar_template\": \"/letter_avatar_proxy/v2/letter/s/71c47a/{size}.png\",\n            \"slug\": \"test-topic\",\n            \"topic_id\": 39,\n            \"target_user_id\": 11,\n            \"target_name\": \"Test User\",\n            \"target_username\": \"testuser\",\n            \"post_number\": 3,\n            \"post_id\": 48,\n            \"reply_to_post_number\": 2,\n            \"username\": \"testuser\",\n            \"name\": \"Test User\",\n            \"user_id\": 11,\n            \"acting_username\": \"testuser\",\n            \"acting_name\": \"Test User\",\n            \"acting_user_id\": 11,\n            \"title\": \"Test Topic\",\n            \"deleted\": false,\n            \"hidden\": false,\n            \"post_type\": 1,\n            \"action_code\": null,\n            \"category_id\": 9,\n            \"closed\": false,\n            \"archived\": false\n        }\n    ]\n}\n"
  },
  {
    "path": "spec/fixtures/replies_and_topics.json",
    "content": "{\n  \"user_actions\": [\n    {\n      \"action_type\": 5,\n      \"created_at\": \"2016-08-10T14:52:49.660Z\",\n      \"excerpt\": \"test excerpt\",\n      \"avatar_template\": \"/user_avatar/localhost/avatar/{size}/59_1.png\",\n      \"acting_avatar_template\": \"/user_avatar/localhost/avatar/{size}/59_1.png\",\n      \"slug\": \"test-topic\",\n      \"topic_id\": 11,\n      \"target_user_id\": 2,\n      \"target_name\": \"Test User\",\n      \"target_username\": \"testuser\",\n      \"post_number\": 2,\n      \"post_id\": 19,\n      \"reply_to_post_number\": null,\n      \"username\": \"testuser\",\n      \"name\": \"Test User\",\n      \"user_id\": 2,\n      \"acting_username\": \"testuser\",\n      \"acting_name\": \"Test User\",\n      \"acting_user_id\": 2,\n      \"title\": \"Test Topic\",\n      \"deleted\": false,\n      \"hidden\": false,\n      \"post_type\": 1,\n      \"action_code\": null,\n      \"category_id\": 1,\n      \"closed\": false,\n      \"archived\": false\n    },\n    {\n      \"action_type\": 4,\n      \"created_at\": \"2016-04-28T17:31:52.527Z\",\n      \"excerpt\": \"test reply\",\n      \"avatar_template\": \"/user_avatar/localhost/pjdavis1/{size}/59_1.png\",\n      \"acting_avatar_template\": \"/user_avatar/localhost/pjdavis1/{size}/59_1.png\",\n      \"slug\": \"test-topic\",\n      \"topic_id\": 11,\n      \"target_user_id\": 2,\n      \"target_name\": \"Test User\",\n      \"target_username\": \"testuser\",\n      \"post_number\": 1,\n      \"post_id\": null,\n      \"username\": \"testuser\",\n      \"name\": \"Test User\",\n      \"user_id\": 2,\n      \"acting_username\": \"testuser\",\n      \"acting_name\": \"Test User\",\n      \"acting_user_id\": 2,\n      \"title\": \"Test Topic\",\n      \"deleted\": false,\n      \"hidden\": null,\n      \"post_type\": null,\n      \"action_code\": null,\n      \"category_id\": 1,\n      \"closed\": false,\n      \"archived\": false\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/retrieve_invite.json",
    "content": "{\n  \"id\": 26,\n  \"invite_key\": \"CGUHjNC4Na\",\n  \"link\": \"http://localhost:3000/invites/CGUHjNC4Na\",\n  \"email\": \"foo@bar.com\",\n  \"emailed\": true,\n  \"custom_message\": null,\n  \"created_at\": \"2021-05-07T20:48:14.278Z\",\n  \"updated_at\": \"2021-05-07T20:48:14.278Z\",\n  \"expires_at\": \"2021-06-06T20:48:14.278Z\",\n  \"expired\": false,\n  \"topics\": [\n    {\n      \"id\": 1,\n      \"title\": \"About the Site Feedback category\",\n      \"fancy_title\": \"About the Site Feedback category\",\n      \"slug\": \"about-the-site-feedback-category\",\n      \"posts_count\": 1\n    }\n  ],\n  \"groups\": [\n    {\n      \"id\": 1,\n      \"automatic\": true,\n      \"name\": \"admins\",\n      \"display_name\": \"admins\",\n      \"user_count\": 1,\n      \"mentionable_level\": 0,\n      \"messageable_level\": 0,\n      \"visibility_level\": 1,\n      \"primary_group\": false,\n      \"title\": null,\n      \"grant_trust_level\": null,\n      \"incoming_email\": null,\n      \"has_messages\": true,\n      \"flair_url\": null,\n      \"flair_bg_color\": null,\n      \"flair_color\": null,\n      \"bio_raw\": \"\",\n      \"bio_cooked\": null,\n      \"bio_excerpt\": null,\n      \"public_admission\": false,\n      \"public_exit\": false,\n      \"allow_membership_requests\": false,\n      \"full_name\": null,\n      \"default_notification_level\": 3,\n      \"membership_request_template\": null,\n      \"members_visibility_level\": 0,\n      \"can_see_members\": true,\n      \"can_admin_group\": true,\n      \"publish_read_state\": false\n    },\n    {\n      \"id\": 2,\n      \"automatic\": true,\n      \"name\": \"moderators\",\n      \"display_name\": \"moderators\",\n      \"user_count\": 1,\n      \"mentionable_level\": 0,\n      \"messageable_level\": 99,\n      \"visibility_level\": 1,\n      \"primary_group\": false,\n      \"title\": null,\n      \"grant_trust_level\": null,\n      \"incoming_email\": null,\n      \"has_messages\": true,\n      \"flair_url\": null,\n      \"flair_bg_color\": null,\n      \"flair_color\": null,\n      \"bio_raw\": null,\n      \"bio_cooked\": null,\n      \"bio_excerpt\": null,\n      \"public_admission\": false,\n      \"public_exit\": false,\n      \"allow_membership_requests\": false,\n      \"full_name\": null,\n      \"default_notification_level\": 2,\n      \"membership_request_template\": null,\n      \"members_visibility_level\": 0,\n      \"can_see_members\": true,\n      \"can_admin_group\": true,\n      \"publish_read_state\": false\n    },\n    {\n      \"id\": 3,\n      \"automatic\": true,\n      \"name\": \"staff\",\n      \"display_name\": \"staff\",\n      \"user_count\": 2,\n      \"mentionable_level\": 0,\n      \"messageable_level\": 0,\n      \"visibility_level\": 1,\n      \"primary_group\": false,\n      \"title\": null,\n      \"grant_trust_level\": null,\n      \"incoming_email\": null,\n      \"has_messages\": false,\n      \"flair_url\": null,\n      \"flair_bg_color\": null,\n      \"flair_color\": null,\n      \"bio_raw\": null,\n      \"bio_cooked\": null,\n      \"bio_excerpt\": null,\n      \"public_admission\": false,\n      \"public_exit\": false,\n      \"allow_membership_requests\": false,\n      \"full_name\": null,\n      \"default_notification_level\": 3,\n      \"membership_request_template\": null,\n      \"members_visibility_level\": 0,\n      \"can_see_members\": true,\n      \"can_admin_group\": true,\n      \"publish_read_state\": false\n    }\n  ]\n}\n"
  },
  {
    "path": "spec/fixtures/search.json",
    "content": "[\n  {\n    \"type\": \"topic\",\n    \"name\": \"Topics\",\n    \"more\": true,\n    \"results\": [\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #1\",\n        \"url\": \"/t/test-topic-1/1\"\n      },\n      {\n        \"id\": 2,\n        \"title\": \"Test topic #2\",\n        \"url\": \"/t/test-topic-2/2\"\n      },\n      {\n        \"id\": 3,\n        \"title\": \"Test topic #3\",\n        \"url\": \"/t/test-topic-3/3\"\n      }\n    ]\n  }\n]\n"
  },
  {
    "path": "spec/fixtures/top.json",
    "content": "{\n  \"topic_list\": {\n    \"can_create_topic\": false,\n    \"more_topics_url\": \"/latest.json?page=1\",\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": null,\n    \"topics\": [\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #1\",\n        \"fancy_title\": \"Test topic #1\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T15:26:25.977-05:00\",\n        \"last_posted_at\": \"2013-02-05T12:14:17.364-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-02-07T13:43:11.191-05:00\",\n        \"unseen\": false,\n        \"pinned\": true,\n        \"excerpt\": \"Welcome! \\n\\nTry is a sandbox, a safe place to play with Discourse and its features.\\n\\nThis site is reset every day (or even more often). Any accounts or posts you create here will not exist tomorrow! \\n\\nFeel free to experim&hellip;\",\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 268,\n        \"like_count\": 14,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #2\",\n        \"fancy_title\": \"Test topic #2\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 2,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-06T13:57:47.984-05:00\",\n        \"last_posted_at\": \"2013-11-06T13:57:48.111-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-06T13:57:48.111-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 1,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      },\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #3\",\n        \"fancy_title\": \"Test topic #3\",\n        \"slug\": \"test-topic-3\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-02-04T21:46:58.194-05:00\",\n        \"last_posted_at\": \"2013-11-06T13:56:27.753-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-06T13:56:27.753-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 82,\n        \"like_count\": 10,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/topic.json",
    "content": "{\n  \"post_stream\": {\n    \"posts\": [\n      {\n        \"id\": 83,\n        \"name\": \"Sam Saffron\",\n        \"username\": \"samsaffron\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon\",\n        \"created_at\": \"2013-02-04T15:26:26.278-05:00\",\n        \"cooked\": \"<p>Welcome!</p>\\n\\n<h3>Try is a sandbox, a safe place to play with Discourse and its features.</h3>\\n\\n<p>This site is reset every day (or even more often). <strong>Any accounts or posts you create here will not exist tomorrow!</strong></p>\\n\\n<p>Feel free to experiment here in the sandbox, but always be civil and respectful -- <em>and don't post anything here you wouldn't want lost forever!</em></p>\\n\\n<p>If you see anything here that is out of bounds, <strong>please flag it</strong>. Help keep our community clean. We monitor the flags regularly.</p>\\n\\n<ul>\\n<li><p>For more information on the Discourse project and its goals, visit <a href=\\\"http://www.discourse.org\\\" rel=\\\"nofollow\\\">http://www.discourse.org</a></p></li>\\n<li><p>If you wish to participate in the Discourse development process, visit <a href=\\\"http://meta.discourse.org\\\" rel=\\\"nofollow\\\">http://meta.discourse.org</a></p></li>\\n<li><p>If you want to check out the open source Discourse code, or set up a Discourse forum, visit <a href=\\\"https://github.com/discourse/discourse\\\" rel=\\\"nofollow\\\">https://github.com/discourse/discourse</a></p></li>\\n</ul>\",\n        \"post_number\": 1,\n        \"post_type\": 1,\n        \"updated_at\": \"2013-04-30T14:33:37.273-04:00\",\n        \"reply_count\": 1,\n        \"reply_to_post_number\": null,\n        \"quote_count\": 0,\n        \"avg_time\": 17,\n        \"incoming_link_count\": 0,\n        \"reads\": 34,\n        \"score\": 12.65,\n        \"yours\": false,\n        \"topic_slug\": \"this-site-is-a-sandbox-it-is-reset-every-day\",\n        \"topic_id\": 57,\n        \"display_username\": \"Sam Saffron\",\n        \"version\": 5,\n        \"can_edit\": null,\n        \"can_delete\": null,\n        \"can_recover\": false,\n        \"link_counts\": [\n          {\n            \"url\": \"http://meta.discourse.org\",\n            \"internal\": false,\n            \"reflection\": false,\n            \"clicks\": 7\n          },\n          {\n            \"url\": \"http://www.discourse.org\",\n            \"internal\": false,\n            \"reflection\": false,\n            \"clicks\": 4\n          },\n          {\n            \"url\": \"https://github.com/discourse/discourse\",\n            \"internal\": false,\n            \"reflection\": false,\n            \"clicks\": 0\n          }\n        ],\n        \"read\": false,\n        \"user_title\": null,\n        \"actions_summary\": [\n          {\n            \"id\": 2,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 3,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 4,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 5,\n            \"count\": 0,\n            \"hidden\": true,\n            \"can_act\": null\n          },\n          {\n            \"id\": 6,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 7,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 8,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          }\n        ],\n        \"moderator\": false,\n        \"staff\": true,\n        \"user_id\": 15,\n        \"hidden\": false,\n        \"hidden_reason_id\": null,\n        \"trust_level\": 1,\n        \"deleted_at\": null,\n        \"user_deleted\": false\n      },\n      {\n        \"id\": 84,\n        \"name\": \"Sam Saffron\",\n        \"username\": \"samsaffron\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon\",\n        \"created_at\": \"2013-02-04T15:26:32.362-05:00\",\n        \"cooked\": \"<p>This topic is now pinned. It will appear at the top of its category.</p>\",\n        \"post_number\": 2,\n        \"post_type\": 2,\n        \"updated_at\": \"2013-02-04T15:26:32.362-05:00\",\n        \"reply_count\": 0,\n        \"reply_to_post_number\": null,\n        \"quote_count\": 0,\n        \"avg_time\": 18,\n        \"incoming_link_count\": 0,\n        \"reads\": 34,\n        \"score\": 7.7,\n        \"yours\": false,\n        \"topic_slug\": \"this-site-is-a-sandbox-it-is-reset-every-day\",\n        \"topic_id\": 57,\n        \"display_username\": \"Sam Saffron\",\n        \"version\": 1,\n        \"can_edit\": null,\n        \"can_delete\": null,\n        \"can_recover\": false,\n        \"read\": false,\n        \"user_title\": null,\n        \"actions_summary\": [\n          {\n            \"id\": 2,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 3,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 4,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 5,\n            \"count\": 0,\n            \"hidden\": true,\n            \"can_act\": null\n          },\n          {\n            \"id\": 6,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 7,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 8,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          }\n        ],\n        \"moderator\": false,\n        \"staff\": true,\n        \"user_id\": 15,\n        \"hidden\": false,\n        \"hidden_reason_id\": null,\n        \"trust_level\": 1,\n        \"deleted_at\": null,\n        \"user_deleted\": false\n      },\n      {\n        \"id\": 86,\n        \"name\": \"Adam Davis\",\n        \"username\": \"stienman\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/281486f2a20201375414760dd347951d.png?s={size}&r=pg&d=identicon\",\n        \"created_at\": \"2013-02-04T15:29:00.843-05:00\",\n        \"cooked\": \"<p><img src=\\\"http://i288.photobucket.com/albums/ll164/stienman/ahh.gif\\\" alt=\\\"image\\\" title=\\\"\\\" width=\\\"36\\\" height=\\\"22\\\"></p>\",\n        \"post_number\": 3,\n        \"post_type\": 1,\n        \"updated_at\": \"2013-02-04T15:29:00.843-05:00\",\n        \"reply_count\": 0,\n        \"reply_to_post_number\": null,\n        \"quote_count\": 0,\n        \"avg_time\": 19,\n        \"incoming_link_count\": 2,\n        \"reads\": 34,\n        \"score\": 152.75,\n        \"yours\": false,\n        \"topic_slug\": \"this-site-is-a-sandbox-it-is-reset-every-day\",\n        \"topic_id\": 57,\n        \"display_username\": \"Adam Davis\",\n        \"version\": 1,\n        \"can_edit\": null,\n        \"can_delete\": null,\n        \"can_recover\": false,\n        \"read\": false,\n        \"user_title\": null,\n        \"actions_summary\": [\n          {\n            \"id\": 2,\n            \"count\": 5,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 3,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 4,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 5,\n            \"count\": 0,\n            \"hidden\": true,\n            \"can_act\": null\n          },\n          {\n            \"id\": 6,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 7,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 8,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          }\n        ],\n        \"moderator\": false,\n        \"staff\": true,\n        \"user_id\": 23,\n        \"hidden\": false,\n        \"hidden_reason_id\": null,\n        \"trust_level\": 1,\n        \"deleted_at\": null,\n        \"user_deleted\": false\n      },\n      {\n        \"id\": 226,\n        \"name\": \"Stefan Plattner\",\n        \"username\": \"splattne\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon\",\n        \"created_at\": \"2013-02-05T12:14:17.364-05:00\",\n        \"cooked\": \"<p>What? So I have to post this every day? <br><img src=\\\"http://q101.com/wp-content/uploads/2012/02/GroundhogDay.gif\\\" alt=\\\"image\\\" title=\\\"\\\" width=\\\"650\\\" height=\\\"824\\\"></p>\",\n        \"post_number\": 14,\n        \"post_type\": 1,\n        \"updated_at\": \"2013-02-05T12:14:17.364-05:00\",\n        \"reply_count\": 0,\n        \"reply_to_post_number\": 1,\n        \"quote_count\": 0,\n        \"avg_time\": 16,\n        \"incoming_link_count\": 0,\n        \"reads\": 22,\n        \"score\": 170.2,\n        \"yours\": false,\n        \"topic_slug\": \"this-site-is-a-sandbox-it-is-reset-every-day\",\n        \"topic_id\": 57,\n        \"display_username\": \"Stefan Plattner\",\n        \"version\": 1,\n        \"can_edit\": null,\n        \"can_delete\": null,\n        \"can_recover\": false,\n        \"read\": false,\n        \"user_title\": null,\n        \"reply_to_user\": {\n          \"username\": \"samsaffron\",\n          \"avatar_template\": \"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon\"\n        },\n        \"actions_summary\": [\n          {\n            \"id\": 2,\n            \"count\": 5,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 3,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 4,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 5,\n            \"count\": 0,\n            \"hidden\": true,\n            \"can_act\": null\n          },\n          {\n            \"id\": 6,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 7,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          },\n          {\n            \"id\": 8,\n            \"count\": 0,\n            \"hidden\": false,\n            \"can_act\": null\n          }\n        ],\n        \"moderator\": false,\n        \"staff\": true,\n        \"user_id\": 50,\n        \"hidden\": false,\n        \"hidden_reason_id\": null,\n        \"trust_level\": 1,\n        \"deleted_at\": null,\n        \"user_deleted\": false\n      }\n    ],\n    \"stream\": [\n      83,\n      84,\n      86,\n      226\n    ]\n  },\n  \"draft\": null,\n  \"draft_key\": \"topic_57\",\n  \"draft_sequence\": null,\n  \"pinned\": true,\n  \"details\": {\n    \"auto_close_at\": null,\n    \"created_by\": {\n      \"id\": 15,\n      \"username\": \"samsaffron\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon\"\n    },\n    \"last_poster\": {\n      \"id\": 50,\n      \"username\": \"splattne\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon\"\n    },\n    \"participants\": [\n      {\n        \"id\": 15,\n        \"username\": \"samsaffron\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon\",\n        \"post_count\": 2\n      },\n      {\n        \"id\": 23,\n        \"username\": \"stienman\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/281486f2a20201375414760dd347951d.png?s={size}&r=pg&d=identicon\",\n        \"post_count\": 1\n      },\n      {\n        \"id\": 50,\n        \"username\": \"splattne\",\n        \"avatar_template\": \"//www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon\",\n        \"post_count\": 1\n      }\n    ],\n    \"suggested_topics\": [\n      {\n        \"id\": 62,\n        \"title\": \"Funny pictures (Keep 'em clean, folks!)\",\n        \"fancy_title\": \"Funny pictures (Keep &lsquo;em clean, folks!)\",\n        \"slug\": \"funny-pictures-keep-em-clean-folks\",\n        \"posts_count\": 39,\n        \"reply_count\": 3,\n        \"highest_post_number\": 39,\n        \"image_url\": \"http://cdn.discourse.org/uploads/try2_discourse/27/nt3b8.jpeg\",\n        \"created_at\": \"2013-02-04T15:42:30.290-05:00\",\n        \"last_posted_at\": \"2013-11-06T02:47:27.135-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-06T03:02:06.494-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"archetype\": \"regular\",\n        \"like_count\": 10,\n        \"views\": 186,\n        \"category\": {\n          \"id\": 13,\n          \"name\": \"uncategPorized\",\n          \"color\": \"AB9364\",\n          \"text_color\": \"FFFFFF\",\n          \"slug\": \"uncategorized\",\n          \"topic_count\": 21,\n          \"description\": \"\",\n          \"topic_url\": null,\n          \"hotness\": 5,\n          \"read_restricted\": false,\n          \"permission\": null,\n          \"available_groups\": [\n            \"admins\",\n            \"everyone\",\n            \"moderators\",\n            \"staff\",\n            \"trust_level_1\",\n            \"trust_level_2\",\n            \"trust_level_3\",\n            \"trust_level_4\",\n            \"trust_level_5\"\n          ],\n          \"auto_close_days\": null,\n          \"group_permissions\": [\n            {\n              \"permission_type\": 1,\n              \"group_name\": \"everyone\"\n            }\n          ],\n          \"position\": 1\n        }\n      },\n      {\n        \"id\": 152,\n        \"title\": \"What are good resolutions to the enviroment?\",\n        \"fancy_title\": \"What are good resolutions to the enviroment?\",\n        \"slug\": \"what-are-good-resolutions-to-the-enviroment\",\n        \"posts_count\": 3,\n        \"reply_count\": 0,\n        \"highest_post_number\": 3,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-05T02:12:37.113-05:00\",\n        \"last_posted_at\": \"2013-11-05T11:32:31.259-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-05T11:32:31.259-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"archetype\": \"regular\",\n        \"like_count\": 0,\n        \"views\": 50,\n        \"category\": {\n          \"id\": 13,\n          \"name\": \"uncategorized\",\n          \"color\": \"AB9364\",\n          \"text_color\": \"FFFFFF\",\n          \"slug\": \"uncategorized\",\n          \"topic_count\": 21,\n          \"description\": \"\",\n          \"topic_url\": null,\n          \"hotness\": 5,\n          \"read_restricted\": false,\n          \"permission\": null,\n          \"available_groups\": [\n            \"admins\",\n            \"everyone\",\n            \"moderators\",\n            \"staff\",\n            \"trust_level_1\",\n            \"trust_level_2\",\n            \"trust_level_3\",\n            \"trust_level_4\",\n            \"trust_level_5\"\n          ],\n          \"auto_close_days\": null,\n          \"group_permissions\": [\n            {\n              \"permission_type\": 1,\n              \"group_name\": \"everyone\"\n            }\n          ],\n          \"position\": 1\n        }\n      },\n      {\n        \"id\": 87,\n        \"title\": \"Hilarious Picture Dump (Image Heavy)\",\n        \"fancy_title\": \"Hilarious Picture Dump (Image Heavy)\",\n        \"slug\": \"hilarious-picture-dump-image-heavy\",\n        \"posts_count\": 4,\n        \"reply_count\": 0,\n        \"highest_post_number\": 4,\n        \"image_url\": \"http://cdn.discourse.org/uploads/try2_discourse/33/1208514086be7cd9d27c00836da43ab15d00ec7be_4c256628scaled.jpeg\",\n        \"created_at\": \"2013-02-04T21:07:51.246-05:00\",\n        \"last_posted_at\": \"2013-02-05T12:52:47.243-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-06-26T01:45:24.590-04:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"archetype\": \"regular\",\n        \"like_count\": 0,\n        \"views\": 69,\n        \"category\": {\n          \"id\": 13,\n          \"name\": \"uncategorized\",\n          \"color\": \"AB9364\",\n          \"text_color\": \"FFFFFF\",\n          \"slug\": \"uncategorized\",\n          \"topic_count\": 21,\n          \"description\": \"\",\n          \"topic_url\": null,\n          \"hotness\": 5,\n          \"read_restricted\": false,\n          \"permission\": null,\n          \"available_groups\": [\n            \"admins\",\n            \"everyone\",\n            \"moderators\",\n            \"staff\",\n            \"trust_level_1\",\n            \"trust_level_2\",\n            \"trust_level_3\",\n            \"trust_level_4\",\n            \"trust_level_5\"\n          ],\n          \"auto_close_days\": null,\n          \"group_permissions\": [\n            {\n              \"permission_type\": 1,\n              \"group_name\": \"everyone\"\n            }\n          ],\n          \"position\": 1\n        }\n      },\n      {\n        \"id\": 197,\n        \"title\": \"Replying as a new topic\",\n        \"fancy_title\": \"Replying as a new topic\",\n        \"slug\": \"replying-as-a-new-topic\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-06T00:57:00.177-05:00\",\n        \"last_posted_at\": \"2013-11-06T00:57:00.326-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-06T00:57:00.326-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"archetype\": \"regular\",\n        \"like_count\": 0,\n        \"views\": 23,\n        \"category\": {\n          \"id\": 13,\n          \"name\": \"uncategorized\",\n          \"color\": \"AB9364\",\n          \"text_color\": \"FFFFFF\",\n          \"slug\": \"uncategorized\",\n          \"topic_count\": 21,\n          \"description\": \"\",\n          \"topic_url\": null,\n          \"hotness\": 5,\n          \"read_restricted\": false,\n          \"permission\": null,\n          \"available_groups\": [\n            \"admins\",\n            \"everyone\",\n            \"moderators\",\n            \"staff\",\n            \"trust_level_1\",\n            \"trust_level_2\",\n            \"trust_level_3\",\n            \"trust_level_4\",\n            \"trust_level_5\"\n          ],\n          \"auto_close_days\": null,\n          \"group_permissions\": [\n            {\n              \"permission_type\": 1,\n              \"group_name\": \"everyone\"\n            }\n          ],\n          \"position\": 1\n        }\n      },\n      {\n        \"id\": 157,\n        \"title\": \"What is your opinion on the movie hunger game?\",\n        \"fancy_title\": \"What is your opinion on the movie hunger game?\",\n        \"slug\": \"what-is-your-opinion-on-the-movie-hunger-game\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-11-05T03:59:13.194-05:00\",\n        \"last_posted_at\": \"2013-11-05T03:59:13.343-05:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-11-05T03:59:13.343-05:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"archetype\": \"regular\",\n        \"like_count\": 1,\n        \"views\": 23,\n        \"category\": {\n          \"id\": 13,\n          \"name\": \"uncategorized\",\n          \"color\": \"AB9364\",\n          \"text_color\": \"FFFFFF\",\n          \"slug\": \"uncategorized\",\n          \"topic_count\": 21,\n          \"description\": \"\",\n          \"topic_url\": null,\n          \"hotness\": 5,\n          \"read_restricted\": false,\n          \"permission\": null,\n          \"available_groups\": [\n            \"admins\",\n            \"everyone\",\n            \"moderators\",\n            \"staff\",\n            \"trust_level_1\",\n            \"trust_level_2\",\n            \"trust_level_3\",\n            \"trust_level_4\",\n            \"trust_level_5\"\n          ],\n          \"auto_close_days\": null,\n          \"group_permissions\": [\n            {\n              \"permission_type\": 1,\n              \"group_name\": \"everyone\"\n            }\n          ],\n          \"position\": 1\n        }\n      }\n    ],\n    \"links\": [\n      {\n        \"url\": \"http://meta.discourse.org\",\n        \"title\": null,\n        \"fancy_title\": null,\n        \"internal\": false,\n        \"reflection\": false,\n        \"clicks\": \"7\",\n        \"user_id\": 15\n      },\n      {\n        \"url\": \"http://www.discourse.org\",\n        \"title\": null,\n        \"fancy_title\": null,\n        \"internal\": false,\n        \"reflection\": false,\n        \"clicks\": \"4\",\n        \"user_id\": 15\n      },\n      {\n        \"url\": \"https://github.com/discourse/discourse\",\n        \"title\": null,\n        \"fancy_title\": null,\n        \"internal\": false,\n        \"reflection\": false,\n        \"clicks\": \"0\",\n        \"user_id\": 15\n      },\n      {\n        \"url\": \"/users/pekka.gaiser\",\n        \"title\": null,\n        \"fancy_title\": null,\n        \"internal\": true,\n        \"reflection\": false,\n        \"clicks\": \"0\",\n        \"user_id\": 9\n      },\n      {\n        \"url\": \"http://dev.discourse.org/t/general-bugs-list/92/365)\",\n        \"title\": null,\n        \"fancy_title\": null,\n        \"internal\": false,\n        \"reflection\": false,\n        \"clicks\": \"0\",\n        \"user_id\": 9\n      },\n      {\n        \"url\": \"/users/pekka\",\n        \"title\": null,\n        \"fancy_title\": null,\n        \"internal\": true,\n        \"reflection\": false,\n        \"clicks\": \"0\",\n        \"user_id\": 9\n      }\n    ]\n  },\n  \"highest_post_number\": 14,\n  \"deleted_by\": null,\n  \"id\": 57,\n  \"title\": \"THIS SITE IS A SANDBOX -- it is reset every day\",\n  \"fancy_title\": \"THIS SITE IS A SANDBOX &ndash; it is reset every day\",\n  \"posts_count\": 4,\n  \"created_at\": \"2013-02-04T15:26:25.977-05:00\",\n  \"views\": 268,\n  \"reply_count\": 9,\n  \"last_posted_at\": \"2013-02-05T12:14:17.364-05:00\",\n  \"visible\": true,\n  \"closed\": false,\n  \"archived\": false,\n  \"has_best_of\": false,\n  \"archetype\": \"regular\",\n  \"slug\": \"this-site-is-a-sandbox-it-is-reset-every-day\",\n  \"category_id\": 13,\n  \"deleted_at\": null\n}\n"
  },
  {
    "path": "spec/fixtures/topic_invite_user.json",
    "content": "{\n  \"success\": true\n}\n"
  },
  {
    "path": "spec/fixtures/topic_posts.json",
    "content": "{\n  \"post_stream\":{\n    \"posts\":[\n      {\n        \"id\":123456,\n        \"name\":\"Someones Name\",\n        \"username\":\"Some-username\",\n        \"avatar_template\":\"https://avatars.discourse.org/v4/letter/w/abcd123/{size}.png\",\n        \"created_at\":\"2020-01-27T11:32:03.646Z\",\n        \"cooked\":\"<p>Some text.</p>\",\n        \"post_number\":2,\n        \"post_type\":1,\n        \"updated_at\":\"2020-01-27T11:32:03.646Z\",\n        \"reply_count\":0,\n        \"reply_to_post_number\":null,\n        \"quote_count\":0,\n        \"incoming_link_count\":0,\n        \"reads\":2,\n        \"readers_count\":1,\n        \"score\":45.2,\n        \"yours\":false,\n        \"topic_id\":57,\n        \"topic_slug\":\"some-topic-slug\",\n        \"display_username\":\"Someones Name\",\n        \"primary_group_name\":null,\n        \"primary_group_flair_url\":null,\n        \"primary_group_flair_bg_color\":null,\n        \"primary_group_flair_color\":null,\n        \"version\":1,\n        \"can_edit\":true,\n        \"can_delete\":true,\n        \"can_recover\":false,\n        \"can_wiki\":true,\n        \"read\":true,\n        \"user_title\":null,\n        \"actions_summary\":[{\"id\":2,\"count\":3,\"can_act\":true}],\n        \"raw\": [{\"type\":\"paragraph\",\"children\":[{\"text\":\"This is a raw post  I've got white   space!... and emojis 😈😈😈😈😈😈\"}]}],\n        \"moderator\":false,\n        \"admin\":false,\n        \"staff\":false,\n        \"user_id\":123,\n        \"hidden\":false,\n        \"trust_level\":3,\n        \"deleted_at\":null,\n        \"user_deleted\":false,\n        \"edit_reason\":null,\n        \"can_view_edit_history\":true,\n        \"wiki\":false,\n        \"reviewable_id\":0,\n        \"reviewable_score_count\":0,\n        \"reviewable_score_pending_count\":0,\n        \"user_created_at\":\"2018-01-10T21:29:15.554Z\",\n        \"user_date_of_birth\":null,\n        \"can_accept_answer\":false,\n        \"can_unaccept_answer\":false,\n        \"accepted_answer\":false\n      }\n    ]\n  },\n  \"id\":57\n}\n"
  },
  {
    "path": "spec/fixtures/topics_created_by.json",
    "content": "{\n  \"users\": [\n    {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"avatar_template\": \"//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon\"\n    }\n  ],\n  \"topic_list\": {\n    \"can_create_topic\": true,\n    \"draft\": null,\n    \"draft_key\": \"new_topic\",\n    \"draft_sequence\": 0,\n    \"topics\": [\n      {\n        \"id\": 1,\n        \"title\": \"Test topic #1\",\n        \"fancy_title\": \"Test topic #1\",\n        \"slug\": \"test-topic-1\",\n        \"posts_count\": 1,\n        \"reply_count\": 0,\n        \"highest_post_number\": 1,\n        \"image_url\": null,\n        \"created_at\": \"2013-07-22T19:27:14.818-04:00\",\n        \"last_posted_at\": \"2013-07-22T19:41:49.768-04:00\",\n        \"bumped\": true,\n        \"bumped_at\": \"2013-07-22T19:41:49.768-04:00\",\n        \"unseen\": false,\n        \"pinned\": false,\n        \"visible\": true,\n        \"closed\": false,\n        \"archived\": false,\n        \"views\": 68,\n        \"like_count\": 0,\n        \"has_best_of\": false,\n        \"archetype\": \"regular\",\n        \"last_poster_username\": \"test_user\",\n        \"category_id\": 1,\n        \"posters\": [\n          {\n            \"extras\": null,\n            \"description\": \"Original Poster, Most Recent Poster\",\n            \"user_id\": 1\n          }\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/update_trust_level.json",
    "content": "{\n  \"admin_user\": {\n    \"id\": 2,\n    \"username\": \"robin\",\n    \"uploaded_avatar_id\": 3,\n    \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": \"2014-09-19T13:00:20.939Z\",\n    \"last_emailed_at\": \"2015-01-01T01:01:24.438Z\",\n    \"created_at\": \"2014-09-13T14:27:07.352Z\",\n    \"last_seen_age\": \"4mon\",\n    \"last_emailed_age\": \"13d\",\n    \"created_at_age\": \"4mon\",\n    \"username_lower\": \"robin\",\n    \"trust_level\": 2,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 3,\n    \"posts_read_count\": 2,\n    \"topics_entered\": 2,\n    \"post_count\": 0,\n    \"can_send_activation_email\": true,\n    \"can_activate\": false,\n    \"can_deactivate\": true,\n    \"ip_address\": \"127.0.0.1\",\n    \"registration_ip_address\": \"127.0.0.1\",\n    \"single_sign_on_record\": null\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/upload_avatar.json",
    "content": "{\n  \"id\": 11,\n  \"user_id\": 1,\n  \"original_filename\": \"avatar.jpg\",\n  \"filesize\": 86900,\n  \"width\": 360,\n  \"height\": 360,\n  \"url\": \"/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg\",\n  \"created_at\": \"2015-06-21T12:35:45.182Z\",\n  \"updated_at\": \"2015-06-21T12:35:45.195Z\",\n  \"sha1\": \"417e624d2453925e6c68748b9aa67637c284b5aa\",\n  \"origin\": null,\n  \"retain_hours\": null\n}"
  },
  {
    "path": "spec/fixtures/upload_file.json",
    "content": "{\n  \"id\": 11,\n  \"user_id\": 1,\n  \"original_filename\": \"grumpy_cat.gif\",\n  \"filesize\": 86900,\n  \"width\": 360,\n  \"height\": 360,\n  \"url\": \"/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg\",\n  \"created_at\": \"2015-06-21T12:35:45.182Z\",\n  \"updated_at\": \"2015-06-21T12:35:45.195Z\",\n  \"sha1\": \"417e624d2453925e6c68748b9aa67637c284b5aa\",\n  \"origin\": null,\n  \"retain_hours\": null\n}\n"
  },
  {
    "path": "spec/fixtures/user.json",
    "content": "{\n  \"user_badges\": [],\n  \"user\": {\n    \"id\": 1,\n    \"username\": \"test_user\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/test/{size}/2.png\",\n    \"name\": \"Test User\",\n    \"email\": \"test_user@example.com\",\n    \"last_posted_at\": null,\n    \"last_seen_at\": \"2014-09-04T08:22:19.370-04:00\",\n    \"bio_cooked\": null,\n    \"created_at\": \"2014-09-04T08:04:25.104-04:00\",\n    \"can_edit\": true,\n    \"can_edit_username\": true,\n    \"can_edit_email\": true,\n    \"can_edit_name\": true,\n    \"stats\": [\n      {\n        \"action_type\": 13,\n        \"count\": 1,\n        \"id\": null\n      }\n    ],\n    \"can_send_private_message_to_user\": true,\n    \"bio_excerpt\": \"<div class='missing-profile'>test hasn't entered anything in the About Me field of their profile yet</div>\",\n    \"trust_level\": 0,\n    \"moderator\": false,\n    \"admin\": false,\n    \"title\": null,\n    \"badge_count\": 0,\n    \"has_title_badges\": false,\n    \"custom_fields\": {},\n    \"number_of_deleted_posts\": 0,\n    \"number_of_flagged_posts\": 0,\n    \"number_of_flags_given\": 0,\n    \"number_of_suspensions\": 0,\n    \"locale\": null,\n    \"email_digests\": true,\n    \"email_private_messages\": true,\n    \"email_direct\": true,\n    \"email_always\": false,\n    \"digest_after_days\": 7,\n    \"mailing_list_mode\": false,\n    \"auto_track_topics_after_msecs\": 240000,\n    \"new_topic_duration_minutes\": 2880,\n    \"external_links_in_new_tab\": false,\n    \"dynamic_favicon\": false,\n    \"enable_quoting\": true,\n    \"muted_category_ids\": [],\n    \"tracked_category_ids\": [],\n    \"watched_category_ids\": [],\n    \"private_messages_stats\": {\n      \"all\": 1,\n      \"mine\": 0,\n      \"unread\": 0\n    },\n    \"disable_jump_reply\": false,\n    \"gravatar_avatar_upload_id\": null,\n    \"custom_avatar_upload_id\": null,\n    \"invited_by\": null,\n    \"custom_groups\": [],\n    \"featured_user_badge_ids\": []\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/user_activate_success.json",
    "content": "{\n  \"success\": \"OK\"\n}\n"
  },
  {
    "path": "spec/fixtures/user_badges.json",
    "content": "{\n  \"user_badges\": [\n    {\n      \"id\": 3,\n      \"granted_at\": \"2014-11-30T14:27:19.404Z\",\n      \"badge_id\": 10,\n      \"user_id\": 1,\n      \"granted_by_id\": -1\n    },\n    {\n      \"id\": 2,\n      \"granted_at\": \"2014-09-16T13:08:23.061Z\",\n      \"post_id\": 15,\n      \"post_number\": 1,\n      \"badge_id\": 5,\n      \"user_id\": 1,\n      \"granted_by_id\": -1,\n      \"topic_id\": 12\n    }\n  ],\n  \"badges\": [\n    {\n      \"id\": 10,\n      \"name\": \"Editor\",\n      \"description\": null,\n      \"grant_count\": 1,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"badge_type_id\": 3\n    },\n    {\n      \"id\": 5,\n      \"name\": \"Welcome\",\n      \"description\": null,\n      \"grant_count\": 1,\n      \"allow_title\": false,\n      \"multiple_grant\": false,\n      \"icon\": \"fa-certificate\",\n      \"image\": null,\n      \"listable\": true,\n      \"enabled\": true,\n      \"badge_grouping_id\": 2,\n      \"system\": true,\n      \"badge_type_id\": 3\n    }\n  ],\n  \"badge_types\": [\n    {\n      \"id\": 3,\n      \"name\": \"Bronze\",\n      \"sort_order\": 7\n    }\n  ],\n  \"users\": [\n    {\n      \"id\": 1,\n      \"username\": \"test_user\",\n      \"uploaded_avatar_id\": 7,\n      \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\"\n    },\n    {\n      \"id\": -1,\n      \"username\": \"system\",\n      \"uploaded_avatar_id\": 1,\n      \"avatar_template\": \"/user_avatar/localhost/system/{size}/1.png\"\n    }\n  ],\n  \"topics\": [\n    {\n      \"id\": 12,\n      \"title\": \"Test topic for inviting users\",\n      \"fancy_title\": \"Test topic for inviting users\",\n      \"slug\": \"test-topic-for-inviting-users\",\n      \"posts_count\": 1\n    }\n  ],\n  \"user\": {\n    \"id\": 1,\n    \"username\": \"test_user\",\n    \"uploaded_avatar_id\": 7,\n    \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\",\n    \"name\": \"Test User\",\n    \"email\": \"batman@gotham.com\",\n    \"last_posted_at\": \"2015-01-07T13:59:29.406Z\",\n    \"last_seen_at\": \"2015-01-07T15:59:16.095Z\",\n    \"created_at\": \"2014-09-11T00:11:44.207Z\",\n    \"website\": \"http://\",\n    \"can_edit\": true,\n    \"can_edit_username\": true,\n    \"can_edit_email\": true,\n    \"can_edit_name\": true,\n    \"stats\": [\n      {\n        \"action_type\": 12,\n        \"count\": 9,\n        \"id\": null\n      },\n      {\n        \"action_type\": 4,\n        \"count\": 12,\n        \"id\": null\n      },\n      {\n        \"action_type\": 5,\n        \"count\": 3,\n        \"id\": null\n      },\n      {\n        \"action_type\": 2,\n        \"count\": 1,\n        \"id\": null\n      },\n      {\n        \"action_type\": 3,\n        \"count\": 1,\n        \"id\": null\n      }\n    ],\n    \"can_send_private_messages\": true,\n    \"can_send_private_message_to_user\": false,\n    \"bio_excerpt\": \"<div class='missing-profile'>the About Me field of your profile is currently blank, <a href='/users/test_user/preferences/about-me'>would you like to fill it out?</a></div>\",\n    \"trust_level\": 0,\n    \"moderator\": false,\n    \"admin\": true,\n    \"title\": null,\n    \"badge_count\": 2,\n    \"notification_count\": 2,\n    \"has_title_badges\": false,\n    \"custom_fields\": {},\n    \"post_count\": 15,\n    \"can_be_deleted\": false,\n    \"can_delete_all_posts\": false,\n    \"locale\": \"\",\n    \"email_digests\": true,\n    \"email_private_messages\": true,\n    \"email_direct\": true,\n    \"email_always\": false,\n    \"digest_after_days\": 7,\n    \"mailing_list_mode\": false,\n    \"auto_track_topics_after_msecs\": 240000,\n    \"new_topic_duration_minutes\": 2880,\n    \"external_links_in_new_tab\": false,\n    \"dynamic_favicon\": false,\n    \"enable_quoting\": true,\n    \"muted_category_ids\": [],\n    \"tracked_category_ids\": [],\n    \"watched_category_ids\": [],\n    \"private_messages_stats\": {\n      \"all\": 9,\n      \"mine\": 9,\n      \"unread\": 0\n    },\n    \"disable_jump_reply\": false,\n    \"gravatar_avatar_upload_id\": null,\n    \"custom_avatar_upload_id\": null,\n    \"invited_by\": null,\n    \"custom_groups\": [],\n    \"featured_user_badge_ids\": [\n      3,\n      2\n    ],\n    \"card_badge\": null\n  }\n}\n"
  },
  {
    "path": "spec/fixtures/user_create_success.json",
    "content": "{\n  \"success\": true,\n  \"active\": false,\n  \"message\": \"You're almost done! We sent an activation email to <b>test2@example.com</b>. Please follow the instructions in the email to activate your account.\"\n}\n"
  },
  {
    "path": "spec/fixtures/user_grant_admin.json",
    "content": "{\n    \"admin_user\": {\n        \"id\": 11,\n        \"username\": \"dave4\",\n        \"uploaded_avatar_id\": null,\n        \"avatar_template\": \"/letter_avatar/dave4/{size}/2.png\",\n        \"active\": true,\n        \"admin\": true,\n        \"moderator\": false,\n        \"last_seen_at\": null,\n        \"last_emailed_at\": \"2014-12-31T17:11:51.189Z\",\n        \"created_at\": \"2014-12-08T14:32:29.906Z\",\n        \"last_seen_age\": null,\n        \"last_emailed_age\": \"20d\",\n        \"created_at_age\": \"43d\",\n        \"username_lower\": \"dave4\",\n        \"trust_level\": 0,\n        \"trust_level_locked\": false,\n        \"flag_level\": 0,\n        \"title\": null,\n        \"suspended_at\": null,\n        \"suspended_till\": null,\n        \"suspended\": null,\n        \"blocked\": false,\n        \"time_read\": \"< 1m\",\n        \"days_visited\": 0,\n        \"posts_read_count\": 0,\n        \"topics_entered\": 0,\n        \"post_count\": 0,\n        \"can_send_activation_email\": true,\n        \"can_activate\": false,\n        \"can_deactivate\": false,\n        \"ip_address\": \"127.0.0.1\",\n        \"registration_ip_address\": \"127.0.0.1\",\n        \"single_sign_on_record\": null\n    }\n}\n"
  },
  {
    "path": "spec/fixtures/user_grant_moderator.json",
    "content": "{\n    \"admin_user\": {\n        \"id\": 11,\n        \"username\": \"dave4\",\n        \"uploaded_avatar_id\": null,\n        \"avatar_template\": \"/letter_avatar/dave4/{size}/2.png\",\n        \"active\": true,\n        \"admin\": false,\n        \"moderator\": true,\n        \"last_seen_at\": null,\n        \"last_emailed_at\": \"2014-12-31T17:11:51.189Z\",\n        \"created_at\": \"2014-12-08T14:32:29.906Z\",\n        \"last_seen_age\": null,\n        \"last_emailed_age\": \"20d\",\n        \"created_at_age\": \"43d\",\n        \"username_lower\": \"dave4\",\n        \"trust_level\": 0,\n        \"trust_level_locked\": false,\n        \"flag_level\": 0,\n        \"title\": null,\n        \"suspended_at\": null,\n        \"suspended_till\": null,\n        \"suspended\": null,\n        \"blocked\": false,\n        \"time_read\": \"< 1m\",\n        \"days_visited\": 0,\n        \"posts_read_count\": 0,\n        \"topics_entered\": 0,\n        \"post_count\": 0,\n        \"can_send_activation_email\": true,\n        \"can_activate\": false,\n        \"can_deactivate\": false,\n        \"ip_address\": \"127.0.0.1\",\n        \"registration_ip_address\": \"127.0.0.1\",\n        \"single_sign_on_record\": null\n    }\n}\n"
  },
  {
    "path": "spec/fixtures/user_list.json",
    "content": "[\n  {\n    \"id\": 1,\n    \"username\": \"test_user\",\n    \"uploaded_avatar_id\": 7,\n    \"avatar_template\": \"/user_avatar/localhost/test_user/{size}/7.png\",\n    \"email\": \"batman@gotham.com\",\n    \"active\": true,\n    \"admin\": true,\n    \"moderator\": false,\n    \"last_seen_at\": \"2015-01-03T13:46:59.822Z\",\n    \"last_emailed_at\": \"2015-01-02T13:38:11.417Z\",\n    \"created_at\": \"2014-09-11T00:11:44.207Z\",\n    \"last_seen_age\": \"1m\",\n    \"last_emailed_age\": \"1d\",\n    \"created_at_age\": \"4mon\",\n    \"username_lower\": \"test_user\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"22m\",\n    \"days_visited\": 22,\n    \"posts_read_count\": 19,\n    \"topics_entered\": 8,\n    \"post_count\": 14\n  },\n  {\n    \"id\": 18,\n    \"username\": \"steve3\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve3/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": \"2015-01-02T15:49:34.257Z\",\n    \"last_emailed_at\": \"2015-01-02T15:50:29.329Z\",\n    \"created_at\": \"2015-01-02T13:53:04.206Z\",\n    \"last_seen_age\": \"22h\",\n    \"last_emailed_age\": \"22h\",\n    \"created_at_age\": \"24h\",\n    \"username_lower\": \"steve3\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 1,\n    \"posts_read_count\": 1,\n    \"topics_entered\": 1,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 10,\n    \"username\": \"dave3\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave3/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": \"2015-01-02T13:37:17.835Z\",\n    \"last_emailed_at\": \"2015-01-01T13:08:57.912Z\",\n    \"created_at\": \"2014-12-08T14:15:29.198Z\",\n    \"last_seen_age\": \"1d\",\n    \"last_emailed_age\": \"2d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave3\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 2,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 7,\n    \"username\": \"phil12\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/phil12/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": \"2014-12-01T21:01:11.506Z\",\n    \"last_emailed_at\": \"2015-01-01T01:01:33.315Z\",\n    \"created_at\": \"2014-12-01T14:24:11.406Z\",\n    \"last_seen_age\": \"33d\",\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"33d\",\n    \"username_lower\": \"phil12\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 1,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 4,\n    \"username\": \"steve\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": \"2014-12-01T14:13:36.498Z\",\n    \"last_emailed_at\": \"2015-01-01T01:01:33.195Z\",\n    \"created_at\": \"2014-09-20T19:42:33.225Z\",\n    \"last_seen_age\": \"33d\",\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"3mon\",\n    \"username_lower\": \"steve\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 4,\n    \"posts_read_count\": 1,\n    \"topics_entered\": 1,\n    \"post_count\": 1\n  },\n  {\n    \"id\": 2,\n    \"username\": \"robin\",\n    \"uploaded_avatar_id\": 3,\n    \"avatar_template\": \"/user_avatar/localhost/robin/{size}/3.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": \"2014-09-19T13:00:20.939Z\",\n    \"last_emailed_at\": \"2015-01-01T01:01:24.438Z\",\n    \"created_at\": \"2014-09-13T14:27:07.352Z\",\n    \"last_seen_age\": \"4mon\",\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"4mon\",\n    \"username_lower\": \"robin\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 3,\n    \"posts_read_count\": 2,\n    \"topics_entered\": 2,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 16,\n    \"username\": \"carl\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/carl/{size}/2.png\",\n    \"active\": false,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2014-12-31T17:54:32.453Z\",\n    \"created_at\": \"2014-12-31T14:20:30.867Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"3d\",\n    \"username_lower\": \"carl\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 8,\n    \"username\": \"dave\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-01T01:01:33.220Z\",\n    \"created_at\": \"2014-12-01T14:29:13.762Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"33d\",\n    \"username_lower\": \"dave\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 9,\n    \"username\": \"dave2\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave2/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-01T01:22:45.477Z\",\n    \"created_at\": \"2014-12-08T14:09:47.150Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave2\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 11,\n    \"username\": \"dave4\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave4/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2014-12-31T17:11:51.189Z\",\n    \"created_at\": \"2014-12-08T14:32:29.906Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave4\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 12,\n    \"username\": \"dave5\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave5/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2014-12-31T17:11:51.189Z\",\n    \"created_at\": \"2014-12-08T17:32:09.337Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave5\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 13,\n    \"username\": \"dave6\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave6/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2014-12-31T17:11:14.396Z\",\n    \"created_at\": \"2014-12-08T17:39:46.840Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave6\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 14,\n    \"username\": \"dave7\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave7/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2014-12-31T17:12:43.028Z\",\n    \"created_at\": \"2014-12-08T17:41:07.950Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave7\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 15,\n    \"username\": \"dave8\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/dave8/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2014-12-31T17:09:30.024Z\",\n    \"created_at\": \"2014-12-08T17:46:13.471Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"3d\",\n    \"created_at_age\": \"26d\",\n    \"username_lower\": \"dave8\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 17,\n    \"username\": \"steve2\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve2/{size}/2.png\",\n    \"active\": false,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-02T13:39:40.453Z\",\n    \"created_at\": \"2015-01-02T13:39:40.277Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"1d\",\n    \"created_at_age\": \"1d\",\n    \"username_lower\": \"steve2\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 19,\n    \"username\": \"steve4\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve4/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-02T15:59:27.135Z\",\n    \"created_at\": \"2015-01-02T15:49:20.936Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"22h\",\n    \"created_at_age\": \"22h\",\n    \"username_lower\": \"steve4\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 20,\n    \"username\": \"steve5\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve5/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-02T16:05:38.191Z\",\n    \"created_at\": \"2015-01-02T15:55:13.665Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"22h\",\n    \"created_at_age\": \"22h\",\n    \"username_lower\": \"steve5\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 21,\n    \"username\": \"steve6\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve6/{size}/2.png\",\n    \"active\": true,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-02T17:18:47.096Z\",\n    \"created_at\": \"2015-01-02T17:07:58.686Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"20h\",\n    \"created_at_age\": \"21h\",\n    \"username_lower\": \"steve6\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": 22,\n    \"username\": \"steve7\",\n    \"uploaded_avatar_id\": null,\n    \"avatar_template\": \"/letter_avatar/steve7/{size}/2.png\",\n    \"active\": false,\n    \"admin\": false,\n    \"moderator\": false,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": \"2015-01-03T13:33:46.728Z\",\n    \"created_at\": \"2015-01-03T13:33:46.137Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": \"14m\",\n    \"created_at_age\": \"14m\",\n    \"username_lower\": \"steve7\",\n    \"trust_level\": 0,\n    \"trust_level_locked\": false,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 0,\n    \"topics_entered\": 0,\n    \"post_count\": 0\n  },\n  {\n    \"id\": -1,\n    \"username\": \"system\",\n    \"uploaded_avatar_id\": 1,\n    \"avatar_template\": \"/user_avatar/localhost/system/{size}/1.png\",\n    \"active\": true,\n    \"admin\": true,\n    \"moderator\": true,\n    \"last_seen_at\": null,\n    \"last_emailed_at\": null,\n    \"created_at\": \"2014-09-11T00:02:55.028Z\",\n    \"last_seen_age\": null,\n    \"last_emailed_age\": null,\n    \"created_at_age\": \"4mon\",\n    \"username_lower\": \"system\",\n    \"trust_level\": 4,\n    \"trust_level_locked\": true,\n    \"flag_level\": 0,\n    \"title\": null,\n    \"suspended_at\": null,\n    \"suspended_till\": null,\n    \"suspended\": null,\n    \"blocked\": false,\n    \"time_read\": \"< 1m\",\n    \"days_visited\": 0,\n    \"posts_read_count\": 13,\n    \"topics_entered\": 0,\n    \"post_count\": 13\n  }\n]\n"
  },
  {
    "path": "spec/fixtures/user_log_out_success.json",
    "content": "{\n  \"success\": \"OK\"\n}\n"
  },
  {
    "path": "spec/fixtures/user_update_avatar_success.json",
    "content": "{\n  \"success\": \"OK\"\n}\n"
  },
  {
    "path": "spec/fixtures/user_update_user.json",
    "content": "{\n  \"success\": true\n}\n"
  },
  {
    "path": "spec/fixtures/user_update_username.json",
    "content": "{\n  \"id\": 7,\n  \"username\": \"fake_user_2\"\n}\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\nrequire \"simplecov\"\n\nSimpleCov.formatter =\n  SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::HTMLFormatter])\n\nSimpleCov.start { add_filter \"/spec/\" }\n\nrequire \"discourse_api\"\nrequire \"rspec\"\nrequire \"webmock/rspec\"\nrequire \"ostruct\"\n\nRSpec.configure do |config|\n  config.expect_with :rspec do |c|\n    c.syntax = :expect\n  end\nend\n\nWebMock.disable_net_connect!(allow_localhost: true)\n\ndef host\n  \"http://localhost:3000\"\nend\n\ndef a_delete(path)\n  a_request(:delete, path)\nend\n\ndef a_get(path)\n  a_request(:get, path)\nend\n\ndef a_post(path)\n  a_request(:post, path)\nend\n\ndef a_put(path)\n  a_request(:put, path)\nend\n\ndef stub_delete(path)\n  stub_request(:delete, path)\nend\n\ndef stub_get(path)\n  stub_request(:get, path)\nend\n\ndef stub_post(path)\n  stub_request(:post, path)\nend\n\ndef stub_put(path)\n  stub_request(:put, path)\nend\n\ndef fixture_path\n  File.expand_path(\"../fixtures\", __FILE__)\nend\n\ndef fixture(file)\n  File.new(fixture_path + \"/\" + file)\nend\n\ndef escape_params(params)\n  params.map { |key, value| [CGI.escape(key), value].join(\"=\") }.join(\"&\")\nend\n"
  }
]