# Building Tests for New discourse\_api Endpoints

**URL:** https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639
**Category:** Development
**Created:** [24.Май.2019 22:21:24 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639 "2019-05-24T22:21:24Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [24.Май.2019 22:21:24 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/1 "2019-05-24T22:21:24Z")

</div>

Adding some `polls` API endpoints for PR to discourse\_api, which work fine. Now I’m trying to understand how to create tests before submitting the PR, e.g.:

```
require 'spec_helper'

describe DiscourseApi::API::Polls do
  subject { DiscourseApi::Client.new("http://localhost:3000", "test_d7fd0429940", "test_user" )}

  describe "#polls" do
    before do
      stub_get("http://localhost:3000/polls/voters.json").to_return(body: fixture("voters.json"), headers: { content_type: "application/json" })
    end

    it "requests the correct resource" do
      subject.voters :post_id => 27885, :poll_name => 'poll' 
      expect(a_get("http://localhost:3000/polls/voters.json")).to have_been_made
    end
  end
end

```

But am getting the error:

```
   Failed to open TCP connection to localhost:3000 (Connection refused - connect(2) for "localhost" port 3000)
 # ./lib/discourse_api/client.rb:141:in `rescue in request'
 # ./lib/discourse_api/client.rb:132:in `request'
 # ./lib/discourse_api/client.rb:85:in `get'
 # ./lib/discourse_api/api/polls.rb:22:in `voters'
 # ./spec/discourse_api/api/polls_spec.rb:12:in `block (3 levels) in <top (required)>'
 # ------------------
 # --- Caused by: ---
 # Errno::ECONNREFUSED:
 # Connection refused - connect(2) for "localhost" port 3000
 # /Users/kimardenmiller/.rbenv/versions/2.5.3/gemsets/d_api/gems/webmock-2.3.2/lib/webmock/http_lib_adapters/net_http.rb:109:in `request'

```

All the existing tests pass fine for me, and I’m trying to copy the format of those existing tests, but cannot for the life of me figure out what I’m doing wrong.

Here’s the (new) endpoint being tested:

```
module DiscourseApi
  module API
    module Polls
     def poll_voters(args)
        args = API.params(args) # post_id, poll_name, user, opts = {}
                  .required(:post_id, :poll_name)
                  .optional(:opts, :api_username)
        response = get("/polls/voters.json", args)
        response[:body]
      end
    end
  end
end

```

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [25.Май.2019 23:53:25 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/2 "2019-05-25T23:53:25Z")

</div>

Have working tests, though my gut tells me I could use tips on making those urls more elegant.

```
require 'spec_helper'

describe DiscourseApi::API::Polls do
  subject { DiscourseApi::Client.new("http://localhost:3000", "test_d7fd0429940", "test_user" )}

  describe "#polls" do
    before do
      stub_get("http://localhost:3000/polls/voters.json?post_id=27885&poll_name=poll").to_return(body: fixture("polls_voters.json"), headers: { content_type: "application/json" })
    end

    it "requests the correct resource" do
      subject.poll_voters post_id: 27885, poll_name: 'poll'
      expect(a_get("http://localhost:3000/polls/voters.json?post_id=27885&poll_name=poll")).to have_been_made
    end

    it "returns the expected votes" do
      voters = subject.poll_voters post_id: 27885, 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

```

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [28.Май.2019 18:00:44 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/4 "2019-05-28T18:00:44Z")

</div>

Glad you got the tests working. They can be tricky sometimes because if the request doesn’t match exactly it will try and create an actual request rather that using the stubbed request.

I think the urls are okay. It would be nice not to use such a high post\_id, but I’m not concerned about that. And ALL the tests need to have `http://localhost:3000` extracted out into a common variable, but that should probably be done in a separate commit. Whenever you are ready you can go ahead and submit a pr and I can review it.

One thing I did notice though is I that passing in the `api_username` as a parameter is no longer supported.

> [@kimardenmiller](#):
>
> .optional(:opts, :api\_username)

This is because the discourse\_api gem now only passes in the auth via the headers, and discourse core ignores any auth credentials in the body if the header is already being used for auth.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [27.Сентябрь.2019 17:04:34 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/5 "2019-09-27T17:04:34Z")

</div>

> [@blake](#):
>
> Во всех тестах `http://localhost:3000` нужно вынести в общую переменную, но это, вероятно, стоит сделать отдельным коммитом.

Продолжение обсуждения:

> <https://github.com/discourse/discourse_api/pull/177>
>
> \- poll vote
> \- toggle poll status
> \- poll voters
> \- RuboCop Fixes

Цели:

- Для тестов: вынести `http://localhost:3000` в общую переменную.

- Для примеров: вынести хост, имя пользователя и api\_key в файл config.yml.

Мы определяем, где размещать и загружать данные. Сейчас у меня в файле discourse\_api.rb (с config.yml в корневой директории) написано:

```plaintext
require 'yaml'
CONFIG = YAML.load_file(File.expand_path('../../config.yml', __FILE__ ))

```

… затем в каждом файле с примерами:

```plaintext
client = DiscourseApi::Client.new(CONFIG['host'])
client.api_key = CONFIG['api_key']
client.api_username = CONFIG['api_username']

```

Это работает для примеров, но не для тестов. Буду благодарен за рекомендации по лучшим практикам для этого PR.

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [27.Сентябрь.2019 20:42:33 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/6 "2019-09-27T20:42:33Z")

</div>

Давайте разделим PR. Сначала сосредоточимся на спецификации и переместим общую переменную в папку spec. Мы должны иметь возможность добавить её в файл helper для spec.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [01.Октябрь.2019 18:05:04 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/7 "2019-10-01T18:05:04Z")

</div>

> [@kimardenmiller](#):
>
> - Например: извлечь хост, имя пользователя и api\_key в файл config.yml

@blake, дай знать, если у тебя есть комментарии к предложенному выше дизайну.

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [01.Октябрь.2019 18:14:07 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/8 "2019-10-01T18:14:07Z")

</div>

> [@kimardenmiller](#):
>
> Например: извлечь host, username и api\_key в файл config.yml

Это нормально.

> [@kimardenmiller](#):
>
> Мы решаем, где размещать и загружать компоненты. Сейчас у меня это находится в файле discourse\_api.rb (с config.yml в корневом каталоге):

Поскольку это только примеры, всё, что связано с ними, должно находиться внутри каталога examples, и мы не должны изменять существующую функциональность в каталоге lib. Даже файл config.yml следует разместить внутри каталога examples. Загрузка YAML-файла довольно легковесна, и мы можем добавлять её в начало каждого файла примера, но, вероятно, лучше добавить её в общий файл внутри каталога examples, из которого все они смогут читать данные.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [01.Октябрь.2019 23:13:28 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/9 "2019-10-01T23:13:28Z")

</div>

Вот что у меня есть:

#### examples/example\_helper.rb

```
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__ )
require File.expand_path('../../lib/discourse_api', __FILE__ )
require 'yaml'

CONFIG = YAML.load_file(File.expand_path('../../config.yml', __FILE__ ))

def client
  discourse_client = DiscourseApi::Client.new(CONFIG['host'])
  discourse_client.api_key = CONFIG['api_key']
  discourse_client.api_username = CONFIG['api_username']
  discourse_client
end

```

#### examples/config-example.yml

```
# host, например localhost:3000 или discourse.my_domain.com
host: ВАШЕ_ИМЯ_ХОСТА

# пользователь API (может влиять на возвращаемые результаты, например, метод .categories возвращает только те категории, которые видит api_username)
# создавайте нового клиента при смене пользователя API
api_username: ВАШ_API_КЛЮЧ

# ключ API из панели администратора Discourse /admin/api/keys
api_key: ВАШ_API_КЛЮЧ

```

#### examples/badges.rb

```
require_relative 'example_helper'

# получить значки
puts client.badges

```

Что думаете? Нам действительно нужен файл yml сейчас, учитывая example\_helper.rb?

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [02.Октябрь.2019 01:25:15 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/10 "2019-10-02T01:25:15Z")

</div>

Выглядит хорошо. Давайте обновим пример вспомогательного файла, добавив значения по умолчанию, чтобы он работал без файла yml. Затем обновите файл README инструкциями по копированию примера файла yml в examples/config.yml. И обязательно добавьте examples/config.yml в .gitignore.

Это довольно стандартная практика, чтобы избежать случайного коммита файла config.yml с учётными данными для продакшена. Кроме того, вы сможете редактировать этот файл, и git не будет считать это изменением.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [02.Октябрь.2019 17:13:52 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/11 "2019-10-02T17:13:52Z")

</div>

Всё в порядке, кроме того, что я не совсем понимаю этот момент

> [@blake](#):
>
> Давайте обновим пример хелпера, добавив значения по умолчанию, чтобы он работал без файла yml.

example\_helper.rb работает как есть, как только появится config.yml. Мы хотим, чтобы он также работал без файла yml, но у нас всё ещё есть файл yml?

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [02.Октябрь.2019 17:18:33 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/12 "2019-10-02T17:18:33Z")

</div>

Да, таким образом Ruby не выдаст ошибку, если они ещё не настроили свой YAML-файл.

Не совсем так, но что-то вроде:

```plaintext
  discourse_client = DiscourseApi::Client.new(CONFIG['host'] || 'localhost:3000')
  discourse_client.api_key = CONFIG['api_key'] || 'api-key'
  discourse_client.api_username = CONFIG['api_username'] || 'api-username'

```

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [02.Октябрь.2019 17:45:10 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/13 "2019-10-02T17:45:10Z")

</div>

Итак, перед созданием файла config.yml, даже с логикой двойного pipe, возникает ошибка:

```
`initialize': No such file or directory @ rb_sysopen

```

Чтобы pipe-ы вообще могли сработать, нам также потребуется изменить `YAML.load_file`.

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [02.Октябрь.2019 17:46:26 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/14 "2019-10-02T17:46:26Z")

</div>

> [@kimardenmiller](#):
>
> Чтобы конвейеры вообще начали работать, нам также нужно изменить `YAML.load_file`.

Да, нам тоже нужно это изменить.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [02.Октябрь.2019 18:16:38 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/15 "2019-10-02T18:16:38Z")

</div>

Итак, этот код

```
config_yml = File.expand_path('../config.yml', __FILE__ )

if File.exists? config_yml
  CONFIG = YAML.load_file config_yml
else
  CONFIG = {}
end

def client
  discourse_client = DiscourseApi::Client.new(CONFIG['host'] || 'http://localhost:3000')
  discourse_client.api_key = CONFIG['api_key'] || 'YOUR_API_KEY'
  discourse_client.api_username = CONFIG['api_username'] || 'YOUR_USERNAME'
  discourse_client
end

```

вызывает у нас ошибки вроде

```
Failed to open TCP connection to localhost:3000 (Connection refused - connect(2) for "localhost" port 3000) (Faraday::ConnectionFailed)

```

Это та ошибка, которую мы хотим получать?

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [02.Октябрь.2019 18:53:10 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/16 "2019-10-02T18:53:10Z")

</div>

Я думаю, что эта ошибка допустима, поскольку именно так сейчас работают примеры, да и в любом случае это касается только примеров. Если захотите, сейчас или позже, мы всегда можем перехватить исключение и вывести понятное сообщение.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [02.Октябрь.2019 19:38:01 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/17 "2019-10-02T19:38:01Z")

</div>

Возможно …

```
config_yml = File.expand_path('../configg.yml', __FILE__ )

begin
  CONFIG = YAML.load_file config_yml
rescue Errno::ENOENT
  raise ArgumentError, '/examples/config.yml файл не найден. Пожалуйста, скопируйте config-example.yml, чтобы создать config.yml для вашей среды.'
end

def client
  discourse_client = DiscourseApi::Client.new(CONFIG['host'] || 'http://localhost:3000')
  discourse_client.api_key = CONFIG['api_key'] || 'YOUR_API_KEY'
  discourse_client.api_username = CONFIG['api_username'] || 'YOUR_USERNAME'
  discourse_client
end

```

Значит, нам всё ещё нужны эти символы? Оставить их?

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [02.Октябрь.2019 20:01:24 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/18 "2019-10-02T20:01:24Z")

</div>

Да, это работает. В таком случае трубы нам не понадобятся, так что да, вы можете их удалить.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [02.Октябрь.2019 21:05:12 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/19 "2019-10-02T21:05:12Z")

</div>

Мне кажется, что наш текущий раздел «Тестирование» немного запутанный, поскольку он смешивает задачи, связанные с discourse\_api, с установкой самого Discourse. Например, если у вас есть тестовый сервер в облаке, вам вовсе не нужно устанавливать Discourse локально. Как вы относитесь к следующему варианту:

## Примеры и тестирование

Чтобы попробовать примеры или запустить тесты, вам потребуется [работающий экземпляр Discourse](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md), установленный либо локально, либо в облаке.

### Примеры

Чтобы запустить примеры из папки `/examples`:

1. Укажите настройки окружения, создав копию файла config-example.yml и назвав её config.yml, заполнив её параметрами вашего окружения.
2. В нужном файле примера закомментируйте все примеры, кроме тех, которые вы хотите запустить.
3. Отредактируйте файл примера, добавив необходимые параметры, например `username`.
4. При запуске файла данные будут считываться из config.yml для вызова соответствующего метода клиента (например, `client.badges`) в вашем экземпляре Discourse.

### Тестирование

Чтобы запустить тесты discourse\_api из папки spec:

1. Установите bundler в директории discourse\_api, выполнив команду `gem install bundler`.
2. Находясь в директории discourse\_api, выполните: `bundle exec rspec spec/`.

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [02.Октябрь.2019 21:11:09 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/20 "2019-10-02T21:11:09Z")

</div>

Звучит хорошо, README можно было бы обновить.

> [@kimardenmiller](#):
>
> Чтобы попробовать примеры или запустить тесты, вам понадобится [работающий экземпляр Discourse](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md), установленный либо локально, либо в облаке.

Для запуска тестов на самом деле не нужен работающий экземпляр Discourse. Все запросы в спецификациях должны быть замокированы, и сервер не требуется.

---

<div class="post-metadata">

### Author: ![kimardenmiller](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/kimardenmiller/32/119631_2.png) [@kimardenmiller](https://meta.discourse.org/u/kimardenmiller)
#### Post date: [02.Октябрь.2019 21:13:35 UTC](https://meta.discourse.org/t/building-tests-for-new-discourse-api-endpoints/118639/21 "2019-10-02T21:13:35Z")

</div>

Ах, верно, так как насчет …

## Примеры

Чтобы попробовать примеры из `/examples`, вам понадобится [работающий экземпляр Discourse](https://github.com/discourse/discourse/blob/master/docs/INSTALL.md), установленный либо локально, либо в облаке. Затем:

1. Укажите вашу среду, создав копию файла `config-example.yml` под именем `config.yml` с вашими настройками окружения.
2. В выбранном файле с примерами закомментируйте все, кроме тех примеров, которые вы хотите запустить.
3. Отредактируйте файл с примером, добавив необходимые параметры, например, целевое имя пользователя (`username`), передаваемое методу.
4. Запуск файла извлечет данные из `config.yml` для выполнения соответствующего метода клиента (например, `client.badges`) в вашем экземпляре Discourse.

## Тестирование

Чтобы запустить тесты `discourse_api` в `spec`:

1. Установите Bundler в директории `discourse_api`, выполнив команду `gem install bundler`.
2. Внутри директории `discourse_api` выполните: `bundle exec rspec spec/`.
