[Solved] How to get Host and Port configuration for Test Environment in Rspec 3.10 Rails

AYAN BISWAS Asks: How to get Host and Port configuration for Test Environment in Rspec 3.10 Rails
Hi I have a controller file at app/controllers/lti_controller.rb and I need to add Test for the POST #launch endpoint which is defined as /lti/launch in routes.rb and working as intended.

Problem​

I need to send some data to the /lti/launch in the encoding format x-www-form-urlencoded and by using Net::HTTP I am doing that from the spec file. But as Net::HTTP requires host and port for their operation and I have given localhost:3000 as of now and which is going to the development environment.

Question​

  1. How to access the host and port of the test environment in Rspec 3.10 such that I can pass them in spec file ?

  2. Is there any way to use post ‘/lti/launch’ , req.body : data in x-www-form-urlencoded format` ? and also get the host and port of the test environment ?

My Spec file​

spec/requests/lti_spec.rb

Code:
# frozen_string_literal: true

require "rails_helper"
require 'uri'
require 'net/http'

describe LtiController, type: :request do
    before do
        # shared keys for lms access
        @oauth_consumer_key_fromlms = 'some_keys'
        @oauth_shared_secret_fromlms = 'some_secrets'
    end

    describe "#launch" do        
        before do
            # creation of assignment and required users
            @mentor = FactoryBot.create(:user)
            @group = FactoryBot.create(:group, mentor: @mentor)
            @member = FactoryBot.create(:user)
            FactoryBot.create(:group_member, user: @member, group: @group)
            @assignment = FactoryBot.create(:assignment, group: @group, lti_consumer_key: @oauth_consumer_key_fromlms, lti_shared_secret: @oauth_shared_secret_fromlms, status: "open")
        end

        def launch_uri
            launch_url = "http://0.0.0.0:3000/lti/launch"
            URI(launch_url)
        end

        let(:parameters) {
            {
                'launch_url' => launch_uri().to_s,
                'user_id' => @member.id,
                'launch_presentation_return_url' => 'http://localhost:3000/tool_return',
                'lti_version' => 'LTI-1p0',
                'lti_message_type' => 'basic-lti-launch-request',
                'resource_link_id' => '88391-e1919-bb3456',
                'lis_person_contact_email_primary' => @member.email,
                'tool_consumer_info_product_family_code' => 'moodle',
                'context_title' => 'sample Course',
                'lis_outcome_service_url' => 'http://localhost:3000/grade_passback',
                'lis_result_sourcedid' => SecureRandom.hex(10)
            } 
        }

        def consumer_data(oauth_consumer_key_fromlms, oauth_shared_secret_fromlms, parameters)
            consumer = IMS::LTI::ToolConsumer.new(oauth_consumer_key_fromlms, oauth_shared_secret_fromlms, parameters)
            allow(consumer).to receive(:to_params).and_return(parameters)
            consumer.generate_launch_data
        end

        context "lti parameters are valid" do
            it "returns success if assignment key and secret are ok and group member is present" do
                data = consumer_data(@oauth_consumer_key_fromlms, @oauth_shared_secret_fromlms, parameters)
                response = Net::HTTP.post_form(launch_uri(), data)
                expect(response.code).to eq("200")
            end
        end

        
    end
end

My Controller file​

app/controllers/lti_controller.rb

Code:
class LtiController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :launch # for lti integration
  before_action :set_group_assignment, only: %i[launch]
  before_action :set_lti_params, only: %i[launch]
  after_action :allow_iframe_lti, only: %i[launch]
  
  def launch
    session[:is_lti]=true # the lti session starting
    require 'oauth/request_proxy/action_controller_request'

    if @assignment.blank?
      # if no assignment is found
      flash[:notice] = t("lti.launch.notice_no_assignment")
      render :launch_error, status: 401
      return
    end
    
    if @group.present? # if there is a valid group based for the lti_token_key
      @provider = IMS::LTI::ToolProvider.new(
        params[:oauth_consumer_key], # lms_oauth_consumer_key
        @assignment.lti_shared_secret, # the group's lti_token
        params
      )

      if !@provider.valid_request?(request) # checking the lti request from the lms end
        render :launch_error, status: 401
        return
      end

      lms_lti_host = URI.join @launch_url_from_lms, '/' # identifies the domain and saves in session
      session[:lms_domain]=lms_lti_host

      @user = User.find_by(email: @email_from_lms) # find user by matching email with circuitverse and lms 

      if @user.present? # user is present in cv 
        if @user.id == @group.mentor_id # user is teacher
          sign_in(@user) # passwordless sign_in the user as the authenticity is verified via lms
          lms_auth_success_notice = t("lti.launch.notice_lms_auth_success_teacher", email_from_lms: @email_from_lms, lms_type: @lms_type, course_title_from_lms: @course_title_from_lms)
          redirect_to group_assignment_path(@group, @assignment), notice: lms_auth_success_notice # if auth_success send to group page
        else
          user_in_group = GroupMember.find_by(user_id:@user.id,group_id:@group.id) # check if the user belongs to the cv group

          if user_in_group.present? # user is member of the group
            # render the button
            flash[:notice] =  t("lti.launch.notice_students_open_in_cv")
            create_project_if_student_present() # create project with lis_result_sourced_id for the student
            render :open_incv, status: 200

          else # user is not a member of the group
            # send the user an email
            flash[:notice] = t("lti.launch.notice_ask_teacher")
            render :launch_error, status: 401
          end 
        end
      else # no such user in circuitverse,showing a notice to create an account in cv
        flash[:notice] = t("lti.launch.notice_no_account_in_cv", email_from_lms: @email_from_lms )
        render :launch_error, status: 400
      end
    else # there is no valid group present for the lti_consumer_key
      flash[:notice] = t("lti.launch.notice_invalid_group")
      render :launch_error, status: 400
    end
  end

  def allow_iframe_lti
    return unless session[:is_lti]
    
    response.headers["X-FRAME-OPTIONS"] = "ALLOW-FROM #{session[:lms_domain]}"
  end

  def create_project_if_student_present
    @user = User.find_by(email: @email_from_lms)
    @project = Project.find_by(author_id: @user.id, assignment_id: @assignment.id) # find if the project is already present
    if @project.blank? # if not then create one
      @project = @user.projects.new
      @project.name = "#{@user.name}/#{@assignment.name}"
      @project.assignment_id = @assignment.id
      @project.project_access_type = "Private"
      @project.build_project_datum
      @project.lis_result_sourced_id = params[:lis_result_sourcedid] # this param is required for grade submission
      @project.save
    end
  end

  private

    def set_group_assignment # query db and check lms_oauth_consumer_key is equal to which assignment and find the group also
      @assignment = Assignment.find_by(lti_consumer_key: params[:oauth_consumer_key])
      if @assignment.present?
        @group =@assignment.group
      end
    end
    
    def set_lti_params # get some of the parameters from the lti request
      @email_from_lms = params[:lis_person_contact_email_primary] # the email from the LMS
      @lms_type = params[:tool_consumer_info_product_family_code] # type of lms like moodle/canvas
      @course_title_from_lms = params[:context_title] # the course titile from lms
      @launch_url_from_lms = params[:launch_presentation_return_url]
      session[:lis_outcome_service_url] = params[:lis_outcome_service_url] # requires for grade submission
      session[:oauth_consumer_key] = params[:oauth_consumer_key] # requires for grade submission
    end
end

Sample data needed to be sent​

Code:
{"oauth_consumer_key"=>"some_keys", "oauth_signature_method"=>"HMAC-SHA1", "oauth_timestamp"=>"1627879512", "oauth_nonce"=>"Id3rLYZBqMvnRuSpisMEEgFkLFnkxZPS2oqoyBJZLM", "oauth_version"=>"1.0", "context_title"=>"sample Course", "launch_presentation_return_url"=>"http://localhost:3000/tool_return", "launch_url"=>"http://0.0.0.0:3000/lti/launch", "lis_outcome_service_url"=>"http://localhost:3000/grade_passback", "lis_person_contact_email_primary"=>"chung@towne-littel.org", "lis_result_sourcedid"=>"3e87e3aa8f5056260a12", "lti_message_type"=>"basic-lti-launch-request", "lti_version"=>"LTI-1p0", "resource_link_id"=>"88391-e1919-bb3456", "tool_consumer_info_product_family_code"=>"moodle", "user_id"=>"461", "oauth_signature"=>"lgsHKJxHolBU1rTZ5M9zXg688hU="}

Ten-tools.com may not be responsible for the answers or solutions given to any question asked by the users. All Answers or responses are user generated answers and we do not have proof of its validity or correctness. Please vote for the answer that helped you in order to help others find out which is the most helpful answer. Questions labeled as solved may be solved or may not be solved depending on the type of question and the date posted for some posts may be scheduled to be deleted periodically. Do not hesitate to share your response here to help other visitors like you. Thank you, Ten-tools.