بناء اختبارات لنهايات discourse_api الجديدة

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

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

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.

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.

متابعة للمحادثة من

الأهداف:

  • للاختبارات: استخراج http://localhost:3000 إلى متغير مشترك.

  • للأمثلة: استخراج المضيف واسم المستخدم ومفتاح الـ API إلى ملف config.yml.

نحن نقرر مكان وضع وتحميل الأشياء. حاليًا، لدي في ملف discourse_api.rb (مع config.yml في المجلد الجذري):

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

… ثم في كل ملف أمثلة:

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

هذا يعمل مع الأمثلة، لكنه لا يعمل مع الاختبارات. أنا مهتم ببعض التوجيهات حول أفضل الممارسات لهذا الـ PR.

لنقم بتقسيم طلبات السحب (PRs) من فضلك. أولاً، دعنا نركز على طلب السحب المتعلق بالمواصفات (spec) ولنقم بنقل المتغير المشترك إلى مجلد المواصفات. يجب أن نكون قادرين على إضافته إلى ملف مساعد المواصفات (spec helper file).

@blake أخبرني إذا كان لديك أي ملاحظات على التصميم أعلاه.

هذا مقبول.

نظرًا لأن هذا مخصص للأمثلة فقط، يجب أن يكون أي شيء متعلق بهذا داخل دليل الأمثلة، دون تعديل أي من الوظائف الموجودة داخل دليل lib. حتى ملف config.yml، يجب وضعه داخل دليل الأمثلة. تحميل ملف YAML خفيف الوزن للغاية، ويمكننا إضافته إلى أعلى كل ملف مثال، ولكن من الأفضل على الأرجح إضافته إلى ملف مشترك داخل دليل الأمثلة يمكنه جميع الملفات قراءته منه.

إليك ما لدي:

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: YOUR_HOST_NAME

# مستخدم الـ API (يمكن أن يؤثر على النتائج المسترجعة، فمثلاً طريقة .categories تُرجع فقط الفئات التي يمكن لمستخدم الـ API رؤيتها)
# أنشئ عميلًا جديدًا عند تغيير مستخدم الـ API
api_username: YOUR_API_KEY

# مفتاح الـ API من لوحة تحكم Discourse الإدارية /admin/api/keys
api_key: YOUR_API_KEY

examples/badges.rb

require_relative 'example_helper'

# احصل على الشارات
puts client.badges

أفكار؟ بالتأكيد نحتاج إلى ملف yml الآن، نظرًا لوجود example_helper.rb؟

يبدو الأمر جيدًا. دعنا نحدّث مساعد المثال بقيم افتراضية بحيث يعمل دون الحاجة إلى ملف YAML. ثم قم بتحديث ملف README بإرشادات لنسخ ملف YAML المثال إلى examples/config.yml. وتأكد من إضافة examples/config.yml إلى ملف .gitignore.

هذا أسلوب شائع جدًا بحيث لا يكون من السهل إيداع ملف config.yml يحتوي على بيانات اعتماد الإنتاج. ويمكنك تحرير الملف دون أن يراه Git كتغيير.

كل شيء على ما يرام، باستثناء أنني لا أفهم هذه النقطة تمامًا

يعمل example_helper.rb كما هو بمجرد وجود config.yml. نريد أن يعمل أيضًا دون وجود ملف yml، لكننا لا نزال نحتفظ بملف yml؟

نعم، بهذه الطريقة لن ينهار Ruby إذا لم يقوموا بإعداد ملف YAML الخاص بهم بعد.

ليس هذا تمامًا، بل شيء مشابه لـ:

  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'

لذا، قبل إنشاء ملف config.yml، حتى مع منطق الأنبوب المزدوج، نفشل مع:

`initialize': لا يوجد ملف أو دليل @ rb_sysopen

لكي تدخل الأنابيب حيز التنفيذ، نحتاج أيضًا إلى تعديل YAML.load_file.

نعم، نحتاج إلى تعديل ذلك أيضًا.

لذا فإن هذا الكود

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)

هل هذا هو الخطأ المفضل لدينا؟

أعتقد أن هذا الخطأ مقبول لأن هذا هو كيفية عمل الأمثلة حاليًا، وهذا الأمر موجود في الأمثلة فقط. إذا أردت، الآن أو لاحقًا، يمكننا دائمًا التقاط الاستثناء وتقديم رسالة مفيدة.

ربما …

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

begin
  CONFIG = YAML.load_file config_yml
rescue Errno::ENOENT
  raise ArgumentError, '/examples/config.yml file not found. Please copy config-example.yml to create a config.yml for your environment.'
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

هل ما زلنا بحاجة إلى الشرطات العمودية إذن؟ هل نتركها كما هي؟

نعم، هذا يعمل. في هذه الحالة، لن نحتاج إلى الأنابيب، لذا نعم، يمكنك إزالتها.

قسم الاختبار الحالي لدينا مربك قليلاً بالنسبة لي، حيث يخلط بين المهام المتعلقة بـ discourse_api وبين تثبيت Discourse نفسه. على سبيل المثال: إذا كان لديك خادم تجريبي في السحابة، فلن تحتاج إلى تثبيت Discourse محليًا على الإطلاق. ما رأيك في:

الأمثلة والاختبار

لتجربة الأمثلة أو تشغيل الاختبارات، ستحتاج إلى نسخة من Discourse تعمل بشكل صحيح، تم تثبيتها إما محليًا أو في السحابة.

الأمثلة

لتشغيل الأمثلة الموجودة في /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/

يبدو الأمر جيدًا، فقد يحتاج ملف readme إلى بعض التحديثات

لتشغيل الاختبارات، في الواقع لا تحتاج إلى نسخة من Discourse تعمل بشكل صحيح. يجب محاكاة جميع طلبات المواصفات (spec requests)، ولا يلزم وجود خادم.

آه، نعم، إذن ماذا عن …

أمثلة

لتجربة الأمثلة الموجودة في /examples، ستحتاج إلى نسخة من Discourse تعمل بشكل صحيح، سواء كانت مثبتة محليًا أو في السحابة، ثم:

  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/.