Repository: discourse/discourse_api Branch: main Commit: 6e5e71b692bb Files: 139 Total size: 306.0 KB Directory structure: gitextract_ygzwukcj/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .streerc ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── config_example.yml ├── discourse_api.gemspec ├── examples/ │ ├── backups.rb │ ├── badges.rb │ ├── bookmark_topic.rb │ ├── category.rb │ ├── change_topic_status.rb │ ├── create_private_message.rb │ ├── create_topic.rb │ ├── create_update_category.rb │ ├── create_user.rb │ ├── dashboard.rb │ ├── disposable_invite_tokens.rb │ ├── example.rb │ ├── group_set_user_notification_level.rb │ ├── groups.rb │ ├── invite_users.rb │ ├── manage_api_keys.rb │ ├── notifications.rb │ ├── polls.rb │ ├── post_action.rb │ ├── search.rb │ ├── sent_private_messages.rb │ ├── sso.rb │ ├── topic_lists.rb │ ├── update_user.rb │ └── upload_file.rb ├── lib/ │ ├── discourse_api/ │ │ ├── api/ │ │ │ ├── api_key.rb │ │ │ ├── backups.rb │ │ │ ├── badges.rb │ │ │ ├── categories.rb │ │ │ ├── dashboard.rb │ │ │ ├── email.rb │ │ │ ├── groups.rb │ │ │ ├── invite.rb │ │ │ ├── notifications.rb │ │ │ ├── params.rb │ │ │ ├── polls.rb │ │ │ ├── posts.rb │ │ │ ├── private_messages.rb │ │ │ ├── search.rb │ │ │ ├── site_settings.rb │ │ │ ├── sso.rb │ │ │ ├── tags.rb │ │ │ ├── topics.rb │ │ │ ├── uploads.rb │ │ │ ├── user_actions.rb │ │ │ └── users.rb │ │ ├── client.rb │ │ ├── error.rb │ │ ├── example_helper.rb │ │ ├── single_sign_on.rb │ │ └── version.rb │ └── discourse_api.rb └── spec/ ├── discourse_api/ │ ├── api/ │ │ ├── api_key_spec.rb │ │ ├── backups_spec.rb │ │ ├── badges_spec.rb │ │ ├── categories_spec.rb │ │ ├── email_spec.rb │ │ ├── groups_spec.rb │ │ ├── invite_spec.rb │ │ ├── notifications_spec.rb │ │ ├── params_spec.rb │ │ ├── polls_spec.rb │ │ ├── posts_spec.rb │ │ ├── private_messages_spec.rb │ │ ├── search_spec.rb │ │ ├── site_settings_spec.rb │ │ ├── sso_spec.rb │ │ ├── topics_spec.rb │ │ ├── uploads_spec.rb │ │ ├── user_actions_spec.rb │ │ └── users_spec.rb │ ├── client_spec.rb │ └── single_sign_on_spec.rb ├── fixtures/ │ ├── admin_user.json │ ├── api_key.json │ ├── backups.json │ ├── badges.json │ ├── categories.json │ ├── category_latest_topics.json │ ├── category_topics.json │ ├── create_topic_with_tags.json │ ├── email_list_all.json │ ├── email_settings.json │ ├── group.json │ ├── groups.json │ ├── hot.json │ ├── latest.json │ ├── list_api_keys.json │ ├── members_0.json │ ├── members_1.json │ ├── members_2.json │ ├── new.json │ ├── notification_success.json │ ├── notifications.json │ ├── polls_toggle_status.json │ ├── polls_vote.json │ ├── polls_voters.json │ ├── post.json │ ├── post_action_users.json │ ├── posts_before.json │ ├── posts_latest.json │ ├── private_messages.json │ ├── regenerate_api_key.json │ ├── replies.json │ ├── replies_and_topics.json │ ├── retrieve_invite.json │ ├── search.json │ ├── top.json │ ├── topic.json │ ├── topic_invite_user.json │ ├── topic_posts.json │ ├── topics_created_by.json │ ├── update_trust_level.json │ ├── upload_avatar.json │ ├── upload_file.json │ ├── user.json │ ├── user_activate_success.json │ ├── user_badges.json │ ├── user_create_success.json │ ├── user_grant_admin.json │ ├── user_grant_moderator.json │ ├── user_list.json │ ├── user_log_out_success.json │ ├── user_update_avatar_success.json │ ├── user_update_user.json │ └── user_update_username.json └── spec_helper.rb ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: pull_request: push: branches: - main jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup ruby uses: ruby/setup-ruby@v1 with: ruby-version: "3.3" bundler-cache: true - name: Rubocop run: bundle exec rubocop - name: syntax_tree if: ${{ !cancelled() }} run: | set -E bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake') test: runs-on: ubuntu-latest strategy: matrix: ruby: ["2.7", "3.0", "3.1", "3.2", "3.3"] steps: - uses: actions/checkout@v4 - name: Setup ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Tests run: bundle exec rake test publish: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: [lint, test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Release Gem uses: discourse/publish-rubygems-action@v3 env: RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} GIT_EMAIL: team@discourse.org GIT_NAME: discoursebot ================================================ FILE: .gitignore ================================================ Gemfile.lock coverage /config.yml ================================================ FILE: .rubocop.yml ================================================ inherit_gem: rubocop-discourse: stree-compat.yml Discourse/Plugins: Enabled: false ================================================ FILE: .streerc ================================================ --print-width=100 --plugins=plugin/trailing_comma,plugin/disable_auto_ternary ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [2.1.0] - 2025-12-02 ### Changed - Add support for `external_id` in topic creation ### Added - Add get topic url by `external_id` ## [2.0.1] - 2023-06-09 ### Removed - Invite admin method removed ## [2.0.0] - 2023-05-26 ### Changed - The package now requires ruby 2.7+ - The package now requires faraday 2.7+ ## [1.1.0] - 2022-07-05 ### Changed - `DiscourseApi::SingleSignOn.parse` now raises `DiscourseApi::SingleSignOn::ParseError` (inherits from `RuntimeError` to preserve backward compatibility) instead of `RuntimeError` when there's a signature mismatch. - `DiscourseApi::SingleSignOn.parse` now raises `DiscourseApi::SingleSignOn::MissingConfigError` (also inherits from `RuntimeError`) if `sso_secret` or `sso_url` are missing. ## [1.0.0] - 2022-05-01 ### Changed - The package now requires ruby 2.6+ ## [0.48.1] - 2022-04-13 ### Added - New attributes for Discourse Connect (aka SSO) ## [0.48.0] - 2022-01-28 ### Added - `group_add_owners` method (#239) - `group_remove_owners` method (#239) - `anonymize` method (#241) ### Changed - `DiscourseApi::Timeout` error now inherits from `DiscourseApi::Error` (#240) - `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) ## [0.47.0] - 2021-07-19 ### Added - Update invite method - Retrieve invite method - Destroy all expired invites method - Destroy invite method - Resend all invites method - Resend invite method - Pass params to get notifications API ### Deprecated - `invite_user_to_topic` has been deprecated, use `invite_to_topic` instead. - `create_private_message` has been deprecated, use `create_pm` instead. ## [0.46.0] - 2021-04-12 ### Added - Allow bookmarking topics - Add timeout to requests - Add params to get_topic_posts ## [0.45.1] - 2021-03-11 ### Added - Fetch global top topics - Allow setting topic notifications - Return full category response ### Changed - Use new search endpoint ## [0.45.0] - 2021-01-15 ### Added - Tag configuration in create_category/update_category - Topic#change_owner - Support passing approved to #create_user ### Changed - API key methods use the latest endpoints ## [0.44.0] - 2020-11-13 ### Fixed - Updated `show_tag` method to use new route ### Removed - Support for Ruby 2.3 and 2.4 ## [0.43.1] - 2020-11-04 ### Fixed - Tagged version 0.43.0 got pushed without commit due to new master branch protections in github. No, code changes here just making sure tags align with commits. ## [0.43.0] - 2020-11-04 ### Added - Add pagination to list groups endpoint ### Deprecated - `change_topic_status` has been deprecated, use `update_topic_status` instead. ## [0.42.0] - 2020-07-09 ### Added - Create topics with tags ## [0.41.0] - 2020-06-17 ### Added - Add basic auth support ### Fixed - Fix SSO custom field prefixes ### Removed - Obsolete api key endpoints ## [0.40.0] - 2020-05-07 ### Fixed - Add missing attributes to `sync_sso` ### Added - Add delete category method ## [0.39.3] - 2020-04-30 ### Fixed - Add `reviewable_by_group_name` to categories ## [0.39.2] - 2020-04-30 ### Fixed - Add `members_visibility_level` to group ## [0.39.1] - 2020-03-27 ### Fixed - Ensure released gem version matches this commit ## [0.39.0] - 2020-03-27 ### Added - Get latest posts across topics via posts.json - Allow more options parameters when creating a category - Don't require topic slug when updating topic status - Example files now read config.yml file when present for client settings ### Fixed - Issue with `topic_posts` and frozen strings - Fixed some topic and category methods ## [0.38.0] - 2019-10-18 ### Added - Allow setting locale in SingleSignOn - Optional param to group members to include owners as well as members ## [0.37.0] - 2019-09-23 ### Added - user-badges endpoint for full badges list - expanded list of allowed messages - grant/revoke moderation ## [0.36.0] - 2019-07-18 ### Added - Added poll methods ### Fixed - Updated create topic example - Fixed capitalization for header auth keys ## [0.35.0] - 2019-05-15 ### Added - Added `custom_fields` param to create/update category - Added `frozen_string_literal: true` to all the files - Added rubocop and all the changes that went along with it ### Fixed - Allow `api_username` to be changed for an initialized client - Update many of the `/users` routes to use the `/u` route ### Changed - Changed `update_trust_level` to follow consistent method param syntax where you specify the id first followed by params ## [0.34.0] - 2019-04-02 ### Added - Header based authentication ### Removed - Query param based authentication ## [0.33.0] - 2019-03-04 ### Added - Added a new method to update a users notification level in a category ## [0.32.0] - 2019-02-13 ### Added - Added a new method to update a users notification level in a group ## [0.31.0] - 2019-02-07 ### Added - Added `deactivate` method - Added 201 and 204 as valid POST responses ## [0.30.0] - 2018-12-19 ### Added - Add params hash to `list_users` ## [0.29.0] - 2018-12-07 ### Added - Add `add_groups` and `remove_groups` to `sync_sso` ## [0.28.2] - 2018-11-26 ### Fixed - Updated arguments for suspending a user ## [0.28.1] - 2018-10-26 ### Fixed - Fixed non-URI chars in `check_username` method ## [0.28.0] - 2018-10-23 ### Added - Added `check_username` method ## [0.27.0] - 2018-09-14 ### Added - Added `site_settings_update` method ## [0.26.0] - 2018-09-10 ### Added - Added user `user_actions` endpoint so you can retrieve `user_replies` and `user_topics_and_replies` ## [0.25.0] - 2018-08-15 ### Added - Added ability to rescue certain error classes and inspect the response object ## [0.24.0] - 2018-05-30 ### Added - Added support for custom `user_fields` when creating a user ## [0.23.1] - 2018-05-24 ### Fixed - Can now change `api_username` without creating a new client ## [0.23.0] - 2018-05-24 ### Added - Added `delete_user` method ## [0.22.0] - 2018-05-04 ### Added - Support for subfolder paths ## [0.21.0] - 2018-04-23 ### Fixed - Update GET groups api route - Update PUT groups api route ## [0.20.0] - 2017-12-13 ### Added - Add base error class ### Fixed - Update SSO ## [0.19.0] - 2017-11-22 ### Added - Added optional `create_post` params ## [0.18.0] - 2017-10-17 ### Added - Added `update_group` API call ### Fixed - Fixed params for create groups endpoint - Fixed invite token API endpoint ## [0.17.0] - 2017-06-29 ### Added - Add title to SSO sync ## [0.16.1] - 2017-06-23 ### Fixed - `user_sso` should use `user_id` instead of `username` - `upload_file` should also include optional `user_id` param ## [0.16.0] - 2017-05-14 ### Added - added `upload_file` ### Removed - removed `upload_post_image` ## [0.15.0] - 2017-04-12 ### Added - added the ability to create private messages ## [0.14.1] - 2016-12-20 ### Fixed - allow for rack 2.0+ versions so that it doesn't clash with rails. ## [0.14.0] - 2016-10-30 ### Added - improved error responses by adding `NotFoundError`, `UnprocessableEntity`, and `TooManyRequests` - added `delete_post` method ## [0.13.0] - 2016-10-09 ### Added - added `update_category` - added `upload_post_image` ## [0.12.0] - 2016-10-06 ### Added - add endpoint for `/admin/users/{id}/suspend` - add endpoint for `/admin/users/{id}/unsuspend` ## [0.11.0] - 2016-09-03 ### Fixed - add destination folder to backup download - `post_action_users` ### Added - `change_topic_status` - set username of topic on creation ## [0.10.1] - 2016-05-04 ### Fixed - raise an error if search is empty - fix /category path to be just /c - return errors for category_latest_topics if there are some ## [0.10.0] - 2016-04-19 ### Added - group_members: Allows you to retrieve more than 100 users with pagination (offset & limit) ### Fixed - Deprecation warning with SimpleCov - updated rack dependency and added ruby 2.3 to travis config ## [0.9.1] - 2016-03-23 ### Fixed - topic and post like/flag need to use `:id` ## [0.9.0] - 2016-03-22 ### Added - can now like/flag topics and posts ## [0.8.1] - 2016-03-03 ### Fixed - enable use of discourse_api to make unauthenticated requests to discourse endpoints like /categories and /topics ## [0.8.0] - 2016-02-28 ### Added - get stats from admin dashboard - get only stat totals from admin dashboard ## [0.7.0] - 2015-12-09 ### Added - get user by external_id ## [0.6.2] - 2015-12-02 ### Fixed - `API::Params` will not work correctly when both optional and defaults are specified ## [0.6.1] - 2015-11-28 ### Fixed - typo in topic_posts method ## [0.6.0] - 2015-11-27 ### Added - get posts in topic by an array of id's ## [0.5.1] - 2015-11-21 ### Fixed - remove puts statement ## [0.5.0] - 2015-11-21 ### Added - get latest category topics by page ## [0.4.0] - 2015-01-15 ### Added - generate an api key for a user - revoke an api key for a user - update user trust level - grant user badge ## [0.3.6] - 2015-01-11 ### Added - list badges - view email settings - list emails sent - list badges by user - be able to specify SSL connection settings - list api keys generated - list backups created ## [0.3.5] - 2015-01-06 ### Added - Can now get a list of users by type: active, new, staff, etc. - `client.category_latest_posts("category-slug")` endpoint ## [0.1.2] - 2014-05-11 - Release ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source "https://rubygems.org" # Specify your gem's dependencies in discourse_api.gemspec gemspec ================================================ FILE: Guardfile ================================================ # frozen_string_literal: true guard :rspec do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { 'spec' } end ================================================ FILE: LICENSE.txt ================================================ Copyright (c) 2014 Civilized Discourse Construction Kit, Inc. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # DiscourseApi The Discourse API gem allows you to consume the Discourse API ## Installation Add this line to your application's Gemfile: gem 'discourse_api' And then execute: $ bundle Or install it yourself as: $ gem install discourse_api ## Usage Over time this project intends to have a full Discourse API. At the moment there are only a few endpoints available: ```ruby client = DiscourseApi::Client.new("http://try.discourse.org") client.api_key = "YOUR_API_KEY" client.api_username = "YOUR_USERNAME" client.ssl(...) #=> specify SSL connection settings if needed # Topic endpoints client.latest_topics #=> Gets a list of the latest topics client.new_topics #=> Gets a list of new topics client.topics_by("sam") #=> Gets a list of topics created by user "sam" client.topic(57) #=> Gets the topic with id 57 # Search endpoint client.search("sandbox") #=> Gets a list of topics that match "sandbox" # Categories endpoint client.categories #=> Gets a list of categories client.category_latest_topics(category_slug: "lounge") #=> Gets a list of latest topics in a category # SSO endpoint client.sync_sso( #=> Synchronizes the SSO record sso_secret: "discourse_sso_rocks", name: "Test Name", username: "test_name", email: "name@example.com", external_id: "2", custom_fields: { field_1: 'potato' } ) # Private messages client.private_messages("test_user") #=> Gets a list of private messages received by "test_user" client.sent_private_messages("test_user") #=> Gets a list of private messages sent by "test_user" client.create_private_message( #=> Creates a private messages by api_username user title: "Confidential: Hello World!", raw: "This is the raw markdown for my private message", target_usernames: "user1,user2" ) ``` You can handle some basic errors by rescuing from certain error classes and inspecting the response object passed to those errors: ```ruby begin client.create_group({ name: 'NO' }) rescue DiscourseApi::UnprocessableEntity => error # `body` is something like `{ errors: ["Name must be at least 3 characters"] }` # This outputs "Name must be at least 3 characters" puts error.response.body['errors'].first end ``` Check 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. If your forum has a basic HTTP authentication enabled, set user and password: ```ruby client.basic_auth = { user: "test", password: "secret" } ``` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## Testing 1. Install discourse locally 2. Inside of your discourse directory, run: `bundle exec rake db:api_test_seed` 3. Start discourse: `bundle exec rails s` 4. Install bundler in the discourse_api directory, run `gem install bundler` 5. Inside of your discourse_api directory, run: `bundle exec rspec spec/` ================================================ FILE: Rakefile ================================================ # frozen_string_literal: true require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) task test: :spec task lint: :rubocop task default: [:spec, :lint] ================================================ FILE: config_example.yml ================================================ # 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. # host e.g. http://localhost:3000 or https://discourse.my_domain.com host: YOUR_HOST_NAME # api user (can effect results returned, e.g. .categories method returns only the categories your api_username can see) # create a new client with when changing api user api_username: YOUR_API_USERNAME # api key from Discourse admin panel /admin/api/keys api_key: YOUR_API_KEY ================================================ FILE: discourse_api.gemspec ================================================ # frozen_string_literal: true lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib) require "discourse_api/version" Gem::Specification.new do |spec| spec.name = "discourse_api" spec.version = DiscourseApi::VERSION spec.authors = ["Sam Saffron", "John Paul Ashenfelter", "Michael Herold", "Blake Erickson"] spec.email = %w[ sam.saffron@gmail.com john@ashenfelter.com michael.j.herold@gmail.com o.blakeerickson@gmail.com ] spec.description = "Discourse API" spec.summary = "Allows access to the Discourse API" spec.homepage = "http://github.com/discourse/discourse_api" spec.license = "MIT" spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_runtime_dependency "faraday", "~> 2.7" spec.add_runtime_dependency "faraday-follow_redirects" spec.add_runtime_dependency "faraday-multipart" spec.add_runtime_dependency "rack", ">= 1.6" spec.add_development_dependency "bundler", "~> 2.0" spec.add_development_dependency "guard", "~> 2.14" spec.add_development_dependency "guard-rspec", "~> 4.7" spec.add_development_dependency "rake", ">= 12.3.3" spec.add_development_dependency "rb-inotify", "~> 0.9" spec.add_development_dependency "rspec", "~> 3.4" spec.add_development_dependency "simplecov", "~> 0.11" spec.add_development_dependency "webmock", "~> 3.0" spec.add_development_dependency "rubocop-discourse", "= 3.8.1" spec.add_development_dependency "syntax_tree", "~> 6.2.0" spec.required_ruby_version = ">= 2.7.0" end ================================================ FILE: examples/backups.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # get list of backup files puts client.backups() # create backup puts client.create_backup() # restore backup puts client.restore_backup("backup_file_name.tar.gz") # download backup puts client.download_backup("backup_file_name.tar.gz", "/tmp/") ================================================ FILE: examples/badges.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # get badges puts client.badges # get badges for a user puts client.user_badges("test-user") # Grants a badge to a user. puts client.grant_user_badge(badge_id: 9, username: "test-user", reason: "Really nice person") # Creates a new badge ### # Required params: # :name, :badge_type_id (gold: 1, bronze: 3, silver: 2) # Optional params: # :description, :allow_title, :multiple_grant, :icon, :listable, # :target_posts, :query, :enabled, :auto_revoke, :badge_grouping_id, # :trigger, :show_posts, :image, :long_description ### puts client.create_badge(name: "Shiny new badge", badge_type_id: 1) ================================================ FILE: examples/bookmark_topic.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # Bookmark topic puts client.bookmark_topic(1418) # Remove bookmark from topic puts client.remove_topic_bookmark(1418) ================================================ FILE: examples/category.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # get categories puts client.categories() # get sub categories for parent category with id 2 puts client.categories(parent_category_id: 2) # get the full categories response puts client.categories_full() # List topics in a category category_topics = client.category_latest_topics(category_slug: "test-category") puts category_topics # List topics in a category paged category_topics_paged = client.category_latest_topics(category_slug: "test-category", page: "5") puts category_topics_paged # update category notification_level update_response = client.category_set_user_notification( id: "test-id", notification_level: "test-notification-level", ) puts update_response ================================================ FILE: examples/change_topic_status.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" response = client.create_topic( category: 1, skip_validations: true, auto_track: false, title: "Concert Master: A new way to choose", raw: "This is the raw markdown for my post", ) # get topic_id from response topic_id = response["topic_id"] ## # available options (guessing from reading discourse source) # status can be: ['autoclose', 'closed', 'archived', 'disabled', 'visible'] # enabled can be: [true, false] ## # lock topic (note: api_username determines user that is performing action) params = { status: "closed", enabled: true, api_username: "YOUR USERNAME/USERS USERNAME" } client.change_topic_status(topic_id, params) # unlock topic (note: api_username determines user that is performing action) params = { status: "closed", enabled: false, api_username: "YOUR USERNAME/USERS USERNAME" } client.change_topic_status(topic_id, params) ================================================ FILE: examples/create_private_message.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" client.create_private_message( title: "Confidential: Hello World!", raw: "This is the raw markdown for my private message", target_usernames: "user1,user2", ) ================================================ FILE: examples/create_topic.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" client.create_topic( category: 1, skip_validations: true, auto_track: false, title: "Concert Master: A new way to choose", raw: "This is the raw markdown for my post", ) # create Poll topic client.create_topic( category: 2, skip_validations: false, auto_track: false, title: "Your Favorite Color?", raw: "[poll name=color]\n- Green\n- Blue\n- Red\n[/poll]", ) # Create Topic with Tags client.create_topic( category: 1, skip_validations: true, auto_track: false, title: "Concert Master: A new way to choose", raw: "This is the raw markdown for my post", tags: %w[asdf fdsa], ) ================================================ FILE: examples/create_update_category.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" ### # Required category params: # :name, :color, :text_color # Optional category params: # :slug, :permissions, :auto_close_hours, :auto_close_based_on_last_post, :position, :email_in, # :email_in_allow_strangers, :logo_url, :background_url, :allow_badges, :topic_template ### # Create category new_category = client.create_category(name: "Test Category", color: "AB9364", text_color: "FFFFFF") puts "Created category: " + new_category.to_s # Update category response = client.update_category( id: new_category["id"], name: "The Best Test Category", slug: "the-best-test-category", color: "0E76BD", text_color: "000000", ) puts "Updated category: " + response.to_s ================================================ FILE: examples/create_user.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # create user user = client.create_user( name: "Bruce Wayne", email: "bruce@wayne.com", username: "batman", password: "WhySoSerious", ) # activate user client.activate(user["user_id"]) ================================================ FILE: examples/dashboard.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # get all dashboard status as json puts client.get_dashboard_stats # get hash of some dashboard total value puts client.get_dashboard_stats_totals # sample output: {"users"=>9, "topics"=>230, "posts"=>441} ================================================ FILE: examples/disposable_invite_tokens.rb ================================================ # frozen_string_literal: true # requires this plugin => https://github.com/discourse/discourse-invite-tokens require "csv" $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # fetch email-less invite tokens invite_tokens = client.disposable_tokens(username: "eviltrout", quantity: 5, group_names: "security,support") # write to CSV file CSV.open(File.expand_path("../invite_tokens.csv", __FILE__), "w") do |csv| invite_tokens.each { |value| csv << [value] } end ================================================ FILE: examples/example.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # get latest topics puts client.latest_topics ================================================ FILE: examples/group_set_user_notification_level.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" @target_username = "YOUR_TARGET_USERNAME" @target_group_id = # YOUR NUMERIC TARGET GROUP ID @user = client.user(@target_username) # each user's group and the group's default notification level are stored under user['groups'] @user["groups"].each do |group| if group["id"] == @target_group_id @group_name = group["name"] @default_level = group["default_notification_level"] end end # and the user's notification setting for each group is stored under user['group_users] @user["group_users"].each do |users_group| if users_group["group_id"] == @target_group_id @notification_level = users_group["notification_level"] puts "Group ID:#{@target_group_id} #{@group_name} Current Notification Level: #{@notification_level} Default: #{@default_level}" response = client.group_set_user_notification_level(@group_name, @user["id"], @default_level) puts response @users_group_users_after_update = client.user(@target_username)["group_users"] # this just pulls the user from the database again to make sure we updated the user's group notification level @users_group_users_after_update.each do |users_group_second_pass| if users_group_second_pass["group_id"] == @target_group_id puts "Updated ID:#{@target_group_id} #{@group_name} Notification Level: #{users_group_second_pass["notification_level"]} Default: #{@default_level}" end end end end ================================================ FILE: examples/groups.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" response = client.create_group(name: "engineering_team") group_id = response["basic_group"]["id"] client.group_add(group_id, username: "sam") client.group_add(group_id, username: "jeff") client.group_add(group_id, usernames: %w[neil dan]) client.group_add(group_id, user_id: 123) client.group_add(group_id, user_ids: [123, 456]) client.group_remove(group_id, username: "neil") client.group_remove(group_id, user_id: 123) client.delete_group(group_id) ## List users of a group members = client.group_members("trust_level_0") puts members ================================================ FILE: examples/invite_users.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # invite user invite = client.invite_user(email: "name@example.com", group_ids: "41,42") #update invite client.update_invite(invite["id"], email: "namee@example.com") # resend invite client.resend_invite("namee@example.com") # invite to a topic client.invite_to_topic(1, email: "foo@bar.com") # if the user is an admin you may invite to a group as well client.invite_to_topic(1, email: "foo@bar.com", group_ids: "1,2,3") # retrieve invite puts client.retrieve_invite(email: "foo@bar.com") # resend all invites client.resend_all_invites # destroy invite client.destroy_invite(invite["id"]) # destroy all expired invites client.destroy_all_expired_invites ================================================ FILE: examples/manage_api_keys.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # generate user api key response = client.create_api_key(key: { description: "Key to The Batmobile", username: "batman" }) api_key_id = response["key"]["id"] puts response # sample output: {"key"=>{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile"}} response = client.revoke_api_key(api_key_id) puts response # sample output: {"key"=>{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile", "revoked_at"=>"2021-01-01T00:00:00.000Z"}} response = client.undo_revoke_api_key(api_key_id) puts response # sample output: {"key"=>{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile", "revoked_at"=>nil}} response = client.list_api_keys puts response # sample output: {"keys"=>[{"id"=>13, "key"=>"abc", "description"=>"Key to the Batmobile"}]} response = client.delete_api_key(api_key_id) puts response # sample output: {"success"=>"OK"} ================================================ FILE: examples/notifications.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # watch an entire category client.category_set_user_notification_level(1, notification_level: 3) # mute a topic client.topic_set_user_notification_level(1, notification_level: 0) # get user notifications client.notifications(username: "discourse") ================================================ FILE: examples/polls.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" options = ["8b4736b1ae3dfb5a28088530f036f9e5"] poll_option_votes = client.poll_vote post_id: 5, poll_name: "poll", options: options puts poll_option_votes.body["vote"] poll_option_votes = client.toggle_poll_status(post_id: 5, poll_name: "poll", status: "closed") puts poll_option_votes[:body]["poll"]["status"] poll_option_votes = client.poll_voters(post_id: 5, poll_name: "poll") puts poll_option_votes["voters"] ================================================ FILE: examples/post_action.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # Post Action Type IDs # 1 - Bookmark # 2 - Like # 3 - Flag: Off-topic # 4 - Flag: Inappropriate # 5 - Vote # 6 - Notify User # 7 - Flag - Notify Moderators # 8 - Flag - Spam # Like a post client.create_post_action(id: 2, post_action_type_id: 2) # Flag a topic as spam client.create_topic_action(id: 1, post_action_type_id: 8) # Unlike a post client.destroy_post_action(id: 3, post_action_type_id: 2) ================================================ FILE: examples/search.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # search for term puts client.search("discourse") ================================================ FILE: examples/sent_private_messages.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" client.sent_private_messages("test_user") ================================================ FILE: examples/sso.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" client.sync_sso( sso_secret: "discourse_sso_rocks", name: "Test Name", username: "test_name", email: "name@example.com", external_id: "2", ) ================================================ FILE: examples/topic_lists.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # get latest topics puts client.latest_topics({}) # get top topics puts client.top_topics # recategorize topic puts client.recategorize_topic(topic_id: 108, category_id: 5) # get all categories puts client.categories({}) ================================================ FILE: examples/update_user.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # update username from "robin" to "batman" puts client.update_username("robin", "batman") # update name of user whose username is "batman" puts client.update_user("batman", name: "Bruce Wayne") # update email of user whose username is "batman" puts client.update_email("batman", "batman@gotham.com") # update avatar of user whose username is "batman" puts client.update_avatar( username: "batman", url: "http://meta-discourse.r.worldssl.net/uploads/default/2497/724a6ef2e79d2bc7.png", ) # update trust level of user whose id is "102" puts client.update_trust_level(user_id: 102, level: 2) # update user bio, location or website puts client.update_user( "batman", bio_raw: "I am Batman.", location: "Gotham", website: "https://en.wikipedia.org/wiki/Batman", ) # log out everywhere and refresh browser of user whose id is "2" puts client.log_out(2) ================================================ FILE: examples/upload_file.rb ================================================ # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require File.expand_path("../../lib/discourse_api", __FILE__) config = DiscourseApi::ExampleHelper.load_yml client = DiscourseApi::Client.new(config["host"] || "http://localhost:3000") client.api_key = config["api_key"] || "YOUR_API_KEY" client.api_username = config["api_username"] || "YOUR_USERNAME" # Upload a file file = Faraday::UploadIO.new("grumpy_cat.pdf", "application/pdf") client.upload_file(file: file) # Upload a file via URL client.upload_file(url: "https://giphy.com/grumpy_cat.gif") ================================================ FILE: lib/discourse_api/api/api_key.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module ApiKey def list_api_keys response = get("/admin/api/keys") response[:body] end def create_api_key(args) args = API.params(args).required(:key).to_h post("/admin/api/keys", args) end def revoke_api_key(id) post("/admin/api/keys/#{id}/revoke") end def undo_revoke_api_key(id) post("/admin/api/keys/#{id}/undo-revoke") end def delete_api_key(id) delete("/admin/api/keys/#{id}") end end end end ================================================ FILE: lib/discourse_api/api/backups.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Backups def backups response = get("/admin/backups.json") response.body end def create_backup post("/admin/backups", with_uploads: true) end def restore_backup(file_name) post("/admin/backups/#{file_name}/restore") end def download_backup(file_name, destination) response = get("/admin/backups/#{file_name}") # write file File.open("#{destination}/#{file_name}", "wb") { |fp| fp.write(response.body) } end end end end ================================================ FILE: lib/discourse_api/api/badges.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Badges def badges response = get("/admin/badges.json") response.body end def user_badges(username) response = get("/user-badges/#{username}.json") response.body["badges"] end def grant_user_badge(params = {}) post("/user_badges", params) end def create_badge(params = {}) args = API .params(params) .required(:name, :badge_type_id) .optional( :description, :allow_title, :multiple_grant, :icon, :listable, :target_posts, :query, :enabled, :auto_revoke, :badge_grouping_id, :trigger, :show_posts, :image, :long_description, ) post("/admin/badges.json", args) end end end end ================================================ FILE: lib/discourse_api/api/categories.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Categories # :color and :text_color are RGB hexadecimal strings # :permissions is a hash with the group name and permission_type which is # an integer 1 = Full 2 = Create Post 3 = Read Only def create_category(args = {}) params = common_category_params(args) response = post("/categories", params.to_h) response["category"] end def update_category(args = {}) category_id = args[:id] params = common_category_params(args, include_id: true) response = put("/categories/#{category_id}", params.to_h) response["body"]["category"] if response["body"] end def reorder_categories(args = {}) params = API.params(args).required(:mapping) post("/categories/reorder", params) end def delete_category(id) response = delete("/categories/#{id}") response[:body]["success"] end def categories(params = {}) categories_full(params)["category_list"]["categories"] end def categories_full(params = {}) response = get("/categories.json", params) response[:body] end def category_latest_topics(args = {}) response = category_latest_topics_full(args) if response["errors"] response["errors"] else response["topic_list"]["topics"] end end def category_latest_topics_full(args = {}) params = API.params(args).required(:category_slug).optional(:page).to_h url = "/c/#{params[:category_slug]}/l/latest.json" url = "#{url}?page=#{params[:page]}" if params.include?(:page) response = get(url) response[:body] end def category_top_topics(category_slug) response = category_top_topics_full(category_slug) if response["errors"] response["errors"] else response["topic_list"]["topics"] end end def category_top_topics_full(category_slug) response = get("/c/#{category_slug}/l/top.json") response[:body] end def category_new_topics(category_slug) response = category_new_topics_full(category_slug) response["topic_list"]["topics"] end def category_new_topics_full(category_slug) response = get("/c/#{category_slug}/l/new.json") response[:body] end def category(id) response = get("/c/#{id}/show") response[:body]["category"] end # TODO: Deprecated. Remove after 20210727 def category_set_user_notification(args = {}) category_id = args[:id] args = API.params(args).required(:notification_level) post("/category/#{category_id}/notifications", args) end def category_set_user_notification_level(category_id, params) params = API.params(params).required(:notification_level) post("/category/#{category_id}/notifications", params) end private def common_category_params(args, include_id: false) params = API.params(args) params = params.required(:id) if include_id params .required(:name) .optional( :color, :text_color, :slug, :permissions, :auto_close_hours, :auto_close_based_on_last_post, :position, :email_in, :email_in_allow_strangers, :logo_url, :background_url, :allow_badges, :topic_template, :custom_fields, :description, :reviewable_by_group_name, :show_subcategory_list, :subcategory_list_style, :allowed_tags, :allowed_tag_groups, :required_tag_group_name, :topic_featured_links_allowed, :search_priority, :form_template_ids, ) .default(parent_category_id: nil) end end end end ================================================ FILE: lib/discourse_api/api/dashboard.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Dashboard def get_dashboard_stats response = get("admin/dashboard.json") response[:body] end def get_dashboard_stats_totals stats = get_dashboard_stats global_reports = stats["global_reports"] users = global_reports[1] topics = global_reports[3] posts = global_reports[4] { "users" => users["total"], "topics" => topics["total"], "posts" => posts["total"] } end end end end ================================================ FILE: lib/discourse_api/api/email.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Email def email_settings response = get("/admin/email.json") response.body end def list_email(filter) response = get("/admin/email/#{filter}.json") response.body end end end end ================================================ FILE: lib/discourse_api/api/groups.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Groups def create_group(args) args = API .params(args) .required(:name) .default(visibility_level: 0) .optional( :mentionable_level, :messageable_level, :automatic_membership_email_domains, :automatic_membership_retroactive, :title, :primary_group, :grant_trust_level, :incoming_email, :flair_url, :flair_bg_color, :flair_color, :bio_raw, :members_visibility_level, :public_admission, :public_exit, :allow_membership_requests, :full_name, :default_notification_level, :usernames, :owner_usernames, :membership_request_template, ) .to_h post("/admin/groups", group: args) end def update_group(group_id, args) args = API .params(args) .default(visibility_level: 0) .optional( :mentionable_level, :messageable_level, :name, :automatic_membership_email_domains, :title, :primary_group, :grant_trust_level, :incoming_email, :flair_url, :flair_bg_color, :flair_color, :bio_raw, :visibility_level, :public_admission, :public_exit, :allow_membership_requests, :full_name, :default_notification_level, :membership_request_template, ) .to_h put("/groups/#{group_id}", group: args) end def group_add_owners(group_id, args) args = API.params(args).required(:usernames).to_h put("/admin/groups/#{group_id}/owners.json", group: args) end def group_remove_owners(group_id, args) args = API.params(args).required(:usernames).to_h delete("/admin/groups/#{group_id}/owners.json", group: args) end def groups(args = {}) params = API.params(args).optional(:page).to_h url = "/groups.json" url += "?page=#{params[:page]}" if params.include?(:page) response = get(url) response.body end def group(group_name) response = get("/groups/#{group_name}.json") response.body end def group_add(group_id, users) users.keys.each do |key| # Accept arrays and convert to comma-delimited string. users[key] = users[key].join(",") if users[key].respond_to? :join # Accept non-plural user_id or username, but send pluralized version in the request. if key.to_s[-1] != "s" users["#{key}s"] = users[key] users.delete(key) end end put("/admin/groups/#{group_id}/members.json", users) end def group_remove(group_id, users) users.keys.each do |key| # Accept arrays and convert to comma-delimited string. users[key] = users[key].join(",") if users[key].respond_to? :join # Accept non-plural user_id or username, but send pluralized version in the request. if key.to_s[-1] != "s" users["#{key}s"] = users[key] users.delete(key) end end delete("/admin/groups/#{group_id}/members.json", users) end def delete_group(group_id) delete("/admin/groups/#{group_id}.json") end def group_members(group_name, params = {}) options = params params = API.params(params).optional(:offset, :limit).default(offset: 0, limit: 100).to_h response = get("/groups/#{group_name}/members.json", params) if options[:all] == true response.body else response.body["members"] end end def group_set_user_notification_level(group, user_id, notification_level) post( "/groups/#{group}/notifications?user_id=#{user_id}¬ification_level=#{notification_level}", ) end end end end ================================================ FILE: lib/discourse_api/api/invite.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Invite def invite_user(params = {}) args = API .params(params) .optional( :email, :skip_email, :custom_message, :max_redemptions_allowed, :topic_id, :group_ids, :expires_at, ) .to_h post("/invites", args) end # TODO: Deprecated. Remove after 20220506 def invite_user_to_topic(params = {}) deprecated(__method__, "invite_to_topic") invite_to_topic(params[:topic_id], params) end def invite_to_topic(topic_id, params = {}) args = API.params(params).optional(:email, :user, :group_ids, :custom_message).to_h post("/t/#{topic_id}/invite", args) end def retrieve_invite(params = {}) args = API.params(params).required(:email).to_h response = get("invites/retrieve.json", args) response.body end # requires this plugin => https://github.com/discourse/discourse-invite-tokens def disposable_tokens(params = {}) post("/invite-token/generate", params) end def update_invite(invite_id, params = {}) args = API .params(params) .optional( :topic_id, :group_ids, :group_names, :email, :send_email, :custom_message, :max_redemptions_allowed, :expires_at, ) .to_h put("invites/#{invite_id}", args) end def destroy_all_expired_invites post("invites/destroy-all-expired") end def resend_all_invites post("invites/reinvite-all") end def resend_invite(email) post("invites/reinvite", { email: email }) end def destroy_invite(invite_id) delete("/invites", { id: invite_id }) end end end end ================================================ FILE: lib/discourse_api/api/notifications.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Notifications def notifications(params = {}) params = API.params(params).optional(:username, :recent, :limit, :offset, :filter) response = get("/notifications.json", params) response[:body] end end end end ================================================ FILE: lib/discourse_api/api/params.rb ================================================ # frozen_string_literal: true module DiscourseApi module API def self.params(args) Params.new(args) end class Params def initialize(args) raise ArgumentError.new("Required to be initialized with a Hash") unless args.is_a? Hash @args = args @required = [] @optional = [] @defaults = {} end def required(*keys) @required.concat(keys) @required.each do |k| raise ArgumentError.new("#{k} is required but not specified") unless @args.key?(k) end self end def optional(*keys) @optional.concat(keys) self end def default(args) args.each { |k, v| @defaults[k] = v } self end def to_h h = {} @required.each { |k| h[k] = @args[k] } @optional.each { |k| h[k] = @args[k] if @args.include?(k) } @defaults.each { |k, v| @args.key?(k) ? h[k] = @args[k] : h[k] = v } h end end end end ================================================ FILE: lib/discourse_api/api/polls.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Polls def poll_vote(args) args = API.params(args).required(:post_id, :poll_name, :options).optional(:created_at) put("/polls/vote", args) end def toggle_poll_status(args) args = API .params(args) .required(:post_id, :poll_name, :status) .optional(:api_username) .optional(:raise_errors) put("/polls/toggle_status", args) end def poll_voters(args) args = API.params(args).required(:post_id, :poll_name).optional(:opts) response = get("/polls/voters.json", args) response[:body] end end end end ================================================ FILE: lib/discourse_api/api/posts.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Posts def create_post(args) args = API.params(args).required(:topic_id, :raw).optional(:created_at, :api_username) post("/posts", args) end def create_post_action(args) args = API.params(args).required(:id, :post_action_type_id) post("/post_actions", args.to_h.merge(flag_topic: false)) end def get_post(id, args = {}) args = API.params(args).optional(:version) response = get("/posts/#{id}.json", args) response[:body] end def posts(args = {}) args = API.params(args).default(before: 0) response = get("/posts.json", args) response[:body] end def wikify_post(id) put("/posts/#{id}/wiki", wiki: true) end def edit_post(id, raw) put("/posts/#{id}", post: { raw: raw }) end def delete_post(id) delete("/posts/#{id}.json") end def destroy_post_action(post_id, post_action_type_id) delete("/post_actions/#{post_id}.json", post_action_type_id: post_action_type_id) end def post_action_users(post_id, post_action_type_id) response = get("/post_action_users.json", id: post_id, post_action_type_id: post_action_type_id) response[:body] end end end end ================================================ FILE: lib/discourse_api/api/private_messages.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module PrivateMessages # TODO: Deprecated. Remove after 20220628 def create_private_message(args = {}) deprecated(__method__, "create_pm") args[:target_recipients] = args.delete :target_usernames create_pm(args.to_h) end # :target_recipients REQUIRED comma separated list of usernames # :category OPTIONAL name of category, not ID # :created_at OPTIONAL seconds since epoch. def create_pm(args = {}) args[:archetype] = "private_message" args = API .params(args) .required(:title, :raw, :target_recipients, :archetype) .optional(:category, :created_at, :api_username) post("/posts", args.to_h) end def private_messages(username, *args) response = get("topics/private-messages/#{username}.json", args) response[:body]["topic_list"]["topics"] end def sent_private_messages(username, *args) response = get("topics/private-messages-sent/#{username}.json", args) response[:body]["topic_list"]["topics"] end end end end ================================================ FILE: lib/discourse_api/api/search.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Search # Returns search results that match the specified term. # # @param term [String] a search term # @param options [Hash] A customizable set of options # @option options [String] :type_filter Returns results of the specified type. # @return [Array] Return results as an array of Hashes. def search(term, options = {}) raise ArgumentError.new("#{term} is required but not specified") unless term raise ArgumentError.new("#{term} is required but not specified") if term.empty? response = get("/search", options.merge(q: term)) response[:body] end end end end ================================================ FILE: lib/discourse_api/api/site_settings.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module SiteSettings def site_setting_update(args = {}) params = API.params(args).required(:name, :value).to_h new_site_setting = { params[:name] => params[:value] } put("/admin/site_settings/#{params[:name]}", new_site_setting) end end end end ================================================ FILE: lib/discourse_api/api/sso.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module SSO def sync_sso(params = {}) sso = DiscourseApi::SingleSignOn.parse_hash(params) post("/admin/users/sync_sso", sso.payload) end end end end ================================================ FILE: lib/discourse_api/api/tags.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Tags def show_tag(tag) response = get("/tag/#{tag}") response[:body] end end end end ================================================ FILE: lib/discourse_api/api/topics.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Topics # :category OPTIONAL name of category, not ID # :skip_validations OPTIONAL boolean # :auto_track OPTIONAL boolean # :created_at OPTIONAL seconds since epoch. def create_topic(args = {}) args = API .params(args) .required(:title, :raw) .optional( :skip_validations, :category, :auto_track, :created_at, :api_username, :tags, :external_id, :visible, ) post("/posts", args.to_h) end def create_topic_action(args) args = API.params(args).required(:id, :post_action_type_id) post("/post_actions", args.to_h.merge(flag_topic: true)) end # timestamp is seconds past the epoch. def edit_topic_timestamp(topic_id, timestamp) put("/t/#{topic_id}/change-timestamp", timestamp: timestamp) end def latest_topics(params = {}) response = get("/latest.json", params) response[:body]["topic_list"]["topics"] end def top_topics(params = {}) response = get("/top.json", params) response[:body]["topic_list"]["topics"] end def new_topics(params = {}) response = get("/new.json", params) response[:body]["topic_list"]["topics"] end def rename_topic(topic_id, title) put("/t/#{topic_id}.json", topic_id: topic_id, title: title) end def recategorize_topic(topic_id, category_id) put("/t/#{topic_id}.json", topic_id: topic_id, category_id: category_id) end # TODO: Deprecated. Remove after 20201231 def change_topic_status(topic_slug, topic_id, params = {}) deprecated(__method__, "update_topic_status") update_topic_status(topic_id, params) end def update_topic_status(topic_id, params = {}) params = API.params(params).required(:status, :enabled).optional(:api_username) put("/t/#{topic_id}/status", params) end def topic(id, params = {}) response = get("/t/#{id}.json", params) response[:body] end def topics_by(username, params = {}) response = get("/topics/created-by/#{username}.json", params) response[:body]["topic_list"]["topics"] end def delete_topic(id) delete("/t/#{id}.json") end def topic_posts(topic_id, post_ids = [], params = {}) params = API.params(params).optional( :asc, :filter, :include_raw, :include_suggested, :post_number, :username_filters, ) url = ["/t/#{topic_id}/posts.json"] if post_ids.count > 0 url.push("?") url.push(post_ids.map { |id| "post_ids[]=#{id}" }.join("&")) end response = get(url.join, params) response[:body] end def change_owner(topic_id, params = {}) params = API.params(params).required(:username, :post_ids) post("/t/#{topic_id}/change-owner.json", params) end def topic_set_user_notification_level(topic_id, params) params = API.params(params).required(:notification_level) post("/t/#{topic_id}/notifications", params) end def bookmark_topic(topic_id) put("/t/#{topic_id}/bookmark.json") end def remove_topic_bookmark(topic_id) put("/t/#{topic_id}/remove_bookmarks.json") end def get_topic_url_by_external_id(external_id) get("/t/external_id/#{external_id}") end end end end ================================================ FILE: lib/discourse_api/api/uploads.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Uploads def upload_file(args) args = API .params(args) .optional(:file, :url, :user_id) .default(type: "composer", synchronous: true) .to_h post("/uploads", args) end end end end ================================================ FILE: lib/discourse_api/api/user_actions.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module UserActions def user_replies(username) params = { username: username, filter: "5" } response = get("/user_actions.json", params) response.body["user_actions"] end def user_topics_and_replies(username) params = { username: username, filter: "4,5" } response = get("/user_actions.json", params) response.body["user_actions"] end end end end ================================================ FILE: lib/discourse_api/api/users.rb ================================================ # frozen_string_literal: true module DiscourseApi module API module Users def activate(id) put("/admin/users/#{id}/activate") end def user(username, params = {}) response = get("/users/#{username}.json", params) response[:body]["user"] end def user_sso(user_id) response = get("/admin/users/#{user_id}.json") response[:body]["single_sign_on_record"] end def update_avatar(username, args) args = API.params(args).optional(:file, :url).default(type: "avatar", synchronous: true).to_h upload_response = post("/uploads", args) put("/u/#{username}/preferences/avatar/pick", upload_id: upload_response["id"]) end def update_email(username, email) put("/u/#{username}/preferences/email", email: email) end def update_user(username, args) args = API .params(args) .optional( :name, :title, :bio_raw, :location, :website, :profile_background, :card_background, :email_messages_level, :mailing_list_mode, :homepage_id, :theme_ids, :user_fields, ) .to_h put("/u/#{username}", args) end def update_username(username, new_username) put("/u/#{username}/preferences/username", new_username: new_username) end def update_trust_level(user_id, args) args = API.params(args).required(:level).to_h response = put("/admin/users/#{user_id}/trust_level", args) response[:body] end def create_user(args) args = API .params(args) .required(:name, :email, :password, :username) .optional(:active, :approved, :staged, :user_fields) .to_h post("/users", args) end def log_out(id) post("/admin/users/#{id}/log_out") end def list_users(type, params = {}) response = get("admin/users/list/#{type}.json", params) response[:body] end def grant_admin(user_id) response = put("admin/users/#{user_id}/grant_admin") response[:body] end def revoke_admin(user_id) response = put("admin/users/#{user_id}/revoke_admin") response[:body] end def grant_moderation(user_id) response = put("admin/users/#{user_id}/grant_moderation") response[:body] end def revoke_moderation(user_id) put("admin/users/#{user_id}/revoke_moderation") end def by_external_id(external_id) response = get("/users/by-external/#{external_id}") response[:body]["user"] end def suspend(user_id, suspend_until, reason) put("/admin/users/#{user_id}/suspend", suspend_until: suspend_until, reason: reason) end def unsuspend(user_id) put("/admin/users/#{user_id}/unsuspend") end def anonymize(user_id) put("/admin/users/#{user_id}/anonymize") end def delete_user(user_id, delete_posts = false) delete("/admin/users/#{user_id}.json?delete_posts=#{delete_posts}") end def check_username(username) response = get("/users/check_username.json?username=#{CGI.escape(username)}") response[:body] end def deactivate(user_id) put("/admin/users/#{user_id}/deactivate") end end end end ================================================ FILE: lib/discourse_api/client.rb ================================================ # frozen_string_literal: true require "faraday" require "faraday/follow_redirects" require "faraday/multipart" require "json" require "uri" require "discourse_api/version" require "discourse_api/api/categories" require "discourse_api/api/search" require "discourse_api/api/sso" require "discourse_api/api/tags" require "discourse_api/api/topics" require "discourse_api/api/polls" require "discourse_api/api/posts" require "discourse_api/api/users" require "discourse_api/api/groups" require "discourse_api/api/invite" require "discourse_api/api/private_messages" require "discourse_api/api/notifications" require "discourse_api/api/badges" require "discourse_api/api/email" require "discourse_api/api/api_key" require "discourse_api/api/backups" require "discourse_api/api/dashboard" require "discourse_api/api/uploads" require "discourse_api/api/user_actions" require "discourse_api/api/site_settings" module DiscourseApi class Client attr_accessor :api_key attr_accessor :basic_auth attr_reader :host, :api_username, :timeout DEFAULT_TIMEOUT = 30 include DiscourseApi::API::Categories include DiscourseApi::API::Search include DiscourseApi::API::SSO include DiscourseApi::API::Tags include DiscourseApi::API::Topics include DiscourseApi::API::Polls include DiscourseApi::API::Posts include DiscourseApi::API::Users include DiscourseApi::API::Groups include DiscourseApi::API::Invite include DiscourseApi::API::PrivateMessages include DiscourseApi::API::Notifications include DiscourseApi::API::Badges include DiscourseApi::API::Email include DiscourseApi::API::ApiKey include DiscourseApi::API::Backups include DiscourseApi::API::Dashboard include DiscourseApi::API::Uploads include DiscourseApi::API::UserActions include DiscourseApi::API::SiteSettings def initialize(host, api_key = nil, api_username = nil) raise ArgumentError, "host needs to be defined" if host.nil? || host.empty? @host = host @api_key = api_key @api_username = api_username @use_relative = check_subdirectory(host) end def timeout=(timeout) @timeout = timeout @connection.options.timeout = timeout if @connection end def api_username=(api_username) @api_username = api_username @connection.headers["Api-Username"] = api_username unless @connection.nil? end def connection_options @connection_options ||= { url: @host, request: { timeout: @timeout || DEFAULT_TIMEOUT, }, headers: { accept: "application/json", user_agent: user_agent, }, } end def ssl(options) connection_options[:ssl] = options end def delete(path, params = {}) request(:delete, path, params) end def get(path, params = {}) request(:get, path, params) end def post(path, params = {}) response = request(:post, path, params) case response.status when 200, 201, 204 response.body else raise DiscourseApi::Error, response.body end end def put(path, params = {}) request(:put, path, params) end def patch(path, params = {}) request(:patch, path, params) end def user_agent @user_agent ||= "DiscourseAPI Ruby Gem #{DiscourseApi::VERSION}" end def deprecated(old, new) warn "[DEPRECATED]: `#{old}` is deprecated. Please use `#{new}` instead." end private def connection @connection ||= Faraday.new connection_options do |conn| # Allow uploading of files conn.request :multipart # Convert request params to "www-form-encoded" conn.request :url_encoded # Allow to interact with forums behind basic HTTP authentication if basic_auth conn.request :authorization, :basic, basic_auth[:user], basic_auth[:password] end # Follow redirects conn.response :follow_redirects, limit: 5 # Parse responses as JSON conn.response :json, content_type: "application/json" # For HTTP debugging, uncomment # conn.response :logger # Use Faraday's default HTTP adapter conn.adapter Faraday.default_adapter # Pass api_key and api_username on every request unless api_username.nil? conn.headers["Api-Key"] = api_key conn.headers["Api-Username"] = api_username end end end def request(method, path, params = {}) unless Hash === params params = params.to_h if params.respond_to? :to_h end path = @use_relative ? path.sub(%r{^/}, "") : path response = connection.send(method.to_sym, path, params) handle_error(response) response.env rescue Faraday::ClientError, JSON::ParserError raise DiscourseApi::Error rescue Faraday::ConnectionFailed raise DiscourseApi::Timeout end def handle_error(response) case response.status when 403 raise DiscourseApi::UnauthenticatedError.new(response.env[:body], response.env) when 404, 410 raise DiscourseApi::NotFoundError.new(response.env[:body], response.env) when 422 raise DiscourseApi::UnprocessableEntity.new(response.env[:body], response.env) when 429 raise DiscourseApi::TooManyRequests.new(response.env[:body], response.env) when 500...600 raise DiscourseApi::Error.new(response.env[:body]) end end def check_subdirectory(host) URI(host).request_uri != "/" end end end ================================================ FILE: lib/discourse_api/error.rb ================================================ # frozen_string_literal: true module DiscourseApi class DiscourseError < StandardError attr_reader :response def initialize(message, response = nil) super(message) @response = response end end class Error < DiscourseError attr_reader :wrapped_exception # Initializes a new Error object # # @param exception [Exception, String] # @return [DiscourseApi::Error] def initialize(exception = $!) @wrapped_exception = exception exception.respond_to?(:message) ? super(exception.message) : super(exception.to_s) end end class UnauthenticatedError < DiscourseError end class NotFoundError < DiscourseError end class UnprocessableEntity < DiscourseError end class TooManyRequests < DiscourseError end class Timeout < Error end end ================================================ FILE: lib/discourse_api/example_helper.rb ================================================ # frozen_string_literal: true require "yaml" module DiscourseApi class ExampleHelper def self.load_yml config_yml = File.expand_path("../../../config.yml", __FILE__) puts config_yml begin config = YAML.load_file config_yml rescue Errno::ENOENT config = {} end config end end end ================================================ FILE: lib/discourse_api/single_sign_on.rb ================================================ # frozen_string_literal: true require "base64" require "rack" require "openssl" module DiscourseApi class SingleSignOn class ParseError < RuntimeError end class MissingConfigError < RuntimeError end ACCESSORS = %i[ add_groups admin avatar_force_update avatar_url bio card_background_url confirmed_2fa email external_id groups locale locale_force_update moderator name no_2fa_methods nonce profile_background_url remove_groups require_2fa require_activation return_sso_url suppress_welcome_message title username ] FIXNUMS = [] BOOLS = %i[ admin avatar_force_update confirmed_2fa locale_force_update moderator no_2fa_methods require_2fa require_activation suppress_welcome_message ] ARRAYS = [:groups] #NONCE_EXPIRY_TIME = 10.minutes # minutes is a rails method and is causing an error. Is this needed in the api? attr_accessor(*ACCESSORS) attr_writer :custom_fields, :sso_secret, :sso_url def self.sso_secret raise MissingConfigError, "sso_secret not implemented on class, be sure to set it on instance" end def self.sso_url raise MissingConfigError, "sso_url not implemented on class, be sure to set it on instance" end def self.parse_hash(payload) sso = new sso.sso_secret = payload.delete(:sso_secret) sso.sso_url = payload.delete(:sso_url) ACCESSORS.each do |k| val = payload[k] val = val.to_i if FIXNUMS.include? k val = %w[true false].include?(val) ? val == "true" : nil if BOOLS.include? k val = val.split(",") if ARRAYS.include?(k) && !val.nil? sso.send("#{k}=", val) end # Set custom_fields sso.custom_fields = payload[:custom_fields] # Add custom_fields (old format) payload.each do |k, v| if field = k[/^custom\.(.+)$/, 1] # Maintain adding of .custom bug sso.custom_fields["custom.#{field}"] = v end end sso end def self.parse(payload, sso_secret = nil) sso = new sso.sso_secret = sso_secret if sso_secret parsed = Rack::Utils.parse_query(payload) if parsed["sso"].nil? || sso.sign(parsed["sso"]) != parsed["sig"] diags = "\n\nsso: #{parsed["sso"].inspect}\n\nsig: #{parsed["sig"].inspect}\n\nexpected sig: #{sso.sign(parsed.fetch("sso", ""))}" if parsed["sso"].nil? || parsed["sso"] =~ %r{[^a-zA-Z0-9=\r\n/+]}m raise ParseError, "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}" else raise ParseError, "Bad signature for payload #{diags}" end end decoded = Base64.decode64(parsed["sso"]) decoded_hash = Rack::Utils.parse_query(decoded) ACCESSORS.each do |k| val = decoded_hash[k.to_s] val = val.to_i if FIXNUMS.include? k val = %w[true false].include?(val) ? val == "true" : nil if BOOLS.include? k val = val.split(",") if ARRAYS.include?(k) && !val.nil? sso.send("#{k}=", val) end decoded_hash.each do |k, v| if field = k[/^custom\.(.+)$/, 1] sso.custom_fields[field] = v end end sso end def diagnostics DiscourseApi::SingleSignOn::ACCESSORS.map { |a| "#{a}: #{send(a)}" }.join("\n") end def sso_secret @sso_secret || self.class.sso_secret end def sso_url @sso_url || self.class.sso_url end def custom_fields @custom_fields ||= {} end def sign(payload) OpenSSL::HMAC.hexdigest("sha256", sso_secret, payload) end def to_url(base_url = nil) base = "#{base_url || sso_url}" "#{base}#{base.include?("?") ? "&" : "?"}#{payload}" end def payload payload = Base64.strict_encode64(unsigned_payload) "sso=#{CGI.escape(payload)}&sig=#{sign(payload)}" end def unsigned_payload payload = {} ACCESSORS.each do |k| next if (val = send k) == nil payload[k] = val end @custom_fields.each { |k, v| payload["custom.#{k}"] = v.to_s } if @custom_fields Rack::Utils.build_query(payload) end end end ================================================ FILE: lib/discourse_api/version.rb ================================================ # frozen_string_literal: true module DiscourseApi VERSION = "2.1.0" end ================================================ FILE: lib/discourse_api.rb ================================================ # frozen_string_literal: true require "discourse_api/api/params" require "discourse_api/client" require "discourse_api/error" require "discourse_api/version" require "discourse_api/example_helper" require "discourse_api/single_sign_on" ================================================ FILE: spec/discourse_api/api/api_key_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::ApiKey do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#list_api_keys" do before do url = "#{host}/admin/api/keys" stub_get(url).to_return( body: fixture("list_api_keys.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.list_api_keys url = "#{host}/admin/api/keys" expect(a_get(url)).to have_been_made end it "returns the requested api keys" do keys = client.list_api_keys expect(keys["keys"]).to be_an Array expect(keys["keys"].first).to be_a Hash expect(keys["keys"].first).to have_key("key") end end describe "#create_api_key" do before do url = "#{host}/admin/api/keys" stub_post(url).to_return( body: fixture("api_key.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.create_api_key(key: { username: "robin" }) url = "#{host}/admin/api/keys" expect(a_post(url)).to have_been_made end it "returns the generated api key" do api_key = client.create_api_key(key: { username: "robin" }) expect(api_key).to be_a Hash expect(api_key["key"]).to have_key("key") end end describe "#revoke_api_key" do before do url = "#{host}/admin/api/keys/10/revoke" stub_post(url).to_return( body: fixture("api_key.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.revoke_api_key(10) url = "#{host}/admin/api/keys/10/revoke" expect(a_post(url)).to have_been_made end it "returns the api key" do api_key = client.revoke_api_key(10) expect(api_key["key"]).to have_key("key") end end describe "#undo_revoke_api_key" do before do url = "#{host}/admin/api/keys/10/undo-revoke" stub_post(url).to_return( body: fixture("api_key.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.undo_revoke_api_key(10) url = "#{host}/admin/api/keys/10/undo-revoke" expect(a_post(url)).to have_been_made end it "returns the api key" do api_key = client.undo_revoke_api_key(10) expect(api_key["key"]).to have_key("key") end end describe "#delete_api_key" do before do url = "#{host}/admin/api/keys/10" stub_delete(url).to_return( body: '{"success": "OK"}', headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.delete_api_key(10) url = "#{host}/admin/api/keys/10" expect(a_delete(url)).to have_been_made end it "returns 200" do response = client.delete_api_key(10) expect(response["status"]).to eq(200) end end end ================================================ FILE: spec/discourse_api/api/backups_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Backups do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#backups" do before do stub_get("#{host}/admin/backups.json").to_return( body: fixture("backups.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.backups expect(a_get("#{host}/admin/backups.json")).to have_been_made end it "returns the requested backups" do backups = client.backups expect(backups).to be_an Array expect(backups.first).to be_a Hash expect(backups.first).to have_key("filename") end end end ================================================ FILE: spec/discourse_api/api/badges_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Badges do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#badges" do before do stub_get("#{host}/admin/badges.json").to_return( body: fixture("badges.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.badges expect(a_get("#{host}/admin/badges.json")).to have_been_made end it "returns the requested badges" do badges = client.badges expect(badges).to be_a Hash expect(badges["badges"]).to be_an Array end end describe "#user-badges" do before do stub_get("#{host}/user-badges/test_user.json").to_return( body: fixture("user_badges.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.user_badges("test_user") expect(a_get("#{host}/user-badges/test_user.json")).to have_been_made end it "returns the requested user badges" do badges = client.user_badges("test_user") expect(badges).to be_an Array expect(badges.first).to be_a Hash expect(badges.first).to have_key("badge_type_id") end end end ================================================ FILE: spec/discourse_api/api/categories_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Categories do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#categories" do before do stub_get("#{host}/categories.json").to_return( body: fixture("categories.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.categories expect(a_get("#{host}/categories.json")).to have_been_made end it "returns the requested categories" do categories = client.categories expect(categories).to be_an Array expect(categories.first).to be_a Hash end it "returns the requested categories with hash arg" do categories = client.categories({}) expect(categories).to be_an Array expect(categories.first).to be_a Hash end end describe "#categories_full" do before do stub_get("#{host}/categories.json").to_return( body: fixture("categories.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.categories expect(a_get("#{host}/categories.json")).to have_been_made end it "returns the entire categories response" do categories = client.categories_full expect(categories).to be_a Hash expect(categories).to have_key "category_list" expect(categories).to have_key "featured_users" end end describe "#category_latest_topics" do before do stub_get("#{host}/c/category-slug/l/latest.json").to_return( body: fixture("category_latest_topics.json"), headers: { content_type: "application/json", }, ) end it "returns the latest topics in a category" do latest_topics = client.category_latest_topics(category_slug: "category-slug") expect(latest_topics).to be_an Array end end describe "#category_latest_topics_full" do before do stub_get("#{host}/c/category-slug/l/latest.json").to_return( body: fixture("category_latest_topics.json"), headers: { content_type: "application/json", }, ) end it "returns the entire latest topics in a category response" do latest_topics = client.category_latest_topics_full(category_slug: "category-slug") expect(latest_topics).to be_a Hash expect(latest_topics).to have_key "topic_list" expect(latest_topics).to have_key "users" end end describe "#category_top_topics" do before do stub_get("#{host}/c/category-slug/l/top.json").to_return( body: fixture("category_topics.json"), headers: { content_type: "application/json", }, ) end it "returns the top topics in a category" do topics = client.category_top_topics("category-slug") expect(topics).to be_an Array end end describe "#category_top_topics_full" do before do stub_get("#{host}/c/category-slug/l/top.json").to_return( body: fixture("category_topics.json"), headers: { content_type: "application/json", }, ) end it "returns the entire top topics in a category response" do topics = client.category_top_topics_full("category-slug") expect(topics).to be_a Hash expect(topics).to have_key "topic_list" expect(topics).to have_key "users" end end describe "#category_new_topics" do before do stub_get("#{host}/c/category-slug/l/new.json").to_return( body: fixture("category_topics.json"), headers: { content_type: "application/json", }, ) end it "returns the new topics in a category" do topics = client.category_new_topics("category-slug") expect(topics).to be_an Array end end describe "#category_new_topics_full" do before do stub_get("#{host}/c/category-slug/l/new.json").to_return( body: fixture("category_topics.json"), headers: { content_type: "application/json", }, ) end it "returns the new topics in a category" do topics = client.category_new_topics_full("category-slug") expect(topics).to be_a Hash expect(topics).to have_key "topic_list" expect(topics).to have_key "users" end end describe "#category_new_category" do before do stub_post("#{host}/categories") client.create_category( name: "test_category", color: "283890", text_color: "FFFFFF", description: "This is a description", permissions: { "group_1" => 1, "admins" => 1, }, ) end it "makes a create category request" do expect( a_post("#{host}/categories").with( body: "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", ), ).to have_been_made end end describe "#reorder_categories" do before do stub_post("#{host}/categories/reorder").to_return( body: fixture("notification_success.json"), headers: { content_type: "application/json", }, ) end it "makes a categories reordering request" do payload = { "1": 2, "2": 3, "3": 4, "4": 1 } response = client.reorder_categories(mapping: payload.to_json) expect( a_post("#{host}/categories/reorder").with( body: "mapping=#{CGI.escape(payload.to_json.to_s)}", ), ).to have_been_made expect(response["success"]).to eq("OK") end end describe "#category_set_user_notification" do before do stub_post("#{host}/category/1/notifications").to_return( body: fixture("notification_success.json"), headers: { content_type: "application/json", }, ) end it "makes the post request" do response = client.category_set_user_notification(id: 1, notification_level: 3) expect(a_post("#{host}/category/1/notifications")).to have_been_made expect(response["success"]).to eq("OK") end end describe "#category_set_user_notification_level" do before do stub_post("#{host}/category/1/notifications").to_return( body: fixture("notification_success.json"), headers: { content_type: "application/json", }, ) end it "makes the post request" do response = client.category_set_user_notification_level(1, notification_level: 3) expect( a_post("#{host}/category/1/notifications").with(body: "notification_level=3"), ).to have_been_made expect(response["success"]).to eq("OK") end end end ================================================ FILE: spec/discourse_api/api/email_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Email do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#email_settings" do before do stub_get("#{host}/admin/email.json").to_return( body: fixture("email_settings.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.email_settings expect(a_get("#{host}/admin/email.json")).to have_been_made end it "returns the requested settings" do settings = client.email_settings expect(settings).to be_a Hash expect(settings).to have_key("delivery_method") expect(settings).to have_key("settings") end end describe "#list_email_all" do before do stub_get("#{host}/admin/email/all.json").to_return( body: fixture("email_list_all.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.list_email("all") expect(a_get("#{host}/admin/email/all.json")).to have_been_made end it "returns all email" do all_email = client.list_email("all") expect(all_email).to be_an Array end end end ================================================ FILE: spec/discourse_api/api/groups_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Groups do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#groups" do before do stub_get("#{host}/groups.json").to_return( body: fixture("groups.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.groups expect(a_get("#{host}/groups.json")).to have_been_made end it "returns the requested groups" do groups = client.groups expect(groups).to be_an Array groups.each { |g| expect(g).to be_a Hash } end it "returns a single group" do stub_get("#{host}/groups/some-group.json").to_return( body: fixture("group.json"), headers: { content_type: "application/json", }, ) group = client.group("some-group") expect(group["basic_group"]).to be_a Hash end it "create new groups" do stub_post("#{host}/admin/groups") client.create_group(name: "test_group") params = escape_params("group[name]" => "test_group", "group[visibility_level]" => 0) expect(a_post("#{host}/admin/groups").with(body: params)).to have_been_made end it "update an existing group" do stub_put("#{host}/groups/42") client.update_group(42, name: "test_group") params = escape_params("group[name]" => "test_group", "group[visibility_level]" => 0) expect(a_put("#{host}/groups/42").with(body: params)).to have_been_made end describe "add members" do before { stub_request(:put, "#{host}/admin/groups/123/members.json") } it "adds a single member by username" do client.group_add(123, username: "sam") expect( a_request(:put, "#{host}/admin/groups/123/members.json").with(body: { usernames: "sam" }), ).to have_been_made end it "adds an array of members by username" do client.group_add(123, usernames: %w[sam jeff]) expect( a_request(:put, "#{host}/admin/groups/123/members.json").with( body: { usernames: "sam,jeff", }, ), ).to have_been_made end it "adds a single member by user_id" do client.group_add(123, user_id: 456) expect( a_request(:put, "#{host}/admin/groups/123/members.json").with(body: { user_ids: "456" }), ).to have_been_made end it "adds an array of members by user_id" do client.group_add(123, user_id: [123, 456]) expect( a_request(:put, "#{host}/admin/groups/123/members.json").with( body: { user_ids: "123,456", }, ), ).to have_been_made end end describe "remove members" do let(:url) { "#{host}/admin/groups/123/members.json?usernames=sam" } before { stub_delete(url) } it "removes member" do client.group_remove(123, username: "sam") expect(a_delete(url)).to have_been_made end end describe "add owners" do let(:url) { "#{host}/admin/groups/123/owners.json" } before { stub_put(url) } it "makes the member an owner" do client.group_add_owners(123, usernames: "sam") params = escape_params("group[usernames]" => "sam") expect( a_request(:put, "#{host}/admin/groups/123/owners.json").with(body: params), ).to have_been_made end end describe "remove owners" do let(:url) { "#{host}/admin/groups/123/owners.json?group%5Busernames%5D=sam" } before { stub_delete(url) } it "removes the owner role from the group member" do client.group_remove_owners(123, usernames: "sam") expect(a_delete(url)).to have_been_made end end describe "group members" do it "list members" do stub_get("#{host}/groups/mygroup/members.json?limit=100&offset=0").to_return( body: fixture("members_0.json"), headers: { content_type: "application/json", }, ) stub_get("#{host}/groups/mygroup/members.json?limit=100&offset=100").to_return( body: fixture("members_1.json"), headers: { content_type: "application/json", }, ) members = client.group_members("mygroup") expect(a_get("#{host}/groups/mygroup/members.json?limit=100&offset=0")).to have_been_made expect(members.length).to eq(100) members = client.group_members("mygroup", offset: 100) expect(a_get("#{host}/groups/mygroup/members.json?limit=100&offset=100")).to have_been_made expect(members.length).to eq(90) end context "with :all params" do it "lists members and owners" do stub_get("#{host}/groups/mygroup/members.json?limit=100&offset=0").to_return( body: fixture("members_2.json"), headers: { content_type: "application/json", }, ) member_data = client.group_members("mygroup", all: true) expect(a_get("#{host}/groups/mygroup/members.json?limit=100&offset=0")).to have_been_made expect(member_data["members"].length).to eq(100) expect(member_data["owners"].length).to eq(7) expect(member_data.keys.sort).to eq(%w[members meta owners]) end end end describe "group user notification level" do before { stub_post("#{host}/groups/mygroup/notifications?user_id=77¬ification_level=3") } it "updates user's notification level for group" do client.group_set_user_notification_level("mygroup", 77, 3) expect( a_post("#{host}/groups/mygroup/notifications?user_id=77¬ification_level=3"), ).to have_been_made end end end end ================================================ FILE: spec/discourse_api/api/invite_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Invite do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#invite_user" do before do stub_post("#{host}/invites").to_return( body: fixture("topic_invite_user.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.invite_user(email: "fake_user@example.com", group_ids: "41,42") expect(a_post("#{host}/invites")).to have_been_made end it "returns success" do response = client.invite_user(email: "fake_user@example.com", group_ids: "41,42") expect(response).to be_a Hash expect(response["success"]).to be_truthy end end describe "#update_invite" do before do stub_put("#{host}/invites/27").to_return( body: fixture("topic_invite_user.json"), headers: { content_type: "application/json", }, ) end it "updates invite" do client.update_invite(27, email: "namee@example.com") expect(a_put("#{host}/invites/27")).to have_been_made end end describe "#retrieve_invite" do before do stub_get("#{host}/invites/retrieve.json?email=foo@bar.com").to_return( body: fixture("retrieve_invite.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.retrieve_invite(email: "foo@bar.com") expect(a_get("#{host}/invites/retrieve.json?email=foo@bar.com")).to have_been_made end it "returns the requested topics" do invites = client.retrieve_invite(email: "foo@bar.com") expect(invites).to be_an Hash end it "returns the requested invite" do invites = client.retrieve_invite(email: "foo@bar.com") expect(invites["email"]).to eq("foo@bar.com") expect(invites).to have_key("invite_key") end end describe "#destroy_all_expired_invites" do let(:url) { "#{host}/invites/destroy-all-expired" } before do stub_post(url).to_return( body: '{"success": "OK"}', headers: { content_type: "application/json", }, ) end it "destroys all expired invites" do client.destroy_all_expired_invites expect(a_post(url)).to have_been_made end end describe "#resend_all_invites" do let(:url) { "#{host}/invites/reinvite-all" } before do stub_post(url).to_return( body: '{"success": "OK"}', headers: { content_type: "application/json", }, ) end it "resends all invites" do client.resend_all_invites expect(a_post(url)).to have_been_made end end describe "#resend_invite" do let(:url) { "#{host}/invites/reinvite" } before do stub_post(url).to_return( body: '{"success": "OK"}', headers: { content_type: "application/json", }, ) end it "resends invite" do client.resend_invite("foo@bar.com") expect(a_post(url)).to have_been_made end end describe "#destroy_invite" do let(:url) { "#{host}/invites?id=27" } before do stub_delete(url).to_return( body: '{"success": "OK"}', headers: { content_type: "application/json", }, ) end it "destroys the invite" do client.destroy_invite(27) expect(a_delete(url)).to have_been_made end end end ================================================ FILE: spec/discourse_api/api/notifications_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Notifications do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#notifications" do before do stub_get("#{host}/notifications.json").to_return( body: fixture("notifications.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.notifications expect(a_get("#{host}/notifications.json")).to have_been_made end it "returns the requested notifications" do notifications = client.notifications expect(notifications).to be_an Array expect(notifications.first).to be_an Hash expect(notifications[0]["notification_type"]).to eq(9) end end end ================================================ FILE: spec/discourse_api/api/params_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Params do def params_for(h) DiscourseApi::API::Params.new(h).required(:r1).optional(:o1, :o2).default(d1: "default") end it "should raise on missing required params" do expect { params_for({ o1: "test" }).to_h }.to raise_error(ArgumentError) end it "should not raise when a required param is false" do expect { params_for({ r1: false }).to_h }.not_to raise_error end it "should not include optional params when not provided" do expect(params_for({ r1: "test" }).to_h).not_to include(:o1) end it "should include optional params if provided but blank" do expect(params_for({ r1: "test", o2: nil }).to_h).to include(:o2) end it "should include default params when defined but not provided" do expect(params_for({ r1: "test" }).to_h).to include(d1: "default") end it "should include default params when defined and provided" do expect(params_for({ r1: "test", d1: "override" }).to_h).to include(d1: "override") end it "should include optional and default params when defined and provided" do expect(params_for({ r1: "test", o1: "optional", d1: "override" }).to_h).to include( o1: "optional", d1: "override", ) end end ================================================ FILE: spec/discourse_api/api/polls_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Polls do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#poll vote" do before do path = "#{host}/polls/vote" stub_put(path).to_return( body: fixture("polls_vote.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do options = ["8b4736b1ae3dfb5a28088530f036f9e5"] client.poll_vote post_id: 5, poll_name: "poll", options: options expect(a_put("#{host}/polls/vote")).to have_been_made end it "returns the expected votes" do options = ["8b4736b1ae3dfb5a28088530f036f9e5"] vote = client.poll_vote post_id: 5, poll_name: "poll", options: options expect(vote.body).to be_a Hash expect(vote.body["poll"]["options"]).to be_an Array expect(vote.body["vote"]).to eq(["8b4736b1ae3dfb5a28088530f036f9e5"]) end describe "#poll toggle_status" do before do path = "#{host}/polls/toggle_status" stub_put(path).to_return( body: fixture("polls_toggle_status.json"), headers: { content_type: "application/json", }, ) end it "toggles the poll status to closed" do client.toggle_poll_status post_id: 5, poll_name: "poll", status: "closed" expect(a_put("#{host}/polls/toggle_status")).to have_been_made end it "returns the expected results of closed poll" do returned_poll_status = client.toggle_poll_status post_id: 5, poll_name: "poll", status: "closed" expect(returned_poll_status.body).to be_a Hash returned_poll_status.body["poll"]["options"].each { |g| expect(g).to be_a Hash } end end describe "#poll voters" do before do stub_get("#{host}/polls/voters.json?post_id=5&poll_name=poll").to_return( body: fixture("polls_voters.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.poll_voters post_id: 5, poll_name: "poll" expect(a_get("#{host}/polls/voters.json?post_id=5&poll_name=poll")).to have_been_made end it "returns the expected votes" do voters = client.poll_voters post_id: 5, poll_name: "poll" expect(voters).to be_a Hash voters.each { |g| expect(g).to be_an Array } expect(voters["voters"]["e539a9df8700d0d05c69356a07b768cf"]).to be_an Array expect(voters["voters"]["e539a9df8700d0d05c69356a07b768cf"][0]["id"]).to eq(356) end end end end ================================================ FILE: spec/discourse_api/api/posts_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Posts do let(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#get_post" do before do stub_get("#{host}/posts/11.json").to_return( body: fixture("post.json"), headers: { content_type: "application/json", }, ) end it "fetches a post" do the_post = client.get_post(11) expect(the_post).to be_a Hash expect(the_post["id"]).to eq(11) end end describe "#posts" do before do stub_get("#{host}/posts.json?before=0").to_return( body: fixture("posts_latest.json"), headers: { content_type: "application/json", }, ) stub_get("#{host}/posts.json?before=14").to_return( body: fixture("posts_before.json"), headers: { content_type: "application/json", }, ) end it "fetches latest posts" do the_posts = client.posts() expect(the_posts).to be_a Hash expect(the_posts["latest_posts"][0]["id"]).to eq(15) end it "fetches posts before 14" do the_posts = client.posts(before: 14) expect(the_posts).to be_a Hash expect(the_posts["latest_posts"][0]["id"]).to eq(14) end end describe "#wikify_post" do before { stub_put("#{host}/posts/9999/wiki") } it "fails on unknown post" do client.wikify_post(9999) expect(a_put("#{host}/posts/9999/wiki")).to have_been_made end end describe "#delete_post" do before { stub_delete("#{host}/posts/9999.json") } it "deletes a post" do client.delete_post(9999) expect(a_delete("#{host}/posts/9999.json")).to have_been_made end end describe "#post_action_users" do before do stub_get("#{host}/post_action_users.json?id=11&post_action_type_id=2").to_return( body: fixture("post_action_users.json"), headers: { content_type: "application/json", }, ) end it "fetches post_action_users" do post_action_users = client.post_action_users(11, 2) expect(post_action_users).to be_a Hash expect(post_action_users["post_action_users"][0]["id"]).to eq(1286) end end end ================================================ FILE: spec/discourse_api/api/private_messages_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::PrivateMessages do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#private_messages" do before do stub_get("#{host}/topics/private-messages/test_user.json").to_return( body: fixture("private_messages.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.private_messages("test_user") expect(a_get("#{host}/topics/private-messages/test_user.json")).to have_been_made end it "returns the requested private messages" do private_messages = client.private_messages("test_user") expect(private_messages).to be_an Array end end describe "#sent_private_messages" do before do stub_get("#{host}/topics/private-messages-sent/test_user.json").to_return( body: fixture("private_messages.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.sent_private_messages("test_user") expect(a_get("#{host}/topics/private-messages-sent/test_user.json")).to have_been_made end it "returns the requested sent private messages" do private_messages = client.sent_private_messages("test_user") expect(private_messages).to be_an Array end end describe "#create_pm" do before do stub_post("#{host}/posts") client.create_pm( title: "Confidential: Hello World!", raw: "This is the raw markdown for my private message", target_recipients: "user1,user2", ) end it "makes a create private message request" do expect( a_post("#{host}/posts").with( body: "archetype=private_message&raw=This+is+the+raw+markdown+for+my+private+message&target_recipients=user1%2Cuser2&title=Confidential%3A+Hello+World%21", ), ).to have_been_made end end end ================================================ FILE: spec/discourse_api/api/search_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Search do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#search" do before do stub_get("#{host}/search").with(query: { q: "test" }).to_return( body: fixture("search.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.search("test") expect(a_get("#{host}/search").with(query: { q: "test" })).to have_been_made end it "returns the requested search" do results = client.search("test") expect(results).to be_an Array expect(results.first).to be_a Hash end it "raises an ArgumentError for nil" do expect { client.search(nil) }.to raise_error(ArgumentError) end it "raises an ArgumentError for empty string" do expect { client.search("") }.to raise_error(ArgumentError) end end end ================================================ FILE: spec/discourse_api/api/site_settings_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::SiteSettings do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#site_setting_update" do before do stub_put("#{host}/admin/site_settings/foo") client.site_setting_update(name: "foo", value: "bar") end it "makes a site_settings_update request" do expect(a_put("#{host}/admin/site_settings/foo").with(body: "foo=bar")).to have_been_made end end end ================================================ FILE: spec/discourse_api/api/sso_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::SSO do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } let(:params) do { :sso_secret => "abc", :sso_url => "www.google.com", :name => "Some User", :username => "some_user", :email => "some@email.com", :external_id => "abc", :suppress_welcome_message => false, :avatar_url => "https://www.website.com", :title => "ruby", :avatar_force_update => false, :add_groups => %w[a b], :remove_groups => %w[c d], # old format (which results in custom.custom.field_1 in unsigned_payload) "custom.field_1" => "tomato", # new format :custom_fields => { field_2: "potato", }, } end let(:expected_unsigned_payload) do "add_groups=a&add_groups=b&avatar_url=https%3A%2F%2Fwww.website.com" \ "&email=some%40email.com&external_id=abc&name=Some+User&remove_groups=c" \ "&remove_groups=d&title=ruby&username=some_user&custom.field_2=potato" \ "&custom.custom.field_1=tomato" end let(:sso_double) { DiscourseApi::SingleSignOn.parse_hash(params) } describe "#sync_sso" do before do stub_post(/.*sync_sso.*/).to_return( body: fixture("user.json"), headers: { content_type: "application/json", }, ) end it "assigns params to sso instance" do allow(DiscourseApi::SingleSignOn).to(receive(:parse_hash).with(params).and_return(sso_double)) client.sync_sso(params) expect(sso_double.custom_fields).to eql( { "custom.field_1" => "tomato", :field_2 => "potato" }, ) expect(sso_double.unsigned_payload).to eql(expected_unsigned_payload) end it "requests the correct resource" do client.sync_sso({ :sso_secret => "test_d7fd0429940", "custom.riffle_url" => "test" }) expect(a_post(/.*sync_sso.*/)).to have_been_made end end end ================================================ FILE: spec/discourse_api/api/topics_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Topics do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#change_topic_status" do before do stub_put("#{host}/t/57/status").to_return( body: fixture("topic.json"), headers: { content_type: "application/json", }, ) end it "changes the topic status" do client.update_topic_status(57, { status: "visible", enabled: false }) expect(a_put("#{host}/t/57/status")).to have_been_made end end describe "#invite_to_topic" do before do stub_post("#{host}/t/12/invite").to_return( body: fixture("topic_invite_user.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.invite_to_topic(12, email: "fake_user@example.com") expect(a_post("#{host}/t/12/invite")).to have_been_made end it "returns success" do response = client.invite_to_topic(12, email: "fake_user@example.com") expect(response).to be_a Hash expect(response["success"]).to be_truthy end end describe "#latest_topics" do before do stub_get("#{host}/latest.json").to_return( body: fixture("latest.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.latest_topics expect(a_get("#{host}/latest.json")).to have_been_made end it "returns the requested topics" do topics = client.latest_topics expect(topics).to be_an Array expect(topics.first).to be_a Hash end it "can take a hash param" do topics = client.latest_topics({}) expect(topics).to be_an Array expect(topics.first).to be_a Hash end end describe "#top_topics" do before do stub_get("#{host}/top.json").to_return( body: fixture("top.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.top_topics expect(a_get("#{host}/top.json")).to have_been_made end it "returns the requested topics" do topics = client.top_topics expect(topics).to be_an Array expect(topics.first).to be_a Hash end it "can take a hash param" do topics = client.top_topics({}) expect(topics).to be_an Array expect(topics.first).to be_a Hash end end describe "#new_topics" do before do stub_get("#{host}/new.json").to_return( body: fixture("new.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.new_topics expect(a_get("#{host}/new.json")).to have_been_made end it "returns the requested topics" do client.api_username = "test_user" topics = client.new_topics expect(topics).to be_an Array expect(topics.first).to be_a Hash end end describe "#topic" do before do stub_get("#{host}/t/57.json").to_return( body: fixture("topic.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.topic(57) expect(a_get("#{host}/t/57.json")).to have_been_made end it "returns the requested topic" do topic = client.topic(57) expect(topic).to be_a Hash expect(topic["id"]).to eq(57) end end describe "#update_topic" do before do stub_put("#{host}/t/57.json").to_return( body: fixture("topic.json"), headers: { content_type: "application/json", }, ) end it "renames the topic" do client.rename_topic(57, "A new title!") expect(a_put("#{host}/t/57.json")).to have_been_made end it "assigns the topic a new category" do client.recategorize_topic(57, 3) expect(a_put("#{host}/t/57.json")).to have_been_made end end describe "#topics_by" do before do stub_get("#{host}/topics/created-by/test.json").to_return( body: fixture("topics_created_by.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.topics_by("test") expect(a_get("#{host}/topics/created-by/test.json")).to have_been_made end it "returns the requested topics" do topics = client.topics_by("test") expect(topics).to be_an Array expect(topics.first).to be_a Hash end end describe "#topic_posts" do before do stub_get(%r{#{host}/t/57/posts\.json}).to_return( body: fixture("topic_posts.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.topic_posts(57) expect(a_get("#{host}/t/57/posts.json")).to have_been_made end it "allows scoping to specific post ids" do client.topic_posts(57, [123, 456]) expect(a_get("#{host}/t/57/posts.json?post_ids[]=123&post_ids[]=456")).to have_been_made end it "returns the requested topic posts" do body = client.topic_posts(57, [123]) expect(body).to be_a Hash expect(body["post_stream"]["posts"]).to be_an Array expect(body["post_stream"]["posts"].first).to be_a Hash end it "can retrieve a topic posts' raw attribute" do body = client.topic_posts(57, [123], { include_raw: true }) expect(body).to be_a Hash expect(body["post_stream"]["posts"]).to be_an Array expect(body["post_stream"]["posts"].first).to be_a Hash expect(body["post_stream"]["posts"].first["raw"]).to be_an Array end end describe "#create_topic_with_tags" do before do stub_post("#{host}/posts").to_return( body: fixture("create_topic_with_tags.json"), headers: { content_type: "application/json", }, ) end it "makes the post request" do client.create_topic title: "Sample Topic Title", raw: "Sample topic content body", tags: %w[asdf fdsa] expect(a_post("#{host}/posts")).to have_been_made end it "returns success" do response = client.create_topic title: "Sample Topic Title", raw: "Sample topic content body", tags: %w[asdf fdsa] expect(response).to be_a Hash expect(response["topic_id"]).to eq 21 end end describe "#topic_set_user_notification_level" do before do stub_post("#{host}/t/1/notifications").to_return( body: fixture("notification_success.json"), headers: { content_type: "application/json", }, ) end it "makes the post request" do response = client.topic_set_user_notification_level(1, notification_level: 3) expect( a_post("#{host}/t/1/notifications").with(body: "notification_level=3"), ).to have_been_made expect(response["success"]).to eq("OK") end end describe "#bookmark_topic" do before do stub_put("#{host}/t/1/bookmark.json").to_return( body: "", headers: { content_type: "application/json", }, ) end it "makes the put request" do response = client.bookmark_topic(1) expect(a_put("#{host}/t/1/bookmark.json")).to have_been_made expect(response.body).to eq(nil) end end describe "#remove_topic_bookmark" do before do stub_put("#{host}/t/1/remove_bookmarks.json").to_return( body: "", headers: { content_type: "application/json", }, ) end it "makes the put request" do response = client.remove_topic_bookmark(1) expect(a_put("#{host}/t/1/remove_bookmarks.json")).to have_been_made expect(response.body).to eq(nil) end end end ================================================ FILE: spec/discourse_api/api/uploads_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Uploads do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#upload_file" do before do stub_post("#{host}/uploads").to_return( body: fixture("upload_file.json"), headers: { content_type: "application/json", }, ) end it "uploads an image via URL" do image = "https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/sam/120/5243.png" args = { url: image } response = client.upload_file(args) expect(response["url"]).to eq( "/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg", ) end it "uploads a file" do file = Faraday::UploadIO.new("spec/fixtures/upload_file.json", "application/json") args = { file: file } response = client.upload_file(args) expect(response["url"]).to eq( "/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg", ) end end end ================================================ FILE: spec/discourse_api/api/user_actions_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::UserActions do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#user_replies" do before do stub_get("#{host}/user_actions.json?username=testuser&filter=5").to_return( body: fixture("replies.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.user_replies("testuser") expect(a_get("#{host}/user_actions.json?username=testuser&filter=5")).to have_been_made end it "returns the requested user" do replies = client.user_replies("testuser") expect(replies).to be_an Array end end describe "#user_topics_and_replies" do before do stub_get("#{host}/user_actions.json?username=testuser&filter=4,5").to_return( body: fixture("replies_and_topics.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.user_topics_and_replies("testuser") expect(a_get("#{host}/user_actions.json?username=testuser&filter=4,5")).to have_been_made end it "returns the requested user" do replies = client.user_topics_and_replies("testuser") expect(replies).to be_an Array end end end ================================================ FILE: spec/discourse_api/api/users_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::API::Users do subject(:client) { DiscourseApi::Client.new("#{host}", "test_d7fd0429940", "test_user") } describe "#user" do before do stub_get("#{host}/users/test.json").to_return( body: fixture("user.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.user("test") expect(a_get("#{host}/users/test.json")).to have_been_made end it "returns the requested user" do user = client.user("test") expect(user).to be_a Hash end it "works with optional params" do user = client.user("test", {}) expect(user).to be_a Hash end end describe "#user_sso" do before do stub_get("#{host}/admin/users/15.json").to_return( body: fixture("admin_user.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.user_sso(15) expect(a_get("#{host}/admin/users/15.json")).to have_been_made end it "has single_sign_on_record" do user_sso = client.user_sso(15) expect(user_sso).to be_a Hash expect(user_sso).to have_key("external_id") end end describe "#update_avatar" do before do stub_post("#{host}/uploads").to_return( body: fixture("upload_avatar.json"), headers: { content_type: "application/json", }, ) stub_put("#{host}/u/test_user/preferences/avatar/pick").to_return( body: fixture("user_update_avatar_success.json"), headers: { content_type: "application/json", }, ) end it "uploads an image" do sam = "https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/sam/120/5243.png" args = { url: sam } response = client.update_avatar("test_user", args) expect(response[:body]["success"]).to eq("OK") end end describe "#update_email" do before do stub_put("#{host}/u/fake_user/preferences/email").to_return( body: fixture("user_update_user.json"), headers: { content_type: "application/json", }, ) end it "makes the put request" do client.update_email("fake_user", "fake_user_2@example.com") expect(a_put("#{host}/u/fake_user/preferences/email")).to have_been_made end it "returns success" do response = client.update_email("fake_user", "fake_user_2@example.com") expect(response[:body]["success"]).to be_truthy end end describe "#update_user" do before do stub_put("#{host}/u/fake_user").to_return( body: fixture("user_update_user.json"), headers: { content_type: "application/json", }, ) end it "makes the put request" do client.api_key = "test_d7fd0429940" client.api_username = "test_user" client.update_user("fake_user", name: "Fake User 2") expect(a_put("#{host}/u/fake_user")).to have_been_made end it "returns success" do client.api_key = "test_d7fd0429940" client.api_username = "test_user" response = client.update_user("fake_user", name: "Fake User 2") expect(response[:body]["success"]).to be_truthy end end describe "#update_username" do before do stub_put("#{host}/u/fake_user/preferences/username").to_return( body: fixture("user_update_username.json"), headers: { content_type: "application/json", }, ) end it "makes the put request" do client.update_username("fake_user", "fake_user_2") expect(a_put("#{host}/u/fake_user/preferences/username")).to have_been_made end it "returns the updated username" do response = client.update_username("fake_user", "fake_user_2") expect(response[:body]["username"]).to eq("fake_user_2") end end describe "#create_user" do before do stub_post("#{host}/users").to_return( body: fixture("user_create_success.json"), headers: { content_type: "application/json", }, ) end it "makes the post request" do client.create_user name: "Test User", email: "test2@example.com", password: "P@ssword", username: "test2" expect(a_post("#{host}/users")).to have_been_made end it "returns success" do response = client.create_user name: "Test User", email: "test2@example.com", password: "P@ssword", username: "test2" expect(response).to be_a Hash expect(response["success"]).to be_truthy end end describe "#activate_user" do before do stub_put("#{host}/admin/users/15/activate").to_return( body: fixture("user_activate_success.json"), headers: { content_type: "application/json", }, ) end it "makes the put request" do client.activate(15) expect(a_put("#{host}/admin/users/15/activate")).to have_been_made end it "returns success" do response = client.activate(15) expect(response[:body]["success"]).to eq("OK") end end describe "#log_out_success" do before do stub_post("#{host}/admin/users/4/log_out").to_return( body: fixture("user_log_out_success.json"), headers: { content_type: "application/json", }, ) end it "makes a post request" do client.log_out(4) expect(a_post("#{host}/admin/users/4/log_out")).to have_been_made end it "returns success" do response = client.log_out(4) expect(response).to be_a Hash expect(response["success"]).to eq("OK") end end describe "#log_out_unsuccessful" do before do stub_post("#{host}/admin/users/90/log_out").to_return( status: 404, headers: { content_type: "application/json", }, ) end it "Raises API Error" do expect { client.log_out(90) }.to raise_error DiscourseApi::NotFoundError end end describe "#list_users" do before do stub_get("#{host}/admin/users/list/active.json").to_return( body: fixture("user_list.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.list_users("active") expect(a_get("#{host}/admin/users/list/active.json")).to have_been_made end it "returns the requested users" do users = client.list_users("active") expect(users).to be_an Array expect(users.first).to be_a Hash end end describe "#update_trust_level" do before do url = "#{host}/admin/users/2/trust_level" stub_put(url).to_return( body: fixture("update_trust_level.json"), headers: { content_type: "application/json", }, ) end it "makes the correct put request" do params = { level: 2 } client.update_trust_level(2, params) url = "#{host}/admin/users/2/trust_level" expect(a_put(url)).to have_been_made end it "updates the trust_level" do params = { level: 2 } admin_user = client.update_trust_level(2, params) expect(admin_user).to be_a Hash expect(admin_user["admin_user"]).to have_key("trust_level") end end describe "#grant admin" do before do url = "#{host}/admin/users/11/grant_admin" stub_put(url).to_return( body: fixture("user_grant_admin.json"), headers: { content_type: "application/json", }, ) end it "makes the correct put request" do client.grant_admin(11) url = "#{host}/admin/users/11/grant_admin" expect(a_put(url)).to have_been_made end it "makes the user an admin" do result = client.grant_admin(11) expect(result).to be_a Hash expect(result["admin_user"]["admin"]).to eq(true) end end describe "#grant moderation" do before do url = "#{host}/admin/users/11/grant_moderation" stub_put(url).to_return( body: fixture("user_grant_moderator.json"), headers: { content_type: "application/json", }, ) end it "makes the correct put request" do client.grant_moderation(11) url = "#{host}/admin/users/11/grant_moderation" expect(a_put(url)).to have_been_made end it "makes the user a moderator" do result = client.grant_moderation(11) expect(result).to be_a Hash expect(result["admin_user"]["moderator"]).to eq(true) end end describe "#revoke moderation" do before do url = "#{host}/admin/users/11/revoke_moderation" stub_put(url).to_return(body: "", status: 200) end it "makes the correct put request" do result = client.revoke_moderation(11) url = "#{host}/admin/users/11/revoke_moderation" expect(a_put(url)).to have_been_made expect(result.status).to eq(200) end end describe "#by_external_id" do before do stub_get("#{host}/users/by-external/1").to_return( body: fixture("user.json"), headers: { content_type: "application/json", }, ) end it "requests the correct resource" do client.by_external_id(1) expect(a_get("#{host}/users/by-external/1")).to have_been_made end it "returns the requested user" do user = client.by_external_id(1) expect(user["id"]).to eq 1 end end describe "#suspend" do before do url = "#{host}/admin/users/11/suspend" stub_put(url).to_return(body: "", status: 200) end it "makes the correct put request" do result = client.suspend(11, "2030-01-01", "no reason") url = "#{host}/admin/users/11/suspend" expect(a_put(url)).to have_been_made expect(result.status).to eq(200) end end describe "#unsuspend" do before do url = "#{host}/admin/users/11/unsuspend" stub_put(url).to_return(body: "", status: 200) end it "makes the correct put request" do result = client.unsuspend(11) url = "#{host}/admin/users/11/unsuspend" expect(a_put(url)).to have_been_made expect(result.status).to eq(200) end end describe "#anonymize" do before do url = "#{host}/admin/users/11/anonymize" stub_put(url).to_return(body: "", status: 200) end it "makes the correct put request" do result = client.anonymize(11) url = "#{host}/admin/users/11/anonymize" expect(a_put(url)).to have_been_made expect(result.status).to eq(200) end end describe "#delete_user" do before do url = "#{host}/admin/users/11.json?delete_posts=true" stub_delete(url).to_return(body: '{"deleted": true}', status: 200) end it "makes the correct delete request" do result = client.delete_user(11, true) url = "#{host}/admin/users/11.json?delete_posts=true" expect(a_delete(url)).to have_been_made expect(result.body).to eq('{"deleted": true}') expect(result.status).to eq(200) end end describe "#check_username" do let(:url) { "#{host}/users/check_username.json?username=sparrow" } let(:body) { '{"available":false,"suggestion":"sparrow1"}' } before { stub_get(url).to_return(body: body, headers: { content_type: "application/json" }) } it "requests the correct resource" do client.check_username("sparrow") expect(a_get(url)).to have_been_made end it "returns the result" do result = client.check_username("sparrow") expect(result["available"]).to eq false end context "when non-URI characters are used" do let(:url) { "#{host}/users/check_username.json?username=1_%5B4%5D%21+%40the%24%23%3F" } let(:body) { '{"errors":["must only include numbers, letters, dashes, and underscores"]}' } it "escapes them" do client.check_username("1_[4]! @the$#?") expect(a_get(url)).to have_been_made end it "returns the result" do result = client.check_username("1_[4]! @the$#?") expect( result["errors"].first, ).to eq "must only include numbers, letters, dashes, and underscores" end end end describe "#deactivate" do before { stub_put("#{host}/admin/users/15/deactivate").to_return(body: nil) } it "makes the put request" do client.deactivate(15) expect(a_put("#{host}/admin/users/15/deactivate")).to have_been_made end it "returns success" do response = client.deactivate(15) expect(response.status).to eq(200) end end end ================================================ FILE: spec/discourse_api/client_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::Client do subject(:client) { DiscourseApi::Client.new(host) } describe ".new" do it "requires a host argument" do expect { DiscourseApi::Client.new }.to raise_error ArgumentError end it "defaults api key to nil" do expect(client.api_key).to be_nil end it "defaults api username to nil" do expect(client.api_username).to be_nil end it "accepts an api key argument" do client = DiscourseApi::Client.new(host, "test") expect(client.api_key).to eq("test") end it "accepts an api username argument" do client = DiscourseApi::Client.new(host, "test", "test_user") expect(client.api_username).to eq("test_user") end end describe "#timeout" do context "with a custom timeout" do it "is set to Faraday connection" do expect(client.send(:connection).options.timeout).to eq(30) end end context "with the default timeout" do it "is set to Faraday connection" do client.timeout = 25 expect(client.send(:connection).options.timeout).to eq(25) end end it "raises DiscourseApi::Timeout" do stub_get("#{host}/t/1.json").to_timeout expect { client.topic(1) }.to raise_error(DiscourseApi::Timeout) end end describe "#api_key" do it "is publicly accessible" do client.api_key = "test_d7fd0429940" expect(client.api_key).to eq("test_d7fd0429940") end end describe "#api_username" do it "is publicly accessible" do client.api_username = "test_user" expect(client.api_username).to eq("test_user") end end describe "#host" do it "is publicly readable" do expect(client.host).to eq("#{host}") end it "is not publicly writeable" do expect(client).not_to respond_to(:host=) end end describe "#connection" do it "looks like a Faraday connection" do expect(client.send(:connection)).to respond_to :run_request end it "memorizes the connection" do c1, c2 = client.send(:connection), client.send(:connection) expect(c1.object_id).to eq(c2.object_id) end end describe "#delete" do before do stub_delete("#{host}/test/delete").with(query: { deleted: "object" }) client.api_key = "test_d7fd0429940" client.api_username = "test_user" end it "allows custom delete requests" do client.delete("/test/delete", { deleted: "object" }) expect(a_delete("#{host}/test/delete").with(query: { deleted: "object" })).to have_been_made end context "when using a host with a subdirectory" do subject(:client) { DiscourseApi::Client.new("#{host}/forum") } before { stub_delete("#{host}/forum/test/delete").with(query: { deleted: "object" }) } it "allows custom delete requests" do client.delete("/test/delete", { deleted: "object" }) expect( a_delete("#{host}/forum/test/delete").with(query: { deleted: "object" }), ).to have_been_made end end end describe "#post" do before do stub_post("#{host}/test/post").with(body: { created: "object" }) client.api_key = "test_d7fd0429940" client.api_username = "test_user" end it "allows custom post requests" do client.post("/test/post", { created: "object" }) expect(a_post("#{host}/test/post").with(body: { created: "object" })).to have_been_made end context "when using a host with a subdirectory" do subject(:client) { DiscourseApi::Client.new("#{host}/forum") } before { stub_post("#{host}/forum/test/post").with(body: { created: "object" }) } it "allows custom post requests" do client.post("/test/post", { created: "object" }) expect( a_post("#{host}/forum/test/post").with(body: { created: "object" }), ).to have_been_made end end end describe "#put" do before do stub_put("#{host}/test/put").with(body: { updated: "object" }) client.api_key = "test_d7fd0429940" client.api_username = "test_user" end it "allows custom put requests" do client.put("/test/put", { updated: "object" }) expect(a_put("#{host}/test/put").with(body: { updated: "object" })).to have_been_made end context "when using a host with a subdirectory" do subject(:client) { DiscourseApi::Client.new("#{host}/forum") } before { stub_put("#{host}/forum/test/put").with(body: { updated: "object" }) } it "allows custom post requests" do client.put("/test/put", { updated: "object" }) expect(a_put("#{host}/forum/test/put").with(body: { updated: "object" })).to have_been_made end end end describe "#request" do it "catches 500 errors" do connection = instance_double(Faraday::Connection) allow(connection).to receive(:get).and_return( OpenStruct.new(env: { body: "error page html" }, status: 500), ) allow(Faraday).to receive(:new).and_return(connection) expect { client.send(:request, :get, "/test") }.to raise_error DiscourseApi::Error end it "catches Faraday errors" do allow(Faraday).to receive(:new).and_raise(Faraday::ClientError.new("BOOM!")) expect { client.send(:request, :get, "/test") }.to raise_error DiscourseApi::Error end it "catches JSON::ParserError errors" do allow(Faraday).to receive(:new).and_raise(JSON::ParserError.new("unexpected token")) expect { client.send(:request, :get, "/test") }.to raise_error DiscourseApi::Error end end end ================================================ FILE: spec/discourse_api/single_sign_on_spec.rb ================================================ # frozen_string_literal: true require "spec_helper" describe DiscourseApi::SingleSignOn do describe DiscourseApi::SingleSignOn::MissingConfigError do it "inherits from RuntimeError for backward compatibility" do expect(DiscourseApi::SingleSignOn::MissingConfigError).to be < RuntimeError end end describe DiscourseApi::SingleSignOn::ParseError do it "inherits from RuntimeError for backward compatibility" do expect(DiscourseApi::SingleSignOn::ParseError).to be < RuntimeError end end describe ".sso_secret" do it "raises MissingConfigError when sso_secret is not present" do expect { described_class.sso_secret }.to raise_error( DiscourseApi::SingleSignOn::MissingConfigError, ) end end describe ".sso_url" do it "raises MissingConfigError when sso_url is not present" do expect { described_class.sso_url }.to raise_error( DiscourseApi::SingleSignOn::MissingConfigError, ) end end describe ".parse" do context "when sso is present" do it "raises ParseError when there's a signature mismatch" do sso = described_class.new sso.sso_secret = "abcd" expect { described_class.parse(sso.payload, "dcba") }.to raise_error( DiscourseApi::SingleSignOn::ParseError, ) end end context "when sso is missing" do it "raises ParseError when there's a signature mismatch" do sso = described_class.new sso.sso_secret = "abcd" missing_sso = Rack::Utils.parse_query(sso.payload) missing_sso.delete("sso") malformed_query = Rack::Utils.build_query(missing_sso) expect { described_class.parse(malformed_query, "dcba") }.to raise_error( DiscourseApi::SingleSignOn::ParseError, /The SSO field should/i, ) end end end end ================================================ FILE: spec/fixtures/admin_user.json ================================================ { "id": 4, "username": "47f6974294f6e3eb9686", "avatar_template": "/letter_avatar_proxy/v2/letter/4/8e8cbc/{size}.png", "active": false, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": null, "created_at": "2017-06-23T16:30:22.539Z", "last_seen_age": null, "last_emailed_age": null, "created_at_age": "17m", "username_lower": "47f6974294f6e3eb9686", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "staged": false, "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0, "name": "47f6974294f6e3eb9686f898f98a67d7", "can_send_activation_email": true, "can_activate": true, "can_deactivate": true, "ip_address": "192.168.56.1", "registration_ip_address": null, "can_grant_admin": true, "can_revoke_admin": false, "can_grant_moderation": true, "can_revoke_moderation": false, "can_impersonate": true, "like_count": 0, "like_given_count": 0, "topic_count": 0, "flags_given_count": 0, "flags_received_count": 0, "private_topics_count": 0, "can_delete_all_posts": true, "can_be_deleted": true, "can_be_anonymized": true, "suspend_reason": null, "primary_group_id": null, "badge_count": 0, "warnings_received_count": 0, "bounce_score": 0, "reset_bounce_score_after": null, "can_view_action_logs": true, "single_sign_on_record": { "user_id": 4, "external_id": "847", "last_payload": "nonce=bcbe1f1b1202fe597feaa2e3f23d566c&name=47f6974294f6e3eb9686f898f98a67d7&username=47f6974294f6e3eb9686f898f98a67d7&email=47f6974294f6e3eb9686f898f98a67d7%40test.com&require_activation=true&external_id=847", "created_at": "2017-06-23T16:30:22.658Z", "updated_at": "2017-06-23T16:30:22.658Z", "external_username": "47f6974294f6e3eb9686f898f98a67d7", "external_email": "47f6974294f6e3eb9686f898f98a67d7@test.com", "external_name": "47f6974294f6e3eb9686f898f98a67d7", "external_avatar_url": null }, "approved_by": null, "suspended_by": null, "groups": [ { "id": 10, "automatic": true, "name": "trust_level_0", "display_name": "trust_level_0", "user_count": 6, "alias_level": 0, "visible": true, "automatic_membership_email_domains": null, "automatic_membership_retroactive": false, "primary_group": false, "title": null, "grant_trust_level": null, "incoming_email": null, "has_messages": false, "flair_url": null, "flair_bg_color": null, "flair_color": null, "bio_raw": null, "bio_cooked": null, "public": false, "allow_membership_requests": false, "full_name": null, "default_notification_level": 3 } ] } ================================================ FILE: spec/fixtures/api_key.json ================================================ { "key": { "id": 5, "key": "e722e04df8cf6d097d565ca04eea1ff8e9e8f09beb405bae6f0c79852916f334", "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } } } ================================================ FILE: spec/fixtures/backups.json ================================================ [ { "filename": "discourse-2015-01-10-065015.tar.gz", "size": 557075, "link": "//localhost:3000/admin/backups/discourse-2015-01-10-065015.tar.gz" }, { "filename": "2014-02-10-065935.tar.gz", "size": 5, "link": "//localhost:3000/admin/backups/2014-02-10-065935.tar.gz" } ] ================================================ FILE: spec/fixtures/badges.json ================================================ { "badges": [ { "id": 9, "name": "Autobiographer", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": 8, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 3 }, { "id": 11, "name": "First Like", "description": null, "grant_count": 1, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 14, "name": "First Link", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": 2, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 15, "name": "First Quote", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": 2, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 12, "name": "First Share", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": 0, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 16, "name": "Read Guidelines", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": 8, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 3 }, { "id": 17, "name": "Reader", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 1, "system": true, "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", "trigger": null, "target_posts": false, "auto_revoke": false, "show_posts": false, "badge_type_id": 3 }, { "id": 23, "name": "Great Share", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "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", "trigger": 0, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 1 }, { "id": 22, "name": "Good Share", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "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", "trigger": 0, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 2 }, { "id": 10, "name": "Editor", "description": null, "grant_count": 1, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "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", "trigger": 2, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 3 }, { "id": 13, "name": "First Flag", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": false, "show_posts": false, "badge_type_id": 3 }, { "id": 21, "name": "Nice Share", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "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", "trigger": 0, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 5, "name": "Welcome", "description": null, "grant_count": 1, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 8, "name": "Great Post", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 3, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 1 }, { "id": 20, "name": "Great Topic", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 3, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 1 }, { "id": 7, "name": "Good Post", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 3, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 2 }, { "id": 19, "name": "Good Topic", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 3, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 2 }, { "id": 6, "name": "Nice Post", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 3, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 18, "name": "Nice Topic", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": true, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 3, "system": true, "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", "trigger": 1, "target_posts": true, "auto_revoke": true, "show_posts": true, "badge_type_id": 3 }, { "id": 4, "name": "Leader", "description": null, "grant_count": 0, "allow_title": true, "multiple_grant": false, "icon": "fa-user", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 4, "system": true, "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", "trigger": 4, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 1 }, { "id": 3, "name": "Regular", "description": null, "grant_count": 0, "allow_title": true, "multiple_grant": false, "icon": "fa-user", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 4, "system": true, "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", "trigger": 4, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 2 }, { "id": 1, "name": "Basic User", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-user", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 4, "system": true, "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", "trigger": 4, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 3 }, { "id": 2, "name": "Member", "description": null, "grant_count": 0, "allow_title": false, "multiple_grant": false, "icon": "fa-user", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 4, "system": true, "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", "trigger": 4, "target_posts": false, "auto_revoke": true, "show_posts": false, "badge_type_id": 3 } ], "badge_types": [ { "id": 3, "name": "Bronze", "sort_order": 7 }, { "id": 1, "name": "Gold", "sort_order": 9 }, { "id": 2, "name": "Silver", "sort_order": 8 } ], "badge_groupings": [ { "id": 1, "name": "Getting Started", "description": null, "position": 10 }, { "id": 2, "name": "Community", "description": null, "position": 11 }, { "id": 3, "name": "Posting", "description": null, "position": 12 }, { "id": 4, "name": "Trust Level", "description": null, "position": 13 }, { "id": 5, "name": "Other", "description": null, "position": 14 } ], "admin_badges": { "protected_system_fields": [ "badge_type_id", "multiple_grant", "target_posts", "show_posts", "query", "trigger", "auto_revoke", "listable" ], "triggers": { "none": 0, "post_action": 1, "post_revision": 2, "trust_level_change": 4, "user_change": 8 }, "badge_ids": [ 9, 11, 14, 15, 12, 16, 17, 23, 22, 10, 13, 21, 5, 8, 20, 7, 19, 6, 18, 4, 3, 1, 2 ], "badge_grouping_ids": [ 1, 2, 3, 4, 5 ], "badge_type_ids": [ 1, 2, 3 ] } } ================================================ FILE: spec/fixtures/categories.json ================================================ { "featured_users": [], "category_list": { "can_create_category": false, "can_create_topic": false, "draft": null, "draft_key": "new_topic", "draft_sequence": null, "categories": [ { "id": 1, "name": "test1", "color": "0000FF", "text_color": "FFFFFF", "slug": "test1", "topic_count": 0, "description": "Test category #1.", "topic_url": "/t/category-definition-for-test1/1", "hotness": 5, "read_restricted": false, "permission": null, "post_count": 0, "topics_week": 0, "topics_month": 0, "topics_year": 0, "description_excerpt": "Test category #1.", "featured_user_id s": [], "topics": [] }, { "id": 2, "name": "test2", "color": "00FF00", "text_color": "FFFFFF", "slug": "test2", "topic_count": 0, "description": "Test category #2.", "topic_url": "/t/category-definition-for-test2/2", "hotness": 5, "read_restricted": false, "permission": null, "post_count": 0, "topics_week": 0, "topics_month": 0, "topics_year": 0, "description_excerpt": "Test category #2.", "featured_user_ids": [], "topics": [] }, { "id": 3, "name": "test3", "color": "FF0000", "text_color": "FFFFFF", "slug": "test3", "topic_count": 0, "description": "Test category #3.", "topic_url": "/t/category-definition-for-test3/3", "hotness": 5, "read_restricted": false, "permission": null, "post_count": 0, "topics_week": 0, "topics_month": 0, "topics_year": 0, "description_excerpt": "Test category #3.", "featured_user_ids": [], "topics": [] } ] } } ================================================ FILE: spec/fixtures/category_latest_topics.json ================================================ { "users": [ { "id": 3, "username": "fuzzy", "avatar_template": "//www.gravatar.com/avatar/c0c59575e86a794d733db4ee29b2baf4.png?s={size}&r=pg&d=identicon" } ], "topic_list": { "can_create_topic": true, "draft": null, "draft_key": "new_topic", "draft_sequence": 2, "topics": [ { "id": 5, "title": "About the category", "fancy_title": "About the category", "slug": "about-the-category", "posts_count": 2, "reply_count": 0, "highest_post_number": 2, "image_url": null, "created_at": "2014-04-16T14:10:50.950-04:00", "last_posted_at": "2014-04-16T14:31:10.406-04:00", "bumped": true, "bumped_at": "2014-04-17T13:35:01.102-04:00", "unseen": false, "last_read_post_number": 2, "unread": 0, "new_posts": 0, "pinned": true, "unpinned": null, "excerpt": null, "visible ": true, "closed": false, "archived": false, "views": 0, "like_count": 0, "starred": false, "has_summary": false, "archetype": "regular", "last_poster_username": "fuzzy", "category_id": 2, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 3 } ] }, { "id": 4, "title": "This is a markdown post", "fancy_title": "This is a markdown post", "slug": "this-is-a-markdown-post", "posts_count": 2, "reply_count": 0, "highest_post_number": 2, "image_url": null, "created_at": "2014-04-16T13:52:58.777-04:00", "last_posted_at": "2014-04-16T14:31:37.371-04:00", "bumped": true, "bumped_at": "2014-04-16T14:31:37.371-04:00", "unseen": false, "last_read_post_number": 2, "unread": 0, "new_posts": 0, "pinned": false, "unpinned ": null, "visible": true, "closed": false, "archived": false, "views": 0, "like_count": 0, "starred": false, "has_summary": false, "archetype": "regular", "last_poster_username": "fuzzy", "category_id": 2, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 3 } ] } ] } } ================================================ FILE: spec/fixtures/category_topics.json ================================================ { "users": [ { "id": 3, "username": "fuzzy", "avatar_template": "//www.gravatar.com/avatar/c0c59575e86a794d733db4ee29b2baf4.png?s={size}&r=pg&d=identicon" } ], "topic_list": { "can_create_topic": true, "draft": null, "draft_key": "new_topic", "draft_sequence": 2, "topics": [ { "id": 5, "title": "About the category", "fancy_title": "About the category", "slug": "about-the-category", "posts_count": 2, "reply_count": 0, "highest_post_number": 2, "image_url": null, "created_at": "2014-04-16T14:10:50.950-04:00", "last_posted_at": "2014-04-16T14:31:10.406-04:00", "bumped": true, "bumped_at": "2014-04-17T13:35:01.102-04:00", "unseen": false, "last_read_post_number": 2, "unread": 0, "new_posts": 0, "pinned": true, "unpinned": null, "excerpt": null, "visible ": true, "closed": false, "archived": false, "views": 0, "like_count": 0, "starred": false, "has_summary": false, "archetype": "regular", "last_poster_username": "fuzzy", "category_id": 2, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 3 } ] }, { "id": 4, "title": "This is a markdown post", "fancy_title": "This is a markdown post", "slug": "this-is-a-markdown-post", "posts_count": 2, "reply_count": 0, "highest_post_number": 2, "image_url": null, "created_at": "2014-04-16T13:52:58.777-04:00", "last_posted_at": "2014-04-16T14:31:37.371-04:00", "bumped": true, "bumped_at": "2014-04-16T14:31:37.371-04:00", "unseen": false, "last_read_post_number": 2, "unread": 0, "new_posts": 0, "pinned": false, "unpinned ": null, "visible": true, "closed": false, "archived": false, "views": 0, "like_count": 0, "starred": false, "has_summary": false, "archetype": "regular", "last_poster_username": "fuzzy", "category_id": 2, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 3 } ] } ] } } ================================================ FILE: spec/fixtures/create_topic_with_tags.json ================================================ { "id": 29, "name": null, "username": "blake", "avatar_template": "/user_avatar/localhost/blake/{size}/3_2.png", "created_at": "2020-07-09T15:52:43.802Z", "cooked": "

cb8ee24eb3c5a81139958cf2b6bee9bd 2a7f7ea218e24c9ee6528cd383527f99 5a3006b92918946230385393a83b6005

", "post_number": 1, "post_type": 1, "updated_at": "2020-07-09T15:52:43.802Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "incoming_link_count": 0, "reads": 0, "readers_count": 0, "score": 0, "yours": true, "topic_id": 21, "topic_slug": "1412a036bfe-6480718525e-9f71f98ef46", "display_username": null, "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": true, "can_delete": false, "can_recover": false, "can_wiki": true, "user_title": null, "actions_summary": [ { "id": 3, "can_act": true }, { "id": 4, "can_act": true }, { "id": 8, "can_act": true }, { "id": 7, "can_act": true } ], "moderator": false, "admin": true, "staff": true, "user_id": 1, "draft_sequence": 0, "hidden": false, "trust_level": 1, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false, "reviewable_id": null, "reviewable_score_count": 0, "reviewable_score_pending_count": 0 } ================================================ FILE: spec/fixtures/email_list_all.json ================================================ [ { "id": 64, "reply_key": null, "to_address": "steve7@example.com", "email_type": "signup", "user_id": 22, "created_at": "2015-01-03T13:33:46.730Z", "skipped": false, "user": { "id": 22, "username": "steve7", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve7/{size}/2.png" } }, { "id": 63, "reply_key": null, "to_address": "steve6@example.com", "email_type": "user_private_message", "user_id": 21, "created_at": "2015-01-02T17:18:47.098Z", "skipped": false, "user": { "id": 21, "username": "steve6", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve6/{size}/2.png" } }, { "id": 62, "reply_key": null, "to_address": "steve5@example.com", "email_type": "user_private_message", "user_id": 20, "created_at": "2015-01-02T16:05:38.192Z", "skipped": false, "user": { "id": 20, "username": "steve5", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve5/{size}/2.png" } }, { "id": 61, "reply_key": null, "to_address": "steve4@example.com", "email_type": "user_private_message", "user_id": 19, "created_at": "2015-01-02T15:59:27.137Z", "skipped": false, "user": { "id": 19, "username": "steve4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve4/{size}/2.png" } }, { "id": 60, "reply_key": null, "to_address": "steve5@example.com", "email_type": "signup", "user_id": 20, "created_at": "2015-01-02T15:57:57.061Z", "skipped": false, "user": { "id": 20, "username": "steve5", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve5/{size}/2.png" } }, { "id": 59, "reply_key": null, "to_address": "steve4@example.com", "email_type": "signup", "user_id": 19, "created_at": "2015-01-02T15:52:36.210Z", "skipped": false, "user": { "id": 19, "username": "steve4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve4/{size}/2.png" } }, { "id": 58, "reply_key": null, "to_address": "steve3@example.com", "email_type": "signup", "user_id": 18, "created_at": "2015-01-02T15:50:29.330Z", "skipped": false, "user": { "id": 18, "username": "steve3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve3/{size}/2.png" } }, { "id": 57, "reply_key": null, "to_address": "steve3@example.com", "email_type": "user_private_message", "user_id": 18, "created_at": "2015-01-02T14:07:40.357Z", "skipped": true, "skipped_reason": "The notification this email is about has already been read", "user": { "id": 18, "username": "steve3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve3/{size}/2.png" } }, { "id": 56, "reply_key": null, "to_address": "steve3@example.com", "email_type": "signup", "user_id": 18, "created_at": "2015-01-02T13:53:04.352Z", "skipped": false, "user": { "id": 18, "username": "steve3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve3/{size}/2.png" } }, { "id": 55, "reply_key": null, "to_address": "steve2@example.com", "email_type": "signup", "user_id": 17, "created_at": "2015-01-02T13:39:40.455Z", "skipped": false, "user": { "id": 17, "username": "steve2", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve2/{size}/2.png" } }, { "id": 54, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "signup", "user_id": 1, "created_at": "2015-01-02T13:38:11.418Z", "skipped": false, "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png" } }, { "id": 53, "reply_key": null, "to_address": "dave3@gotham.com", "email_type": "signup", "user_id": 10, "created_at": "2015-01-01T13:08:57.914Z", "skipped": false, "user": { "id": 10, "username": "dave3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave3/{size}/2.png" } }, { "id": 52, "reply_key": null, "to_address": "dave2@gotham.com", "email_type": "authorize_email", "user_id": 9, "created_at": "2015-01-01T01:22:45.479Z", "skipped": false, "user": { "id": 9, "username": "dave2", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave2/{size}/2.png" } }, { "id": 51, "reply_key": null, "to_address": "phil@example.com", "email_type": "user_private_message", "user_id": 7, "created_at": "2015-01-01T01:01:33.316Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } }, { "id": 50, "reply_key": null, "to_address": "dave@example.com", "email_type": "user_private_message", "user_id": 8, "created_at": "2015-01-01T01:01:33.222Z", "skipped": false, "user": { "id": 8, "username": "dave", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave/{size}/2.png" } }, { "id": 49, "reply_key": null, "to_address": "steve@example.com", "email_type": "user_private_message", "user_id": 4, "created_at": "2015-01-01T01:01:33.200Z", "skipped": false, "user": { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png" } }, { "id": 48, "reply_key": null, "to_address": "steve@example.com", "email_type": "signup", "user_id": 4, "created_at": "2015-01-01T01:01:33.141Z", "skipped": false, "user": { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png" } }, { "id": 47, "reply_key": null, "to_address": "steve999@example.com", "email_type": "authorize_email", "user_id": 4, "created_at": "2015-01-01T01:01:32.846Z", "skipped": false, "user": { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png" } }, { "id": 46, "reply_key": null, "to_address": "steve345@example.com", "email_type": "authorize_email", "user_id": 4, "created_at": "2015-01-01T01:01:32.844Z", "skipped": false, "user": { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png" } }, { "id": 45, "reply_key": null, "to_address": "robin3@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2015-01-01T01:01:24.446Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 44, "reply_key": null, "to_address": "robin3@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2015-01-01T01:01:24.445Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 43, "reply_key": null, "to_address": "robin3@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2015-01-01T01:01:24.444Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 42, "reply_key": null, "to_address": "robin3@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2015-01-01T01:01:24.441Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 41, "reply_key": null, "to_address": "no_email_found", "email_type": "signup", "user_id": null, "created_at": "2014-12-31T18:12:05.707Z", "skipped": true, "skipped_reason": "Can't find user with id 6", "user": null }, { "id": 40, "reply_key": null, "to_address": "foo@bar.com", "email_type": "authorize_email", "user_id": 8, "created_at": "2014-12-31T17:56:39.302Z", "skipped": false, "user": { "id": 8, "username": "dave", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave/{size}/2.png" } }, { "id": 39, "reply_key": null, "to_address": "dave2@example.com", "email_type": "user_private_message", "user_id": 9, "created_at": "2014-12-31T17:56:21.004Z", "skipped": false, "user": { "id": 9, "username": "dave2", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave2/{size}/2.png" } }, { "id": 38, "reply_key": null, "to_address": "fake@example.com", "email_type": "signup", "user_id": 16, "created_at": "2014-12-31T17:54:32.454Z", "skipped": false, "user": { "id": 16, "username": "carl", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/carl/{size}/2.png" } }, { "id": 37, "reply_key": null, "to_address": "test_user2@example.com", "email_type": "authorize_email", "user_id": 1, "created_at": "2014-12-31T17:53:48.162Z", "skipped": false, "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png" } }, { "id": 36, "reply_key": null, "to_address": "foo@bar.com", "email_type": "authorize_email", "user_id": 7, "created_at": "2014-12-31T17:13:15.797Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } }, { "id": 35, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 7, "created_at": "2014-12-31T17:13:06.259Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } }, { "id": 34, "reply_key": null, "to_address": "dave7@example.com", "email_type": "signup", "user_id": 14, "created_at": "2014-12-31T17:12:43.031Z", "skipped": false, "user": { "id": 14, "username": "dave7", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave7/{size}/2.png" } }, { "id": 33, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2014-12-31T17:12:43.017Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 32, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2014-12-31T17:12:43.010Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 31, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 1, "created_at": "2014-12-31T17:12:43.007Z", "skipped": false, "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png" } }, { "id": 30, "reply_key": null, "to_address": "foo@bar.com", "email_type": "authorize_email", "user_id": 7, "created_at": "2014-12-31T17:12:42.959Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } }, { "id": 29, "reply_key": null, "to_address": "dave4@example.com", "email_type": "signup", "user_id": 11, "created_at": "2014-12-31T17:11:51.195Z", "skipped": false, "user": { "id": 11, "username": "dave4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave4/{size}/2.png" } }, { "id": 28, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2014-12-31T17:11:51.193Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 27, "reply_key": null, "to_address": "dave5@example.com", "email_type": "signup", "user_id": 12, "created_at": "2014-12-31T17:11:51.192Z", "skipped": false, "user": { "id": 12, "username": "dave5", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave5/{size}/2.png" } }, { "id": 26, "reply_key": null, "to_address": "foo@bar.com", "email_type": "authorize_email", "user_id": 8, "created_at": "2014-12-31T17:11:51.144Z", "skipped": false, "user": { "id": 8, "username": "dave", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave/{size}/2.png" } }, { "id": 25, "reply_key": null, "to_address": "dave6@example.com", "email_type": "signup", "user_id": 13, "created_at": "2014-12-31T17:11:14.401Z", "skipped": false, "user": { "id": 13, "username": "dave6", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave6/{size}/2.png" } }, { "id": 24, "reply_key": null, "to_address": "test_user2@example.com", "email_type": "authorize_email", "user_id": 1, "created_at": "2014-12-31T17:11:14.399Z", "skipped": false, "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png" } }, { "id": 23, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 7, "created_at": "2014-12-31T17:11:14.398Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } }, { "id": 22, "reply_key": null, "to_address": "foo@bar.com", "email_type": "authorize_email", "user_id": 7, "created_at": "2014-12-31T17:10:28.274Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } }, { "id": 21, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2014-12-31T17:10:28.257Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 20, "reply_key": null, "to_address": "steve123@example.com", "email_type": "authorize_email", "user_id": 4, "created_at": "2014-12-31T17:10:28.252Z", "skipped": false, "user": { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png" } }, { "id": 19, "reply_key": null, "to_address": "steve123@example.com", "email_type": "authorize_email", "user_id": 4, "created_at": "2014-12-31T17:10:03.163Z", "skipped": false, "user": { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png" } }, { "id": 18, "reply_key": null, "to_address": "dave8@example.com", "email_type": "signup", "user_id": 15, "created_at": "2014-12-31T17:09:30.028Z", "skipped": false, "user": { "id": 15, "username": "dave8", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave8/{size}/2.png" } }, { "id": 17, "reply_key": null, "to_address": "dave3@example.com", "email_type": "signup", "user_id": 10, "created_at": "2014-12-31T17:09:30.011Z", "skipped": false, "user": { "id": 10, "username": "dave3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave3/{size}/2.png" } }, { "id": 16, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 2, "created_at": "2014-12-31T17:09:30.005Z", "skipped": false, "user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png" } }, { "id": 15, "reply_key": null, "to_address": "batman@gotham.com", "email_type": "authorize_email", "user_id": 7, "created_at": "2014-12-31T17:09:29.997Z", "skipped": false, "user": { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png" } } ] ================================================ FILE: spec/fixtures/email_settings.json ================================================ { "delivery_method": "smtp", "settings": [ { "name": "address", "value": "localhost" }, { "name": "port", "value": 1025 } ] } ================================================ FILE: spec/fixtures/group.json ================================================ {"basic_group": { "id": 101, "name": "group_1", "user_count": 17, "automatic": false, "alias_level": 0, "visible": true } } ================================================ FILE: spec/fixtures/groups.json ================================================ [ { "id": 101, "name": "group_1", "user_count": 17, "automatic": false, "alias_level": 0, "visible": true }, { "id": 102, "name": "group_2", "user_count": 4, "automatic": false, "alias_level": 0, "visible": true } ] ================================================ FILE: spec/fixtures/hot.json ================================================ { "users": [ { "id": 1, "username": "test_users", "avatar_template": "//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon" } ], "topic_list": { "can_create_topic": false, "draft": null, "draft_key": "new_topic", "draft_sequence": null, "topics": [ { "id": 1, "title": "Test topic #1", "fancy_title": "Test topic #1", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T14:38:46.480-05:00", "last_posted_at": "2013-09-13T16:46:59.783-04:00", "bumped": true, "bumped_at": "2013-09-13T16:46:59.783-04:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 1, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 2, "title": "Test topic #2", "fancy_title": "Test topic #2", "slug": "test-topic-2", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T14:38:46.480-05:00", "last_posted_at": "2013-09-13T16:46:59.783-04:00", "bumped": true, "bumped_at": "2013-09-13T16:46:59.783-04:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 1, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 3, "title": "Test topic #3", "fancy_title": "Test topic #3", "slug": "test-topic-3", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T14:38:46.480-05:00", "last_posted_at": "2013-09-13T16:46:59.783-04:00", "bumped": true, "bumped_at": "2013-09-13T16:46:59.783-04:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 1, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": "latest", "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] } ] } } ================================================ FILE: spec/fixtures/latest.json ================================================ { "users": [ { "id": 1, "username": "test_users", "avatar_template": "//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon" } ], "topic_list": { "can_create_topic": false, "more_topics_url": "/latest.json?page=1", "draft": null, "draft_key": "new_topic", "draft_sequence": null, "topics": [ { "id": 1, "title": "Test topic #1", "fancy_title": "Test topic #1", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T15:26:25.977-05:00", "last_posted_at": "2013-02-05T12:14:17.364-05:00", "bumped": true, "bumped_at": "2013-02-07T13:43:11.191-05:00", "unseen": false, "pinned": true, "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…", "visible": true, "closed": false, "archived": false, "views": 268, "like_count": 14, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 1, "title": "Test topic #2", "fancy_title": "Test topic #2", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 2, "image_url": null, "created_at": "2013-11-06T13:57:47.984-05:00", "last_posted_at": "2013-11-06T13:57:48.111-05:00", "bumped": true, "bumped_at": "2013-11-06T13:57:48.111-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 1, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 1, "title": "Test topic #3", "fancy_title": "Test topic #3", "slug": "test-topic-3", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T21:46:58.194-05:00", "last_posted_at": "2013-11-06T13:56:27.753-05:00", "bumped": true, "bumped_at": "2013-11-06T13:56:27.753-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 82, "like_count": 10, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] } ] } } ================================================ FILE: spec/fixtures/list_api_keys.json ================================================ { "keys": [ { "id": 1, "key": "test_d7fd0429940", "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png" } } ] } ================================================ FILE: spec/fixtures/members_0.json ================================================ { "members": [{ "id": 1, "name": "Test 0", "username": "Test_0" }, { "id": 2, "name": "Test 1", "username": "Test_1" }, { "id": 3, "name": "Test 2", "username": "Test_2" }, { "id": 4, "name": "Test 3", "username": "Test_3" }, { "id": 5, "name": "Test 4", "username": "Test_4" }, { "id": 6, "name": "Test 5", "username": "Test_5" }, { "id": 7, "name": "Test 6", "username": "Test_6" }, { "id": 8, "name": "Test 7", "username": "Test_7" }, { "id": 9, "name": "Test 8", "username": "Test_8" }, { "id": 10, "name": "Test 9", "username": "Test_9" }, { "id": 11, "name": "Test 10", "username": "Test_10" }, { "id": 12, "name": "Test 11", "username": "Test_11" }, { "id": 13, "name": "Test 12", "username": "Test_12" }, { "id": 14, "name": "Test 13", "username": "Test_13" }, { "id": 15, "name": "Test 14", "username": "Test_14" }, { "id": 16, "name": "Test 15", "username": "Test_15" }, { "id": 17, "name": "Test 16", "username": "Test_16" }, { "id": 18, "name": "Test 17", "username": "Test_17" }, { "id": 19, "name": "Test 18", "username": "Test_18" }, { "id": 20, "name": "Test 19", "username": "Test_19" }, { "id": 21, "name": "Test 20", "username": "Test_20" }, { "id": 22, "name": "Test 21", "username": "Test_21" }, { "id": 23, "name": "Test 22", "username": "Test_22" }, { "id": 24, "name": "Test 23", "username": "Test_23" }, { "id": 25, "name": "Test 24", "username": "Test_24" }, { "id": 26, "name": "Test 25", "username": "Test_25" }, { "id": 27, "name": "Test 26", "username": "Test_26" }, { "id": 28, "name": "Test 27", "username": "Test_27" }, { "id": 29, "name": "Test 28", "username": "Test_28" }, { "id": 30, "name": "Test 29", "username": "Test_29" }, { "id": 31, "name": "Test 30", "username": "Test_30" }, { "id": 32, "name": "Test 31", "username": "Test_31" }, { "id": 33, "name": "Test 32", "username": "Test_32" }, { "id": 34, "name": "Test 33", "username": "Test_33" }, { "id": 35, "name": "Test 34", "username": "Test_34" }, { "id": 36, "name": "Test 35", "username": "Test_35" }, { "id": 37, "name": "Test 36", "username": "Test_36" }, { "id": 38, "name": "Test 37", "username": "Test_37" }, { "id": 39, "name": "Test 38", "username": "Test_38" }, { "id": 40, "name": "Test 39", "username": "Test_39" }, { "id": 41, "name": "Test 40", "username": "Test_40" }, { "id": 42, "name": "Test 41", "username": "Test_41" }, { "id": 43, "name": "Test 42", "username": "Test_42" }, { "id": 44, "name": "Test 43", "username": "Test_43" }, { "id": 45, "name": "Test 44", "username": "Test_44" }, { "id": 46, "name": "Test 45", "username": "Test_45" }, { "id": 47, "name": "Test 46", "username": "Test_46" }, { "id": 48, "name": "Test 47", "username": "Test_47" }, { "id": 49, "name": "Test 48", "username": "Test_48" }, { "id": 50, "name": "Test 49", "username": "Test_49" }, { "id": 51, "name": "Test 50", "username": "Test_50" }, { "id": 52, "name": "Test 51", "username": "Test_51" }, { "id": 53, "name": "Test 52", "username": "Test_52" }, { "id": 54, "name": "Test 53", "username": "Test_53" }, { "id": 55, "name": "Test 54", "username": "Test_54" }, { "id": 56, "name": "Test 55", "username": "Test_55" }, { "id": 57, "name": "Test 56", "username": "Test_56" }, { "id": 58, "name": "Test 57", "username": "Test_57" }, { "id": 59, "name": "Test 58", "username": "Test_58" }, { "id": 60, "name": "Test 59", "username": "Test_59" }, { "id": 61, "name": "Test 60", "username": "Test_60" }, { "id": 62, "name": "Test 61", "username": "Test_61" }, { "id": 63, "name": "Test 62", "username": "Test_62" }, { "id": 64, "name": "Test 63", "username": "Test_63" }, { "id": 65, "name": "Test 64", "username": "Test_64" }, { "id": 66, "name": "Test 65", "username": "Test_65" }, { "id": 67, "name": "Test 66", "username": "Test_66" }, { "id": 68, "name": "Test 67", "username": "Test_67" }, { "id": 69, "name": "Test 68", "username": "Test_68" }, { "id": 70, "name": "Test 69", "username": "Test_69" }, { "id": 71, "name": "Test 70", "username": "Test_70" }, { "id": 72, "name": "Test 71", "username": "Test_71" }, { "id": 73, "name": "Test 72", "username": "Test_72" }, { "id": 74, "name": "Test 73", "username": "Test_73" }, { "id": 75, "name": "Test 74", "username": "Test_74" }, { "id": 76, "name": "Test 75", "username": "Test_75" }, { "id": 77, "name": "Test 76", "username": "Test_76" }, { "id": 78, "name": "Test 77", "username": "Test_77" }, { "id": 79, "name": "Test 78", "username": "Test_78" }, { "id": 80, "name": "Test 79", "username": "Test_79" }, { "id": 81, "name": "Test 80", "username": "Test_80" }, { "id": 82, "name": "Test 81", "username": "Test_81" }, { "id": 83, "name": "Test 82", "username": "Test_82" }, { "id": 84, "name": "Test 83", "username": "Test_83" }, { "id": 85, "name": "Test 84", "username": "Test_84" }, { "id": 86, "name": "Test 85", "username": "Test_85" }, { "id": 87, "name": "Test 86", "username": "Test_86" }, { "id": 88, "name": "Test 87", "username": "Test_87" }, { "id": 89, "name": "Test 88", "username": "Test_88" }, { "id": 90, "name": "Test 89", "username": "Test_89" }, { "id": 91, "name": "Test 90", "username": "Test_90" }, { "id": 92, "name": "Test 91", "username": "Test_91" }, { "id": 93, "name": "Test 92", "username": "Test_92" }, { "id": 94, "name": "Test 93", "username": "Test_93" }, { "id": 95, "name": "Test 94", "username": "Test_94" }, { "id": 96, "name": "Test 95", "username": "Test_95" }, { "id": 97, "name": "Test 96", "username": "Test_96" }, { "id": 98, "name": "Test 97", "username": "Test_97" }, { "id": 99, "name": "Test 98", "username": "Test_98" }, { "id": 100, "name": "Test 99", "username": "Test_99" }] } ================================================ FILE: spec/fixtures/members_1.json ================================================ { "members": [{ "id": 101, "name": "Test 100", "username": "Test_100" }, { "id": 102, "name": "Test 101", "username": "Test_101" }, { "id": 103, "name": "Test 102", "username": "Test_102" }, { "id": 104, "name": "Test 103", "username": "Test_103" }, { "id": 105, "name": "Test 104", "username": "Test_104" }, { "id": 106, "name": "Test 105", "username": "Test_105" }, { "id": 107, "name": "Test 106", "username": "Test_106" }, { "id": 108, "name": "Test 107", "username": "Test_107" }, { "id": 109, "name": "Test 108", "username": "Test_108" }, { "id": 110, "name": "Test 109", "username": "Test_109" }, { "id": 111, "name": "Test 110", "username": "Test_110" }, { "id": 112, "name": "Test 111", "username": "Test_111" }, { "id": 113, "name": "Test 112", "username": "Test_112" }, { "id": 114, "name": "Test 113", "username": "Test_113" }, { "id": 115, "name": "Test 114", "username": "Test_114" }, { "id": 116, "name": "Test 115", "username": "Test_115" }, { "id": 117, "name": "Test 116", "username": "Test_116" }, { "id": 118, "name": "Test 117", "username": "Test_117" }, { "id": 119, "name": "Test 118", "username": "Test_118" }, { "id": 120, "name": "Test 119", "username": "Test_119" }, { "id": 121, "name": "Test 120", "username": "Test_120" }, { "id": 122, "name": "Test 121", "username": "Test_121" }, { "id": 123, "name": "Test 122", "username": "Test_122" }, { "id": 124, "name": "Test 123", "username": "Test_123" }, { "id": 125, "name": "Test 124", "username": "Test_124" }, { "id": 126, "name": "Test 125", "username": "Test_125" }, { "id": 127, "name": "Test 126", "username": "Test_126" }, { "id": 128, "name": "Test 127", "username": "Test_127" }, { "id": 129, "name": "Test 128", "username": "Test_128" }, { "id": 130, "name": "Test 129", "username": "Test_129" }, { "id": 131, "name": "Test 130", "username": "Test_130" }, { "id": 132, "name": "Test 131", "username": "Test_131" }, { "id": 133, "name": "Test 132", "username": "Test_132" }, { "id": 134, "name": "Test 133", "username": "Test_133" }, { "id": 135, "name": "Test 134", "username": "Test_134" }, { "id": 136, "name": "Test 135", "username": "Test_135" }, { "id": 137, "name": "Test 136", "username": "Test_136" }, { "id": 138, "name": "Test 137", "username": "Test_137" }, { "id": 139, "name": "Test 138", "username": "Test_138" }, { "id": 140, "name": "Test 139", "username": "Test_139" }, { "id": 141, "name": "Test 140", "username": "Test_140" }, { "id": 142, "name": "Test 141", "username": "Test_141" }, { "id": 143, "name": "Test 142", "username": "Test_142" }, { "id": 144, "name": "Test 143", "username": "Test_143" }, { "id": 145, "name": "Test 144", "username": "Test_144" }, { "id": 146, "name": "Test 145", "username": "Test_145" }, { "id": 147, "name": "Test 146", "username": "Test_146" }, { "id": 148, "name": "Test 147", "username": "Test_147" }, { "id": 149, "name": "Test 148", "username": "Test_148" }, { "id": 150, "name": "Test 149", "username": "Test_149" }, { "id": 151, "name": "Test 150", "username": "Test_150" }, { "id": 152, "name": "Test 151", "username": "Test_151" }, { "id": 153, "name": "Test 152", "username": "Test_152" }, { "id": 154, "name": "Test 153", "username": "Test_153" }, { "id": 155, "name": "Test 154", "username": "Test_154" }, { "id": 156, "name": "Test 155", "username": "Test_155" }, { "id": 157, "name": "Test 156", "username": "Test_156" }, { "id": 158, "name": "Test 157", "username": "Test_157" }, { "id": 159, "name": "Test 158", "username": "Test_158" }, { "id": 160, "name": "Test 159", "username": "Test_159" }, { "id": 161, "name": "Test 160", "username": "Test_160" }, { "id": 162, "name": "Test 161", "username": "Test_161" }, { "id": 163, "name": "Test 162", "username": "Test_162" }, { "id": 164, "name": "Test 163", "username": "Test_163" }, { "id": 165, "name": "Test 164", "username": "Test_164" }, { "id": 166, "name": "Test 165", "username": "Test_165" }, { "id": 167, "name": "Test 166", "username": "Test_166" }, { "id": 168, "name": "Test 167", "username": "Test_167" }, { "id": 169, "name": "Test 168", "username": "Test_168" }, { "id": 170, "name": "Test 169", "username": "Test_169" }, { "id": 171, "name": "Test 170", "username": "Test_170" }, { "id": 172, "name": "Test 171", "username": "Test_171" }, { "id": 173, "name": "Test 172", "username": "Test_172" }, { "id": 174, "name": "Test 173", "username": "Test_173" }, { "id": 175, "name": "Test 174", "username": "Test_174" }, { "id": 176, "name": "Test 175", "username": "Test_175" }, { "id": 177, "name": "Test 176", "username": "Test_176" }, { "id": 178, "name": "Test 177", "username": "Test_177" }, { "id": 179, "name": "Test 178", "username": "Test_178" }, { "id": 180, "name": "Test 179", "username": "Test_179" }, { "id": 181, "name": "Test 180", "username": "Test_180" }, { "id": 182, "name": "Test 181", "username": "Test_181" }, { "id": 183, "name": "Test 182", "username": "Test_182" }, { "id": 184, "name": "Test 183", "username": "Test_183" }, { "id": 185, "name": "Test 184", "username": "Test_184" }, { "id": 186, "name": "Test 185", "username": "Test_185" }, { "id": 187, "name": "Test 186", "username": "Test_186" }, { "id": 188, "name": "Test 187", "username": "Test_187" }, { "id": 189, "name": "Test 188", "username": "Test_188" }, { "id": 190, "name": "Test 189", "username": "Test_189" }] } ================================================ FILE: spec/fixtures/members_2.json ================================================ { "members": [{ "id": 1, "name": "Test 0", "username": "Test_0" }, { "id": 2, "name": "Test 1", "username": "Test_1" }, { "id": 3, "name": "Test 2", "username": "Test_2" }, { "id": 4, "name": "Test 3", "username": "Test_3" }, { "id": 5, "name": "Test 4", "username": "Test_4" }, { "id": 6, "name": "Test 5", "username": "Test_5" }, { "id": 7, "name": "Test 6", "username": "Test_6" }, { "id": 8, "name": "Test 7", "username": "Test_7" }, { "id": 9, "name": "Test 8", "username": "Test_8" }, { "id": 10, "name": "Test 9", "username": "Test_9" }, { "id": 11, "name": "Test 10", "username": "Test_10" }, { "id": 12, "name": "Test 11", "username": "Test_11" }, { "id": 13, "name": "Test 12", "username": "Test_12" }, { "id": 14, "name": "Test 13", "username": "Test_13" }, { "id": 15, "name": "Test 14", "username": "Test_14" }, { "id": 16, "name": "Test 15", "username": "Test_15" }, { "id": 17, "name": "Test 16", "username": "Test_16" }, { "id": 18, "name": "Test 17", "username": "Test_17" }, { "id": 19, "name": "Test 18", "username": "Test_18" }, { "id": 20, "name": "Test 19", "username": "Test_19" }, { "id": 21, "name": "Test 20", "username": "Test_20" }, { "id": 22, "name": "Test 21", "username": "Test_21" }, { "id": 23, "name": "Test 22", "username": "Test_22" }, { "id": 24, "name": "Test 23", "username": "Test_23" }, { "id": 25, "name": "Test 24", "username": "Test_24" }, { "id": 26, "name": "Test 25", "username": "Test_25" }, { "id": 27, "name": "Test 26", "username": "Test_26" }, { "id": 28, "name": "Test 27", "username": "Test_27" }, { "id": 29, "name": "Test 28", "username": "Test_28" }, { "id": 30, "name": "Test 29", "username": "Test_29" }, { "id": 31, "name": "Test 30", "username": "Test_30" }, { "id": 32, "name": "Test 31", "username": "Test_31" }, { "id": 33, "name": "Test 32", "username": "Test_32" }, { "id": 34, "name": "Test 33", "username": "Test_33" }, { "id": 35, "name": "Test 34", "username": "Test_34" }, { "id": 36, "name": "Test 35", "username": "Test_35" }, { "id": 37, "name": "Test 36", "username": "Test_36" }, { "id": 38, "name": "Test 37", "username": "Test_37" }, { "id": 39, "name": "Test 38", "username": "Test_38" }, { "id": 40, "name": "Test 39", "username": "Test_39" }, { "id": 41, "name": "Test 40", "username": "Test_40" }, { "id": 42, "name": "Test 41", "username": "Test_41" }, { "id": 43, "name": "Test 42", "username": "Test_42" }, { "id": 44, "name": "Test 43", "username": "Test_43" }, { "id": 45, "name": "Test 44", "username": "Test_44" }, { "id": 46, "name": "Test 45", "username": "Test_45" }, { "id": 47, "name": "Test 46", "username": "Test_46" }, { "id": 48, "name": "Test 47", "username": "Test_47" }, { "id": 49, "name": "Test 48", "username": "Test_48" }, { "id": 50, "name": "Test 49", "username": "Test_49" }, { "id": 51, "name": "Test 50", "username": "Test_50" }, { "id": 52, "name": "Test 51", "username": "Test_51" }, { "id": 53, "name": "Test 52", "username": "Test_52" }, { "id": 54, "name": "Test 53", "username": "Test_53" }, { "id": 55, "name": "Test 54", "username": "Test_54" }, { "id": 56, "name": "Test 55", "username": "Test_55" }, { "id": 57, "name": "Test 56", "username": "Test_56" }, { "id": 58, "name": "Test 57", "username": "Test_57" }, { "id": 59, "name": "Test 58", "username": "Test_58" }, { "id": 60, "name": "Test 59", "username": "Test_59" }, { "id": 61, "name": "Test 60", "username": "Test_60" }, { "id": 62, "name": "Test 61", "username": "Test_61" }, { "id": 63, "name": "Test 62", "username": "Test_62" }, { "id": 64, "name": "Test 63", "username": "Test_63" }, { "id": 65, "name": "Test 64", "username": "Test_64" }, { "id": 66, "name": "Test 65", "username": "Test_65" }, { "id": 67, "name": "Test 66", "username": "Test_66" }, { "id": 68, "name": "Test 67", "username": "Test_67" }, { "id": 69, "name": "Test 68", "username": "Test_68" }, { "id": 70, "name": "Test 69", "username": "Test_69" }, { "id": 71, "name": "Test 70", "username": "Test_70" }, { "id": 72, "name": "Test 71", "username": "Test_71" }, { "id": 73, "name": "Test 72", "username": "Test_72" }, { "id": 74, "name": "Test 73", "username": "Test_73" }, { "id": 75, "name": "Test 74", "username": "Test_74" }, { "id": 76, "name": "Test 75", "username": "Test_75" }, { "id": 77, "name": "Test 76", "username": "Test_76" }, { "id": 78, "name": "Test 77", "username": "Test_77" }, { "id": 79, "name": "Test 78", "username": "Test_78" }, { "id": 80, "name": "Test 79", "username": "Test_79" }, { "id": 81, "name": "Test 80", "username": "Test_80" }, { "id": 82, "name": "Test 81", "username": "Test_81" }, { "id": 83, "name": "Test 82", "username": "Test_82" }, { "id": 84, "name": "Test 83", "username": "Test_83" }, { "id": 85, "name": "Test 84", "username": "Test_84" }, { "id": 86, "name": "Test 85", "username": "Test_85" }, { "id": 87, "name": "Test 86", "username": "Test_86" }, { "id": 88, "name": "Test 87", "username": "Test_87" }, { "id": 89, "name": "Test 88", "username": "Test_88" }, { "id": 90, "name": "Test 89", "username": "Test_89" }, { "id": 91, "name": "Test 90", "username": "Test_90" }, { "id": 92, "name": "Test 91", "username": "Test_91" }, { "id": 93, "name": "Test 92", "username": "Test_92" }, { "id": 94, "name": "Test 93", "username": "Test_93" }, { "id": 95, "name": "Test 94", "username": "Test_94" }, { "id": 96, "name": "Test 95", "username": "Test_95" }, { "id": 97, "name": "Test 96", "username": "Test_96" }, { "id": 98, "name": "Test 97", "username": "Test_97" }, { "id": 99, "name": "Test 98", "username": "Test_98" }, { "id": 100, "name": "Test 99", "username": "Test_99" }], "owners": [{ "id": 101, "name": "Onwer 0", "username": "Owner_0" }, { "id": 102, "name": "Onwer 1", "username": "Owner_1" }, { "id": 103, "name": "Onwer 2", "username": "Owner_2" }, { "id": 104, "name": "Onwer 3", "username": "Owner_3" }, { "id": 105, "name": "Onwer 4", "username": "Owner_4" }, { "id": 106, "name": "Onwer 5", "username": "Owner_5" }, { "id": 107, "name": "Onwer 6", "username": "Owner_6" }], "meta": { "total": 107, "limit": 0, "offset": 0 } } ================================================ FILE: spec/fixtures/new.json ================================================ { "users": [ { "id": 1, "username": "test_user", "avatar_template": "//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon" } ], "topic_list": { "can_create_topic": true, "draft": null, "draft_key": "new_topic", "draft_sequence": 0, "topics": [ { "id": 1, "title": "Test topic #1", "fancy_title": "Test topic #1", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-11-07T03:02:03.615-05:00", "last_posted_at": "2013-11-07T09:42:57.094-05:00", "bumped": true, "bumped_at": "2013-11-07T09:42:57.094-05:00", "unseen": true, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 20, "like_count": 1, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 2, "title": "Test topic #2", "fancy_title": "Test topic #2", "slug": "test-topic-2", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-11-06T12:21:37.811-05:00", "last_posted_at": "2013-11-07T05:41:55.171-05:00", "bumped": true, "bumped_at": "2013-11-07T05:41:55.171-05:00", "unseen": true, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 50, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "cowboytomash", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 3, "title": "Test topic #3", "fancy_title": "Test topic #3", "slug": "test-topic-3", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-11-07T02:43:19.243-05:00", "last_posted_at": "2013-11-07T02:43:19.389-05:00", "bumped": true, "bumped_at": "2013-11-07T02:43:19.389-05:00", "unseen": true, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 14, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "akshar", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] } ] } } ================================================ FILE: spec/fixtures/notification_success.json ================================================ { "success": "OK" } ================================================ FILE: spec/fixtures/notifications.json ================================================ [ { "notification_type": 9, "read": false, "created_at": "2014-04-02T11:58:00.670-04:00", "post_number": 2, "topic_id": 14404, "slug": "json-feed-for-notifications", "data": { "topic_title": ".json feed for notifications", "original_post_id": 47600, "original_username": "riking", "display_username": "riking" } } ] ================================================ FILE: spec/fixtures/polls_toggle_status.json ================================================ { "poll": { "name": "poll", "type": "regular", "status": "closed", "public": true, "results": "always", "options": [ { "id": "8b4736b1ae3dfb5a28088530f036f9e5", "html": "I will attend the Apr 25, 2019 Launchpad meeting.", "votes": 1 }, { "id": "e09884fbf9b72d83e804050078847643", "html": "I want to lead the meeting.", "votes": 0 }, { "id": "e539a9df8700d0d05c69356a07b768cf", "html": "I’ll not attend this meeting.", "votes": 2 } ], "voters": 3, "preloaded_voters": { "8b4736b1ae3dfb5a28088530f036f9e5": [ { "id": 17, "username": "Kim_Miller", "name": "Kim Miller", "avatar_template": "/user_avatar/discourse.gomomentum.org/user_one/{size}/3220_2.png", "title": "New Owners Council" } ], "e539a9df8700d0d05c69356a07b768cf": [ { "id": 356, "username": "David_Ashby", "name": "David Ashby", "avatar_template": "/user_avatar/discourse.gomomentum.org/user_two/{size}/2403_2.png", "title": "Team Agape" }, { "id": 72, "username": "David_Kirk", "name": "David Kirk", "avatar_template": "/user_avatar/discourse.gomomentum.org/user_three/{size}/236_2.png", "title": "Foundry" } ] } } } ================================================ FILE: spec/fixtures/polls_vote.json ================================================ { "poll": { "name": "poll", "type": "regular", "status": "open", "public": true, "results": "always", "options": [ { "id": "8b4736b1ae3dfb5a28088530f036f9e5", "html": "I will attend the Apr 25, 2019 Launchpad meeting.", "votes": 2 }, { "id": "e09884fbf9b72d83e804050078847643", "html": "I want to lead the meeting.", "votes": 0 }, { "id": "e539a9df8700d0d05c69356a07b768cf", "html": "I’ll not attend this meeting.", "votes": 2 } ], "voters": 4, "preloaded_voters": { "8b4736b1ae3dfb5a28088530f036f9e5": [ { "id": 17, "username": "Kim_Miller", "name": "Kim Miller", "avatar_template": "/user_avatar/discourse.gomomentum.org/kim_miller/{size}/3220_2.png", "title": "New Owners Council" }, { "id": 150, "username": "KM_Admin", "name": "Kim Miller", "avatar_template": "/user_avatar/discourse.gomomentum.org/km_admin/{size}/1232_2.png", "title": "Admin" } ], "e539a9df8700d0d05c69356a07b768cf": [ { "id": 356, "username": "David_Ashby", "name": "David Ashby", "avatar_template": "/user_avatar/discourse.gomomentum.org/david_ashby/{size}/2403_2.png", "title": "Team Agape" }, { "id": 72, "username": "David_Kirk", "name": "David Kirk", "avatar_template": "/user_avatar/discourse.gomomentum.org/david_kirk/{size}/236_2.png", "title": "Foundry" } ] } }, "vote": [ "8b4736b1ae3dfb5a28088530f036f9e5" ] } ================================================ FILE: spec/fixtures/polls_voters.json ================================================ { "voters": { "e539a9df8700d0d05c69356a07b768cf": [ { "id": 356, "username": "David_Ashby", "name": "David Ashby", "avatar_template": "/user_avatar/discourse.gomomentum.org/david_ashby/{size}/2403_2.png", "title": "Team Agape" }, { "id": 72, "username": "David_Kirk", "name": "David Kirk", "avatar_template": "/user_avatar/discourse.gomomentum.org/david_kirk/{size}/236_2.png", "title": "Foundry" } ] } } ================================================ FILE: spec/fixtures/post.json ================================================ { "id": 11, "name": "system", "username": "system", "avatar_template": "/user_avatar/localhost/system/{size}/1.png", "uploaded_avatar_id": 1, "created_at": "2014-10-31T04:37:32.553Z", "cooked": "

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\n\n

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.

", "post_number": 1, "post_type": 1, "updated_at": "2014-10-31T04:37:32.553Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 8, "topic_slug": "welcome-to-discourse", "topic_auto_close_at": null, "display_username": "system", "primary_group_name": null, "version": 1, "can_edit": true, "can_delete": false, "can_recover": true, "user_title": null, "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.", "actions_summary": [ { "id": 2, "count": 0, "hidden": false, "can_act": true, "can_defer_flags": false }, { "id": 3, "count": 0, "hidden": false, "can_act": true, "can_defer_flags": false }, { "id": 4, "count": 0, "hidden": false, "can_act": true, "can_defer_flags": false }, { "id": 5, "count": 0, "hidden": true, "can_act": true, "can_defer_flags": false }, { "id": 6, "count": 0, "hidden": false, "can_act": true, "can_defer_flags": false }, { "id": 7, "count": 0, "hidden": false, "can_act": true, "can_defer_flags": false }, { "id": 8, "count": 0, "hidden": false, "can_act": true, "can_defer_flags": false } ], "moderator": true, "admin": true, "staff": true, "user_id": -1, "hidden": false, "hidden_reason_id": null, "trust_level": 4, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false } ================================================ FILE: spec/fixtures/post_action_users.json ================================================ { "post_action_users": [ { "id": 1286, "username": "john_smith", "avatar_template": "/user_avatar/domain/john_smith/size/1609_1.png", "post_url": null, "username_lower": "john_smith" }, { "id": 1, "username": "jane_smith", "avatar_template": "/user_avatar/domain/jane_smith/size/17_1.png", "post_url": null, "username_lower": "jane_smith" } ] } ================================================ FILE: spec/fixtures/posts_before.json ================================================ { "latest_posts": [ { "id": 14, "name": null, "username": "johndoe", "avatar_template": "/letter_avatar_proxy/v3/letter/p/df705f/{size}.png", "created_at": "2020-01-25T00:58:35.475Z", "cooked": "

hi there. hi there. hi there.

", "post_number": 1, "post_type": 1, "updated_at": "2020-01-25T00:58:35.475Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 11, "topic_slug": "this-is-an-uncategorized-title", "topic_title": "This is an uncategorized title", "topic_html_title": "This is an uncategorized title", "category_id": 1, "display_username": null, "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "raw": "hi there. hi there. hi there.", "actions_summary": [], "moderator": false, "admin": true, "staff": true, "user_id": 1, "hidden": false, "trust_level": 1, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false }, { "id": 10, "name": "system", "username": "system", "avatar_template": "/user_avatar/localhost/system/{size}/1_2.png", "created_at": "2020-01-24T23:26:09.014Z", "cooked": "

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

Edit this into a brief description of your community:

\n\n

\n

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.

", "post_number": 1, "post_type": 1, "updated_at": "2020-01-24T23:26:09.014Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 7, "topic_slug": "welcome-to-discourse", "topic_title": "Welcome to Discourse", "topic_html_title": "Welcome to Discourse", "category_id": 1, "display_username": "system", "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "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\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.", "actions_summary": [], "moderator": true, "admin": true, "staff": true, "user_id": -1, "hidden": false, "trust_level": 4, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false }, { "id": 2, "name": "system", "username": "system", "avatar_template": "/user_avatar/localhost/system/{size}/1_2.png", "created_at": "2020-01-24T23:26:05.551Z", "cooked": "

Discussion about this site, its organization, how it works, and how we can improve it.

", "post_number": 1, "post_type": 1, "updated_at": "2020-01-24T23:26:05.551Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 2, "topic_slug": "about-the-site-feedback-category", "topic_title": "About the Site Feedback category", "topic_html_title": "About the Site Feedback category", "category_id": 3, "display_username": "system", "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "raw": "Discussion about this site, its organization, how it works, and how we can improve it.", "actions_summary": [], "moderator": true, "admin": true, "staff": true, "user_id": -1, "hidden": false, "trust_level": 4, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false } ] } ================================================ FILE: spec/fixtures/posts_latest.json ================================================ { "latest_posts": [ { "id": 15, "name": null, "username": "johndoe", "avatar_template": "/letter_avatar_proxy/v3/letter/p/df705f/{size}.png", "created_at": "2020-01-25T02:10:07.971Z", "cooked": "

holla bullhonky.

", "post_number": 2, "post_type": 1, "updated_at": "2020-01-25T02:10:07.971Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 11, "topic_slug": "this-is-an-uncategorized-title", "topic_title": "This is an uncategorized title", "topic_html_title": "This is an uncategorized title", "category_id": 1, "display_username": null, "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "raw": "holla bullhonky.", "actions_summary": [], "moderator": false, "admin": true, "staff": true, "user_id": 1, "hidden": false, "trust_level": 1, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false }, { "id": 14, "name": null, "username": "johndoe", "avatar_template": "/letter_avatar_proxy/v3/letter/p/df705f/{size}.png", "created_at": "2020-01-25T00:58:35.475Z", "cooked": "

hi there. hi there. hi there.

", "post_number": 1, "post_type": 1, "updated_at": "2020-01-25T00:58:35.475Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 11, "topic_slug": "this-is-an-uncategorized-title", "topic_title": "This is an uncategorized title", "topic_html_title": "This is an uncategorized title", "category_id": 1, "display_username": null, "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "raw": "hi there. hi there. hi there.", "actions_summary": [], "moderator": false, "admin": true, "staff": true, "user_id": 1, "hidden": false, "trust_level": 1, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false }, { "id": 10, "name": "system", "username": "system", "avatar_template": "/user_avatar/localhost/system/{size}/1_2.png", "created_at": "2020-01-24T23:26:09.014Z", "cooked": "

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

Edit this into a brief description of your community:

\n\n

\n

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.

", "post_number": 1, "post_type": 1, "updated_at": "2020-01-24T23:26:09.014Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 7, "topic_slug": "welcome-to-discourse", "topic_title": "Welcome to Discourse", "topic_html_title": "Welcome to Discourse", "category_id": 1, "display_username": "system", "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "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\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.", "actions_summary": [], "moderator": true, "admin": true, "staff": true, "user_id": -1, "hidden": false, "trust_level": 4, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false }, { "id": 2, "name": "system", "username": "system", "avatar_template": "/user_avatar/localhost/system/{size}/1_2.png", "created_at": "2020-01-24T23:26:05.551Z", "cooked": "

Discussion about this site, its organization, how it works, and how we can improve it.

", "post_number": 1, "post_type": 1, "updated_at": "2020-01-24T23:26:05.551Z", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": null, "incoming_link_count": 0, "reads": 1, "score": 0.2, "yours": false, "topic_id": 2, "topic_slug": "about-the-site-feedback-category", "topic_title": "About the Site Feedback category", "topic_html_title": "About the Site Feedback category", "category_id": 3, "display_username": "system", "primary_group_name": null, "primary_group_flair_url": null, "primary_group_flair_bg_color": null, "primary_group_flair_color": null, "version": 1, "can_edit": false, "can_delete": false, "can_recover": false, "can_wiki": false, "user_title": null, "raw": "Discussion about this site, its organization, how it works, and how we can improve it.", "actions_summary": [], "moderator": true, "admin": true, "staff": true, "user_id": -1, "hidden": false, "trust_level": 4, "deleted_at": null, "user_deleted": false, "edit_reason": null, "can_view_edit_history": true, "wiki": false } ] } ================================================ FILE: spec/fixtures/private_messages.json ================================================ { "users": [ { "id": 1, "username": "test_user", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/test_user/{size}/2.png" }, { "id": 2, "username": "batman", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/batman/{size}/3.png" } ], "topic_list": { "can_create_topic": true, "draft": null, "draft_key": "new_topic", "draft_sequence": 1, "topics": [ { "id": 11, "title": "Welcome to Discourse!", "fancy_title": "Welcome to Discourse!", "slug": "welcome-to-discourse", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": "http://www.discourse.org/images/welcome/progress-bar.png", "created_at": "2014-09-13T10:27:19.714-04:00", "last_posted_at": "2014-09-13T10:27:20.140-04:00", "bumped": true, "bumped_at": "2014-09-13T10:27:20.140-04:00", "unseen": false, "last_read_post_number": 1, "unread": 0, "new_posts": 0, "pinned": false, "unpinned": null, "visible": true, "closed": false, "archived": false, "notification_level": 3, "views": 1, "like_count": 0, "starred": false, "has_summary": false, "archetype": "private_message", "last_poster_username": "test_user", "category_id": null, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ], "participants": [ { "extras": null, "description": null, "user_id": 2 } ] } ] } } ================================================ FILE: spec/fixtures/regenerate_api_key.json ================================================ { "api_key": { "id": 10, "key": "d8a80230fd14531c9b4809c7800b1fe78073a3771d3d2a56a5671bace55bc393", "user": null } } ================================================ FILE: spec/fixtures/replies.json ================================================ { "user_actions": [ { "action_type": 5, "created_at": "2016-06-29T20:27:55.767Z", "excerpt": "Test Reply #1", "avatar_template": "/letter_avatar_proxy/v2/letter/s/71c47a/{size}.png", "acting_avatar_template": "/letter_avatar_proxy/v2/letter/s/71c47a/{size}.png", "slug": "test-topic", "topic_id": 39, "target_user_id": 11, "target_name": "Test User", "target_username": "testuser", "post_number": 3, "post_id": 48, "reply_to_post_number": 2, "username": "testuser", "name": "Test User", "user_id": 11, "acting_username": "testuser", "acting_name": "Test User", "acting_user_id": 11, "title": "Test Topic", "deleted": false, "hidden": false, "post_type": 1, "action_code": null, "category_id": 9, "closed": false, "archived": false } ] } ================================================ FILE: spec/fixtures/replies_and_topics.json ================================================ { "user_actions": [ { "action_type": 5, "created_at": "2016-08-10T14:52:49.660Z", "excerpt": "test excerpt", "avatar_template": "/user_avatar/localhost/avatar/{size}/59_1.png", "acting_avatar_template": "/user_avatar/localhost/avatar/{size}/59_1.png", "slug": "test-topic", "topic_id": 11, "target_user_id": 2, "target_name": "Test User", "target_username": "testuser", "post_number": 2, "post_id": 19, "reply_to_post_number": null, "username": "testuser", "name": "Test User", "user_id": 2, "acting_username": "testuser", "acting_name": "Test User", "acting_user_id": 2, "title": "Test Topic", "deleted": false, "hidden": false, "post_type": 1, "action_code": null, "category_id": 1, "closed": false, "archived": false }, { "action_type": 4, "created_at": "2016-04-28T17:31:52.527Z", "excerpt": "test reply", "avatar_template": "/user_avatar/localhost/pjdavis1/{size}/59_1.png", "acting_avatar_template": "/user_avatar/localhost/pjdavis1/{size}/59_1.png", "slug": "test-topic", "topic_id": 11, "target_user_id": 2, "target_name": "Test User", "target_username": "testuser", "post_number": 1, "post_id": null, "username": "testuser", "name": "Test User", "user_id": 2, "acting_username": "testuser", "acting_name": "Test User", "acting_user_id": 2, "title": "Test Topic", "deleted": false, "hidden": null, "post_type": null, "action_code": null, "category_id": 1, "closed": false, "archived": false } ] } ================================================ FILE: spec/fixtures/retrieve_invite.json ================================================ { "id": 26, "invite_key": "CGUHjNC4Na", "link": "http://localhost:3000/invites/CGUHjNC4Na", "email": "foo@bar.com", "emailed": true, "custom_message": null, "created_at": "2021-05-07T20:48:14.278Z", "updated_at": "2021-05-07T20:48:14.278Z", "expires_at": "2021-06-06T20:48:14.278Z", "expired": false, "topics": [ { "id": 1, "title": "About the Site Feedback category", "fancy_title": "About the Site Feedback category", "slug": "about-the-site-feedback-category", "posts_count": 1 } ], "groups": [ { "id": 1, "automatic": true, "name": "admins", "display_name": "admins", "user_count": 1, "mentionable_level": 0, "messageable_level": 0, "visibility_level": 1, "primary_group": false, "title": null, "grant_trust_level": null, "incoming_email": null, "has_messages": true, "flair_url": null, "flair_bg_color": null, "flair_color": null, "bio_raw": "", "bio_cooked": null, "bio_excerpt": null, "public_admission": false, "public_exit": false, "allow_membership_requests": false, "full_name": null, "default_notification_level": 3, "membership_request_template": null, "members_visibility_level": 0, "can_see_members": true, "can_admin_group": true, "publish_read_state": false }, { "id": 2, "automatic": true, "name": "moderators", "display_name": "moderators", "user_count": 1, "mentionable_level": 0, "messageable_level": 99, "visibility_level": 1, "primary_group": false, "title": null, "grant_trust_level": null, "incoming_email": null, "has_messages": true, "flair_url": null, "flair_bg_color": null, "flair_color": null, "bio_raw": null, "bio_cooked": null, "bio_excerpt": null, "public_admission": false, "public_exit": false, "allow_membership_requests": false, "full_name": null, "default_notification_level": 2, "membership_request_template": null, "members_visibility_level": 0, "can_see_members": true, "can_admin_group": true, "publish_read_state": false }, { "id": 3, "automatic": true, "name": "staff", "display_name": "staff", "user_count": 2, "mentionable_level": 0, "messageable_level": 0, "visibility_level": 1, "primary_group": false, "title": null, "grant_trust_level": null, "incoming_email": null, "has_messages": false, "flair_url": null, "flair_bg_color": null, "flair_color": null, "bio_raw": null, "bio_cooked": null, "bio_excerpt": null, "public_admission": false, "public_exit": false, "allow_membership_requests": false, "full_name": null, "default_notification_level": 3, "membership_request_template": null, "members_visibility_level": 0, "can_see_members": true, "can_admin_group": true, "publish_read_state": false } ] } ================================================ FILE: spec/fixtures/search.json ================================================ [ { "type": "topic", "name": "Topics", "more": true, "results": [ { "id": 1, "title": "Test topic #1", "url": "/t/test-topic-1/1" }, { "id": 2, "title": "Test topic #2", "url": "/t/test-topic-2/2" }, { "id": 3, "title": "Test topic #3", "url": "/t/test-topic-3/3" } ] } ] ================================================ FILE: spec/fixtures/top.json ================================================ { "topic_list": { "can_create_topic": false, "more_topics_url": "/latest.json?page=1", "draft": null, "draft_key": "new_topic", "draft_sequence": null, "topics": [ { "id": 1, "title": "Test topic #1", "fancy_title": "Test topic #1", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T15:26:25.977-05:00", "last_posted_at": "2013-02-05T12:14:17.364-05:00", "bumped": true, "bumped_at": "2013-02-07T13:43:11.191-05:00", "unseen": false, "pinned": true, "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…", "visible": true, "closed": false, "archived": false, "views": 268, "like_count": 14, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 1, "title": "Test topic #2", "fancy_title": "Test topic #2", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 2, "image_url": null, "created_at": "2013-11-06T13:57:47.984-05:00", "last_posted_at": "2013-11-06T13:57:48.111-05:00", "bumped": true, "bumped_at": "2013-11-06T13:57:48.111-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 1, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] }, { "id": 1, "title": "Test topic #3", "fancy_title": "Test topic #3", "slug": "test-topic-3", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-02-04T21:46:58.194-05:00", "last_posted_at": "2013-11-06T13:56:27.753-05:00", "bumped": true, "bumped_at": "2013-11-06T13:56:27.753-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 82, "like_count": 10, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] } ] } } ================================================ FILE: spec/fixtures/topic.json ================================================ { "post_stream": { "posts": [ { "id": 83, "name": "Sam Saffron", "username": "samsaffron", "avatar_template": "//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon", "created_at": "2013-02-04T15:26:26.278-05:00", "cooked": "

Welcome!

\n\n

Try is a sandbox, a safe place to play with Discourse and its features.

\n\n

This site is reset every day (or even more often). Any accounts or posts you create here will not exist tomorrow!

\n\n

Feel free to experiment here in the sandbox, but always be civil and respectful -- and don't post anything here you wouldn't want lost forever!

\n\n

If you see anything here that is out of bounds, please flag it. Help keep our community clean. We monitor the flags regularly.

\n\n", "post_number": 1, "post_type": 1, "updated_at": "2013-04-30T14:33:37.273-04:00", "reply_count": 1, "reply_to_post_number": null, "quote_count": 0, "avg_time": 17, "incoming_link_count": 0, "reads": 34, "score": 12.65, "yours": false, "topic_slug": "this-site-is-a-sandbox-it-is-reset-every-day", "topic_id": 57, "display_username": "Sam Saffron", "version": 5, "can_edit": null, "can_delete": null, "can_recover": false, "link_counts": [ { "url": "http://meta.discourse.org", "internal": false, "reflection": false, "clicks": 7 }, { "url": "http://www.discourse.org", "internal": false, "reflection": false, "clicks": 4 }, { "url": "https://github.com/discourse/discourse", "internal": false, "reflection": false, "clicks": 0 } ], "read": false, "user_title": null, "actions_summary": [ { "id": 2, "count": 0, "hidden": false, "can_act": null }, { "id": 3, "count": 0, "hidden": false, "can_act": null }, { "id": 4, "count": 0, "hidden": false, "can_act": null }, { "id": 5, "count": 0, "hidden": true, "can_act": null }, { "id": 6, "count": 0, "hidden": false, "can_act": null }, { "id": 7, "count": 0, "hidden": false, "can_act": null }, { "id": 8, "count": 0, "hidden": false, "can_act": null } ], "moderator": false, "staff": true, "user_id": 15, "hidden": false, "hidden_reason_id": null, "trust_level": 1, "deleted_at": null, "user_deleted": false }, { "id": 84, "name": "Sam Saffron", "username": "samsaffron", "avatar_template": "//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon", "created_at": "2013-02-04T15:26:32.362-05:00", "cooked": "

This topic is now pinned. It will appear at the top of its category.

", "post_number": 2, "post_type": 2, "updated_at": "2013-02-04T15:26:32.362-05:00", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": 18, "incoming_link_count": 0, "reads": 34, "score": 7.7, "yours": false, "topic_slug": "this-site-is-a-sandbox-it-is-reset-every-day", "topic_id": 57, "display_username": "Sam Saffron", "version": 1, "can_edit": null, "can_delete": null, "can_recover": false, "read": false, "user_title": null, "actions_summary": [ { "id": 2, "count": 0, "hidden": false, "can_act": null }, { "id": 3, "count": 0, "hidden": false, "can_act": null }, { "id": 4, "count": 0, "hidden": false, "can_act": null }, { "id": 5, "count": 0, "hidden": true, "can_act": null }, { "id": 6, "count": 0, "hidden": false, "can_act": null }, { "id": 7, "count": 0, "hidden": false, "can_act": null }, { "id": 8, "count": 0, "hidden": false, "can_act": null } ], "moderator": false, "staff": true, "user_id": 15, "hidden": false, "hidden_reason_id": null, "trust_level": 1, "deleted_at": null, "user_deleted": false }, { "id": 86, "name": "Adam Davis", "username": "stienman", "avatar_template": "//www.gravatar.com/avatar/281486f2a20201375414760dd347951d.png?s={size}&r=pg&d=identicon", "created_at": "2013-02-04T15:29:00.843-05:00", "cooked": "

\"image\"

", "post_number": 3, "post_type": 1, "updated_at": "2013-02-04T15:29:00.843-05:00", "reply_count": 0, "reply_to_post_number": null, "quote_count": 0, "avg_time": 19, "incoming_link_count": 2, "reads": 34, "score": 152.75, "yours": false, "topic_slug": "this-site-is-a-sandbox-it-is-reset-every-day", "topic_id": 57, "display_username": "Adam Davis", "version": 1, "can_edit": null, "can_delete": null, "can_recover": false, "read": false, "user_title": null, "actions_summary": [ { "id": 2, "count": 5, "hidden": false, "can_act": null }, { "id": 3, "count": 0, "hidden": false, "can_act": null }, { "id": 4, "count": 0, "hidden": false, "can_act": null }, { "id": 5, "count": 0, "hidden": true, "can_act": null }, { "id": 6, "count": 0, "hidden": false, "can_act": null }, { "id": 7, "count": 0, "hidden": false, "can_act": null }, { "id": 8, "count": 0, "hidden": false, "can_act": null } ], "moderator": false, "staff": true, "user_id": 23, "hidden": false, "hidden_reason_id": null, "trust_level": 1, "deleted_at": null, "user_deleted": false }, { "id": 226, "name": "Stefan Plattner", "username": "splattne", "avatar_template": "//www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon", "created_at": "2013-02-05T12:14:17.364-05:00", "cooked": "

What? So I have to post this every day?
\"image\"

", "post_number": 14, "post_type": 1, "updated_at": "2013-02-05T12:14:17.364-05:00", "reply_count": 0, "reply_to_post_number": 1, "quote_count": 0, "avg_time": 16, "incoming_link_count": 0, "reads": 22, "score": 170.2, "yours": false, "topic_slug": "this-site-is-a-sandbox-it-is-reset-every-day", "topic_id": 57, "display_username": "Stefan Plattner", "version": 1, "can_edit": null, "can_delete": null, "can_recover": false, "read": false, "user_title": null, "reply_to_user": { "username": "samsaffron", "avatar_template": "//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon" }, "actions_summary": [ { "id": 2, "count": 5, "hidden": false, "can_act": null }, { "id": 3, "count": 0, "hidden": false, "can_act": null }, { "id": 4, "count": 0, "hidden": false, "can_act": null }, { "id": 5, "count": 0, "hidden": true, "can_act": null }, { "id": 6, "count": 0, "hidden": false, "can_act": null }, { "id": 7, "count": 0, "hidden": false, "can_act": null }, { "id": 8, "count": 0, "hidden": false, "can_act": null } ], "moderator": false, "staff": true, "user_id": 50, "hidden": false, "hidden_reason_id": null, "trust_level": 1, "deleted_at": null, "user_deleted": false } ], "stream": [ 83, 84, 86, 226 ] }, "draft": null, "draft_key": "topic_57", "draft_sequence": null, "pinned": true, "details": { "auto_close_at": null, "created_by": { "id": 15, "username": "samsaffron", "avatar_template": "//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon" }, "last_poster": { "id": 50, "username": "splattne", "avatar_template": "//www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon" }, "participants": [ { "id": 15, "username": "samsaffron", "avatar_template": "//www.gravatar.com/avatar/3dcae8378d46c244172a115c28ca49ce.png?s={size}&r=pg&d=identicon", "post_count": 2 }, { "id": 23, "username": "stienman", "avatar_template": "//www.gravatar.com/avatar/281486f2a20201375414760dd347951d.png?s={size}&r=pg&d=identicon", "post_count": 1 }, { "id": 50, "username": "splattne", "avatar_template": "//www.gravatar.com/avatar/7847006dbf49f1722b07c8da396f1275.png?s={size}&r=pg&d=identicon", "post_count": 1 } ], "suggested_topics": [ { "id": 62, "title": "Funny pictures (Keep 'em clean, folks!)", "fancy_title": "Funny pictures (Keep ‘em clean, folks!)", "slug": "funny-pictures-keep-em-clean-folks", "posts_count": 39, "reply_count": 3, "highest_post_number": 39, "image_url": "http://cdn.discourse.org/uploads/try2_discourse/27/nt3b8.jpeg", "created_at": "2013-02-04T15:42:30.290-05:00", "last_posted_at": "2013-11-06T02:47:27.135-05:00", "bumped": true, "bumped_at": "2013-11-06T03:02:06.494-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "archetype": "regular", "like_count": 10, "views": 186, "category": { "id": 13, "name": "uncategPorized", "color": "AB9364", "text_color": "FFFFFF", "slug": "uncategorized", "topic_count": 21, "description": "", "topic_url": null, "hotness": 5, "read_restricted": false, "permission": null, "available_groups": [ "admins", "everyone", "moderators", "staff", "trust_level_1", "trust_level_2", "trust_level_3", "trust_level_4", "trust_level_5" ], "auto_close_days": null, "group_permissions": [ { "permission_type": 1, "group_name": "everyone" } ], "position": 1 } }, { "id": 152, "title": "What are good resolutions to the enviroment?", "fancy_title": "What are good resolutions to the enviroment?", "slug": "what-are-good-resolutions-to-the-enviroment", "posts_count": 3, "reply_count": 0, "highest_post_number": 3, "image_url": null, "created_at": "2013-11-05T02:12:37.113-05:00", "last_posted_at": "2013-11-05T11:32:31.259-05:00", "bumped": true, "bumped_at": "2013-11-05T11:32:31.259-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "archetype": "regular", "like_count": 0, "views": 50, "category": { "id": 13, "name": "uncategorized", "color": "AB9364", "text_color": "FFFFFF", "slug": "uncategorized", "topic_count": 21, "description": "", "topic_url": null, "hotness": 5, "read_restricted": false, "permission": null, "available_groups": [ "admins", "everyone", "moderators", "staff", "trust_level_1", "trust_level_2", "trust_level_3", "trust_level_4", "trust_level_5" ], "auto_close_days": null, "group_permissions": [ { "permission_type": 1, "group_name": "everyone" } ], "position": 1 } }, { "id": 87, "title": "Hilarious Picture Dump (Image Heavy)", "fancy_title": "Hilarious Picture Dump (Image Heavy)", "slug": "hilarious-picture-dump-image-heavy", "posts_count": 4, "reply_count": 0, "highest_post_number": 4, "image_url": "http://cdn.discourse.org/uploads/try2_discourse/33/1208514086be7cd9d27c00836da43ab15d00ec7be_4c256628scaled.jpeg", "created_at": "2013-02-04T21:07:51.246-05:00", "last_posted_at": "2013-02-05T12:52:47.243-05:00", "bumped": true, "bumped_at": "2013-06-26T01:45:24.590-04:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "archetype": "regular", "like_count": 0, "views": 69, "category": { "id": 13, "name": "uncategorized", "color": "AB9364", "text_color": "FFFFFF", "slug": "uncategorized", "topic_count": 21, "description": "", "topic_url": null, "hotness": 5, "read_restricted": false, "permission": null, "available_groups": [ "admins", "everyone", "moderators", "staff", "trust_level_1", "trust_level_2", "trust_level_3", "trust_level_4", "trust_level_5" ], "auto_close_days": null, "group_permissions": [ { "permission_type": 1, "group_name": "everyone" } ], "position": 1 } }, { "id": 197, "title": "Replying as a new topic", "fancy_title": "Replying as a new topic", "slug": "replying-as-a-new-topic", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-11-06T00:57:00.177-05:00", "last_posted_at": "2013-11-06T00:57:00.326-05:00", "bumped": true, "bumped_at": "2013-11-06T00:57:00.326-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "archetype": "regular", "like_count": 0, "views": 23, "category": { "id": 13, "name": "uncategorized", "color": "AB9364", "text_color": "FFFFFF", "slug": "uncategorized", "topic_count": 21, "description": "", "topic_url": null, "hotness": 5, "read_restricted": false, "permission": null, "available_groups": [ "admins", "everyone", "moderators", "staff", "trust_level_1", "trust_level_2", "trust_level_3", "trust_level_4", "trust_level_5" ], "auto_close_days": null, "group_permissions": [ { "permission_type": 1, "group_name": "everyone" } ], "position": 1 } }, { "id": 157, "title": "What is your opinion on the movie hunger game?", "fancy_title": "What is your opinion on the movie hunger game?", "slug": "what-is-your-opinion-on-the-movie-hunger-game", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-11-05T03:59:13.194-05:00", "last_posted_at": "2013-11-05T03:59:13.343-05:00", "bumped": true, "bumped_at": "2013-11-05T03:59:13.343-05:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "archetype": "regular", "like_count": 1, "views": 23, "category": { "id": 13, "name": "uncategorized", "color": "AB9364", "text_color": "FFFFFF", "slug": "uncategorized", "topic_count": 21, "description": "", "topic_url": null, "hotness": 5, "read_restricted": false, "permission": null, "available_groups": [ "admins", "everyone", "moderators", "staff", "trust_level_1", "trust_level_2", "trust_level_3", "trust_level_4", "trust_level_5" ], "auto_close_days": null, "group_permissions": [ { "permission_type": 1, "group_name": "everyone" } ], "position": 1 } } ], "links": [ { "url": "http://meta.discourse.org", "title": null, "fancy_title": null, "internal": false, "reflection": false, "clicks": "7", "user_id": 15 }, { "url": "http://www.discourse.org", "title": null, "fancy_title": null, "internal": false, "reflection": false, "clicks": "4", "user_id": 15 }, { "url": "https://github.com/discourse/discourse", "title": null, "fancy_title": null, "internal": false, "reflection": false, "clicks": "0", "user_id": 15 }, { "url": "/users/pekka.gaiser", "title": null, "fancy_title": null, "internal": true, "reflection": false, "clicks": "0", "user_id": 9 }, { "url": "http://dev.discourse.org/t/general-bugs-list/92/365)", "title": null, "fancy_title": null, "internal": false, "reflection": false, "clicks": "0", "user_id": 9 }, { "url": "/users/pekka", "title": null, "fancy_title": null, "internal": true, "reflection": false, "clicks": "0", "user_id": 9 } ] }, "highest_post_number": 14, "deleted_by": null, "id": 57, "title": "THIS SITE IS A SANDBOX -- it is reset every day", "fancy_title": "THIS SITE IS A SANDBOX – it is reset every day", "posts_count": 4, "created_at": "2013-02-04T15:26:25.977-05:00", "views": 268, "reply_count": 9, "last_posted_at": "2013-02-05T12:14:17.364-05:00", "visible": true, "closed": false, "archived": false, "has_best_of": false, "archetype": "regular", "slug": "this-site-is-a-sandbox-it-is-reset-every-day", "category_id": 13, "deleted_at": null } ================================================ FILE: spec/fixtures/topic_invite_user.json ================================================ { "success": true } ================================================ FILE: spec/fixtures/topic_posts.json ================================================ { "post_stream":{ "posts":[ { "id":123456, "name":"Someones Name", "username":"Some-username", "avatar_template":"https://avatars.discourse.org/v4/letter/w/abcd123/{size}.png", "created_at":"2020-01-27T11:32:03.646Z", "cooked":"

Some text.

", "post_number":2, "post_type":1, "updated_at":"2020-01-27T11:32:03.646Z", "reply_count":0, "reply_to_post_number":null, "quote_count":0, "incoming_link_count":0, "reads":2, "readers_count":1, "score":45.2, "yours":false, "topic_id":57, "topic_slug":"some-topic-slug", "display_username":"Someones Name", "primary_group_name":null, "primary_group_flair_url":null, "primary_group_flair_bg_color":null, "primary_group_flair_color":null, "version":1, "can_edit":true, "can_delete":true, "can_recover":false, "can_wiki":true, "read":true, "user_title":null, "actions_summary":[{"id":2,"count":3,"can_act":true}], "raw": [{"type":"paragraph","children":[{"text":"This is a raw post I've got white space!... and emojis 😈😈😈😈😈😈"}]}], "moderator":false, "admin":false, "staff":false, "user_id":123, "hidden":false, "trust_level":3, "deleted_at":null, "user_deleted":false, "edit_reason":null, "can_view_edit_history":true, "wiki":false, "reviewable_id":0, "reviewable_score_count":0, "reviewable_score_pending_count":0, "user_created_at":"2018-01-10T21:29:15.554Z", "user_date_of_birth":null, "can_accept_answer":false, "can_unaccept_answer":false, "accepted_answer":false } ] }, "id":57 } ================================================ FILE: spec/fixtures/topics_created_by.json ================================================ { "users": [ { "id": 1, "username": "test_user", "avatar_template": "//www.gravatar.com/avatar/test.png?s={size}&r=pg&d=identicon" } ], "topic_list": { "can_create_topic": true, "draft": null, "draft_key": "new_topic", "draft_sequence": 0, "topics": [ { "id": 1, "title": "Test topic #1", "fancy_title": "Test topic #1", "slug": "test-topic-1", "posts_count": 1, "reply_count": 0, "highest_post_number": 1, "image_url": null, "created_at": "2013-07-22T19:27:14.818-04:00", "last_posted_at": "2013-07-22T19:41:49.768-04:00", "bumped": true, "bumped_at": "2013-07-22T19:41:49.768-04:00", "unseen": false, "pinned": false, "visible": true, "closed": false, "archived": false, "views": 68, "like_count": 0, "has_best_of": false, "archetype": "regular", "last_poster_username": "test_user", "category_id": 1, "posters": [ { "extras": null, "description": "Original Poster, Most Recent Poster", "user_id": 1 } ] } ] } } ================================================ FILE: spec/fixtures/update_trust_level.json ================================================ { "admin_user": { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2014-09-19T13:00:20.939Z", "last_emailed_at": "2015-01-01T01:01:24.438Z", "created_at": "2014-09-13T14:27:07.352Z", "last_seen_age": "4mon", "last_emailed_age": "13d", "created_at_age": "4mon", "username_lower": "robin", "trust_level": 2, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 3, "posts_read_count": 2, "topics_entered": 2, "post_count": 0, "can_send_activation_email": true, "can_activate": false, "can_deactivate": true, "ip_address": "127.0.0.1", "registration_ip_address": "127.0.0.1", "single_sign_on_record": null } } ================================================ FILE: spec/fixtures/upload_avatar.json ================================================ { "id": 11, "user_id": 1, "original_filename": "avatar.jpg", "filesize": 86900, "width": 360, "height": 360, "url": "/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg", "created_at": "2015-06-21T12:35:45.182Z", "updated_at": "2015-06-21T12:35:45.195Z", "sha1": "417e624d2453925e6c68748b9aa67637c284b5aa", "origin": null, "retain_hours": null } ================================================ FILE: spec/fixtures/upload_file.json ================================================ { "id": 11, "user_id": 1, "original_filename": "grumpy_cat.gif", "filesize": 86900, "width": 360, "height": 360, "url": "/uploads/default/original/1X/417e624d2453925e6c68748b9aa67637c284b5aa.jpg", "created_at": "2015-06-21T12:35:45.182Z", "updated_at": "2015-06-21T12:35:45.195Z", "sha1": "417e624d2453925e6c68748b9aa67637c284b5aa", "origin": null, "retain_hours": null } ================================================ FILE: spec/fixtures/user.json ================================================ { "user_badges": [], "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/test/{size}/2.png", "name": "Test User", "email": "test_user@example.com", "last_posted_at": null, "last_seen_at": "2014-09-04T08:22:19.370-04:00", "bio_cooked": null, "created_at": "2014-09-04T08:04:25.104-04:00", "can_edit": true, "can_edit_username": true, "can_edit_email": true, "can_edit_name": true, "stats": [ { "action_type": 13, "count": 1, "id": null } ], "can_send_private_message_to_user": true, "bio_excerpt": "
test hasn't entered anything in the About Me field of their profile yet
", "trust_level": 0, "moderator": false, "admin": false, "title": null, "badge_count": 0, "has_title_badges": false, "custom_fields": {}, "number_of_deleted_posts": 0, "number_of_flagged_posts": 0, "number_of_flags_given": 0, "number_of_suspensions": 0, "locale": null, "email_digests": true, "email_private_messages": true, "email_direct": true, "email_always": false, "digest_after_days": 7, "mailing_list_mode": false, "auto_track_topics_after_msecs": 240000, "new_topic_duration_minutes": 2880, "external_links_in_new_tab": false, "dynamic_favicon": false, "enable_quoting": true, "muted_category_ids": [], "tracked_category_ids": [], "watched_category_ids": [], "private_messages_stats": { "all": 1, "mine": 0, "unread": 0 }, "disable_jump_reply": false, "gravatar_avatar_upload_id": null, "custom_avatar_upload_id": null, "invited_by": null, "custom_groups": [], "featured_user_badge_ids": [] } } ================================================ FILE: spec/fixtures/user_activate_success.json ================================================ { "success": "OK" } ================================================ FILE: spec/fixtures/user_badges.json ================================================ { "user_badges": [ { "id": 3, "granted_at": "2014-11-30T14:27:19.404Z", "badge_id": 10, "user_id": 1, "granted_by_id": -1 }, { "id": 2, "granted_at": "2014-09-16T13:08:23.061Z", "post_id": 15, "post_number": 1, "badge_id": 5, "user_id": 1, "granted_by_id": -1, "topic_id": 12 } ], "badges": [ { "id": 10, "name": "Editor", "description": null, "grant_count": 1, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "badge_type_id": 3 }, { "id": 5, "name": "Welcome", "description": null, "grant_count": 1, "allow_title": false, "multiple_grant": false, "icon": "fa-certificate", "image": null, "listable": true, "enabled": true, "badge_grouping_id": 2, "system": true, "badge_type_id": 3 } ], "badge_types": [ { "id": 3, "name": "Bronze", "sort_order": 7 } ], "users": [ { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png" }, { "id": -1, "username": "system", "uploaded_avatar_id": 1, "avatar_template": "/user_avatar/localhost/system/{size}/1.png" } ], "topics": [ { "id": 12, "title": "Test topic for inviting users", "fancy_title": "Test topic for inviting users", "slug": "test-topic-for-inviting-users", "posts_count": 1 } ], "user": { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png", "name": "Test User", "email": "batman@gotham.com", "last_posted_at": "2015-01-07T13:59:29.406Z", "last_seen_at": "2015-01-07T15:59:16.095Z", "created_at": "2014-09-11T00:11:44.207Z", "website": "http://", "can_edit": true, "can_edit_username": true, "can_edit_email": true, "can_edit_name": true, "stats": [ { "action_type": 12, "count": 9, "id": null }, { "action_type": 4, "count": 12, "id": null }, { "action_type": 5, "count": 3, "id": null }, { "action_type": 2, "count": 1, "id": null }, { "action_type": 3, "count": 1, "id": null } ], "can_send_private_messages": true, "can_send_private_message_to_user": false, "bio_excerpt": "
the About Me field of your profile is currently blank, would you like to fill it out?
", "trust_level": 0, "moderator": false, "admin": true, "title": null, "badge_count": 2, "notification_count": 2, "has_title_badges": false, "custom_fields": {}, "post_count": 15, "can_be_deleted": false, "can_delete_all_posts": false, "locale": "", "email_digests": true, "email_private_messages": true, "email_direct": true, "email_always": false, "digest_after_days": 7, "mailing_list_mode": false, "auto_track_topics_after_msecs": 240000, "new_topic_duration_minutes": 2880, "external_links_in_new_tab": false, "dynamic_favicon": false, "enable_quoting": true, "muted_category_ids": [], "tracked_category_ids": [], "watched_category_ids": [], "private_messages_stats": { "all": 9, "mine": 9, "unread": 0 }, "disable_jump_reply": false, "gravatar_avatar_upload_id": null, "custom_avatar_upload_id": null, "invited_by": null, "custom_groups": [], "featured_user_badge_ids": [ 3, 2 ], "card_badge": null } } ================================================ FILE: spec/fixtures/user_create_success.json ================================================ { "success": true, "active": false, "message": "You're almost done! We sent an activation email to test2@example.com. Please follow the instructions in the email to activate your account." } ================================================ FILE: spec/fixtures/user_grant_admin.json ================================================ { "admin_user": { "id": 11, "username": "dave4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave4/{size}/2.png", "active": true, "admin": true, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:11:51.189Z", "created_at": "2014-12-08T14:32:29.906Z", "last_seen_age": null, "last_emailed_age": "20d", "created_at_age": "43d", "username_lower": "dave4", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0, "can_send_activation_email": true, "can_activate": false, "can_deactivate": false, "ip_address": "127.0.0.1", "registration_ip_address": "127.0.0.1", "single_sign_on_record": null } } ================================================ FILE: spec/fixtures/user_grant_moderator.json ================================================ { "admin_user": { "id": 11, "username": "dave4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave4/{size}/2.png", "active": true, "admin": false, "moderator": true, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:11:51.189Z", "created_at": "2014-12-08T14:32:29.906Z", "last_seen_age": null, "last_emailed_age": "20d", "created_at_age": "43d", "username_lower": "dave4", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0, "can_send_activation_email": true, "can_activate": false, "can_deactivate": false, "ip_address": "127.0.0.1", "registration_ip_address": "127.0.0.1", "single_sign_on_record": null } } ================================================ FILE: spec/fixtures/user_list.json ================================================ [ { "id": 1, "username": "test_user", "uploaded_avatar_id": 7, "avatar_template": "/user_avatar/localhost/test_user/{size}/7.png", "email": "batman@gotham.com", "active": true, "admin": true, "moderator": false, "last_seen_at": "2015-01-03T13:46:59.822Z", "last_emailed_at": "2015-01-02T13:38:11.417Z", "created_at": "2014-09-11T00:11:44.207Z", "last_seen_age": "1m", "last_emailed_age": "1d", "created_at_age": "4mon", "username_lower": "test_user", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "22m", "days_visited": 22, "posts_read_count": 19, "topics_entered": 8, "post_count": 14 }, { "id": 18, "username": "steve3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve3/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2015-01-02T15:49:34.257Z", "last_emailed_at": "2015-01-02T15:50:29.329Z", "created_at": "2015-01-02T13:53:04.206Z", "last_seen_age": "22h", "last_emailed_age": "22h", "created_at_age": "24h", "username_lower": "steve3", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 1, "posts_read_count": 1, "topics_entered": 1, "post_count": 0 }, { "id": 10, "username": "dave3", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave3/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2015-01-02T13:37:17.835Z", "last_emailed_at": "2015-01-01T13:08:57.912Z", "created_at": "2014-12-08T14:15:29.198Z", "last_seen_age": "1d", "last_emailed_age": "2d", "created_at_age": "26d", "username_lower": "dave3", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 2, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 7, "username": "phil12", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/phil12/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2014-12-01T21:01:11.506Z", "last_emailed_at": "2015-01-01T01:01:33.315Z", "created_at": "2014-12-01T14:24:11.406Z", "last_seen_age": "33d", "last_emailed_age": "3d", "created_at_age": "33d", "username_lower": "phil12", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 1, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 4, "username": "steve", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2014-12-01T14:13:36.498Z", "last_emailed_at": "2015-01-01T01:01:33.195Z", "created_at": "2014-09-20T19:42:33.225Z", "last_seen_age": "33d", "last_emailed_age": "3d", "created_at_age": "3mon", "username_lower": "steve", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 4, "posts_read_count": 1, "topics_entered": 1, "post_count": 1 }, { "id": 2, "username": "robin", "uploaded_avatar_id": 3, "avatar_template": "/user_avatar/localhost/robin/{size}/3.png", "active": true, "admin": false, "moderator": false, "last_seen_at": "2014-09-19T13:00:20.939Z", "last_emailed_at": "2015-01-01T01:01:24.438Z", "created_at": "2014-09-13T14:27:07.352Z", "last_seen_age": "4mon", "last_emailed_age": "3d", "created_at_age": "4mon", "username_lower": "robin", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 3, "posts_read_count": 2, "topics_entered": 2, "post_count": 0 }, { "id": 16, "username": "carl", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/carl/{size}/2.png", "active": false, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:54:32.453Z", "created_at": "2014-12-31T14:20:30.867Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "3d", "username_lower": "carl", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 8, "username": "dave", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-01T01:01:33.220Z", "created_at": "2014-12-01T14:29:13.762Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "33d", "username_lower": "dave", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 9, "username": "dave2", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave2/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-01T01:22:45.477Z", "created_at": "2014-12-08T14:09:47.150Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "26d", "username_lower": "dave2", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 11, "username": "dave4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave4/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:11:51.189Z", "created_at": "2014-12-08T14:32:29.906Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "26d", "username_lower": "dave4", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 12, "username": "dave5", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave5/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:11:51.189Z", "created_at": "2014-12-08T17:32:09.337Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "26d", "username_lower": "dave5", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 13, "username": "dave6", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave6/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:11:14.396Z", "created_at": "2014-12-08T17:39:46.840Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "26d", "username_lower": "dave6", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 14, "username": "dave7", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave7/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:12:43.028Z", "created_at": "2014-12-08T17:41:07.950Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "26d", "username_lower": "dave7", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 15, "username": "dave8", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/dave8/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2014-12-31T17:09:30.024Z", "created_at": "2014-12-08T17:46:13.471Z", "last_seen_age": null, "last_emailed_age": "3d", "created_at_age": "26d", "username_lower": "dave8", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 17, "username": "steve2", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve2/{size}/2.png", "active": false, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-02T13:39:40.453Z", "created_at": "2015-01-02T13:39:40.277Z", "last_seen_age": null, "last_emailed_age": "1d", "created_at_age": "1d", "username_lower": "steve2", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 19, "username": "steve4", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve4/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-02T15:59:27.135Z", "created_at": "2015-01-02T15:49:20.936Z", "last_seen_age": null, "last_emailed_age": "22h", "created_at_age": "22h", "username_lower": "steve4", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 20, "username": "steve5", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve5/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-02T16:05:38.191Z", "created_at": "2015-01-02T15:55:13.665Z", "last_seen_age": null, "last_emailed_age": "22h", "created_at_age": "22h", "username_lower": "steve5", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 21, "username": "steve6", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve6/{size}/2.png", "active": true, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-02T17:18:47.096Z", "created_at": "2015-01-02T17:07:58.686Z", "last_seen_age": null, "last_emailed_age": "20h", "created_at_age": "21h", "username_lower": "steve6", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": 22, "username": "steve7", "uploaded_avatar_id": null, "avatar_template": "/letter_avatar/steve7/{size}/2.png", "active": false, "admin": false, "moderator": false, "last_seen_at": null, "last_emailed_at": "2015-01-03T13:33:46.728Z", "created_at": "2015-01-03T13:33:46.137Z", "last_seen_age": null, "last_emailed_age": "14m", "created_at_age": "14m", "username_lower": "steve7", "trust_level": 0, "trust_level_locked": false, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 0, "topics_entered": 0, "post_count": 0 }, { "id": -1, "username": "system", "uploaded_avatar_id": 1, "avatar_template": "/user_avatar/localhost/system/{size}/1.png", "active": true, "admin": true, "moderator": true, "last_seen_at": null, "last_emailed_at": null, "created_at": "2014-09-11T00:02:55.028Z", "last_seen_age": null, "last_emailed_age": null, "created_at_age": "4mon", "username_lower": "system", "trust_level": 4, "trust_level_locked": true, "flag_level": 0, "title": null, "suspended_at": null, "suspended_till": null, "suspended": null, "blocked": false, "time_read": "< 1m", "days_visited": 0, "posts_read_count": 13, "topics_entered": 0, "post_count": 13 } ] ================================================ FILE: spec/fixtures/user_log_out_success.json ================================================ { "success": "OK" } ================================================ FILE: spec/fixtures/user_update_avatar_success.json ================================================ { "success": "OK" } ================================================ FILE: spec/fixtures/user_update_user.json ================================================ { "success": true } ================================================ FILE: spec/fixtures/user_update_username.json ================================================ { "id": 7, "username": "fake_user_2" } ================================================ FILE: spec/spec_helper.rb ================================================ # frozen_string_literal: true require "simplecov" SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::HTMLFormatter]) SimpleCov.start { add_filter "/spec/" } require "discourse_api" require "rspec" require "webmock/rspec" require "ostruct" RSpec.configure do |config| config.expect_with :rspec do |c| c.syntax = :expect end end WebMock.disable_net_connect!(allow_localhost: true) def host "http://localhost:3000" end def a_delete(path) a_request(:delete, path) end def a_get(path) a_request(:get, path) end def a_post(path) a_request(:post, path) end def a_put(path) a_request(:put, path) end def stub_delete(path) stub_request(:delete, path) end def stub_get(path) stub_request(:get, path) end def stub_post(path) stub_request(:post, path) end def stub_put(path) stub_request(:put, path) end def fixture_path File.expand_path("../fixtures", __FILE__) end def fixture(file) File.new(fixture_path + "/" + file) end def escape_params(params) params.map { |key, value| [CGI.escape(key), value].join("=") }.join("&") end