• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / tigerdata-app / 8d70f2ab-acc5-4aab-b64b-743d66ddd2eb

29 Aug 2025 06:22PM UTC coverage: 87.983% (-0.1%) from 88.118%
8d70f2ab-acc5-4aab-b64b-743d66ddd2eb

Pull #1801

circleci

JaymeeH
Merge branch '1586-request-mailer' of https://github.com/pulibrary/tiger-data-app into 1586-request-mailer
Pull Request #1801: 1586 request mailer

10 of 10 new or added lines in 2 files covered. (100.0%)

1173 existing lines in 56 files now uncovered.

2482 of 2821 relevant lines covered (87.98%)

317.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.67
/app/models/mediaflux/request.rb
1
# frozen_string_literal: true
2
module Mediaflux
2✔
3
    class Request
2✔
4

5
      # As this is an abstract class, this should be overridden to specify the Mediaflux API service
6
      def self.service
2✔
UNCOV
7
        raise(NotImplementedError, "#{self} is an abstract class, please override #{self}.service")
1✔
8
      end
9

10
      # The default request URL path for the Mediaflux API
11
      # @return [String]
12
      def self.request_path
2✔
13
        "/__mflux_svc__"
5,491✔
14
      end
15

16
      def self.uri
2✔
17
        # Setting the URI to the Ansible build or Docker build of mediaflux depending on the environment
18
        URI("#{Connection.transport}://#{Connection.host}:#{Connection.port}#{request_path}")
2,757✔
19
      end
20

21
      # Constructs a new HTTP POST request for usage with the Mediaflux API
22
      # @return [Net::HTTP::Post]
23
      def self.build_post_request
2✔
24
        Net::HTTP::Post.new(request_path)
2,734✔
25
      end
26

27
      # The default XML namespace which should be used for building the XML
28
      #   Document transmitted in the body of the HTTP request
29
      # @return [String]
30
      def self.default_xml_namespace
2✔
31
        "tigerdata"
51✔
32
      end
33

34
      def self.default_xml_namespace_uri
2✔
35
        "http://tigerdata.princeton.edu"
51✔
36
      end
37

38
      # Constructs and memoizes a new instance of the Net::HTTP::Persistent object at the level of the Class
39
      # @returns http_client [Net::HTTP::Persistent] HTTP client for transmitting requests to the Mediaflux server API
40
      def self.find_or_create_http_client
2✔
41
        HttpConnection.instance.http_client
2,740✔
42
      end
43

44
      attr_reader :session_token
2✔
45

46
      # Constructor
47
      # @param file [File] any upload file required for the POST request
48
      # @param session_token [String] the API token for the authenticated session
49
      # @param http_client [Net::HTTP::Persistent] HTTP client for transmitting requests to the Mediaflux server API
50
      def initialize(file: nil, session_token: nil, http_client: nil, session_user: nil)
2✔
51
        @http_client = http_client || self.class.find_or_create_http_client
2,740✔
52
        @file = file
2,740✔
53
        @session_token = session_token
2,740✔
54
        @session_user = session_user
2,740✔
55
      end
56

57
      # Resolves the HTTP request against the Mediaflux API
58
      # @return [Net::HTTP]
59
      def resolve
2✔
60
        @http_response = @http_client.request self.class.uri, http_request
2,739✔
61
      end
62

63
      # Determines whether or not the request has been resolved
64
      # @return [Boolean]
65
      def resolved?
2✔
66
        @http_response.present?
3,452✔
67
      end
68

69
      # Resolves the HTTP request, and returns the XML Document parsed from the response body
70
      # @return [Nokogiri::XML::Document]
71
      def response_xml
2✔
72
        resolve unless resolved?
3,450✔
73
        Rails.logger.debug(response_body)
3,450✔
74
        @response_xml ||= Nokogiri::XML.parse(response_body)
3,450✔
75
        Rails.logger.debug(@response_xml)
3,450✔
76
        if @response_xml.xpath("//message").text == "session is not valid"
3,450✔
UNCOV
77
          raise Mediaflux::SessionExpired, "Session expired for token #{session_token}"
4✔
78
        end
79

80
        @response_xml
3,446✔
81
      end
82

83
      # The response body of the mediaflux call
84
      def response_body
2✔
85
        @response_body ||= http_response.body
6,086✔
86
      end
87

88
      def error?
2✔
89
        response_error.present?
750✔
90
      end
91

92
      def response_error
2✔
93
        xml = response_xml
762✔
94
        return nil if xml.xpath("/response/reply/error").count == 0
762✔
95
        error = {
96
          title: xml.xpath("/response/reply/error").text,
17✔
97
          message: xml.xpath("/response/reply/message").text
98
        }
99
        Rails.logger.error "MediaFlux error: #{error[:title]}, #{error[:message]}"
17✔
100
        error
17✔
101
      end
102

103
      delegate :to_s, to: :response_xml
2✔
104

105
      def xml_payload( name: self.class.service)
2✔
106
        body = build_http_request_body(name: )
5,470✔
107
        xml_payload = body.to_xml
5,470✔
108
      end
109

110
      # The output of this routine can be passed to xtoshell in aterm.  The output of which can be sent to service.execute
111
      # @param [String] name name of the service this request will send
112
      # @return [String] xml that can be passed to xtoshell without manipulation
113
      def xtoshell_xml( name: self.class.service)
2✔
UNCOV
114
        xml_builder = build_http_request_body(name: )
1✔
UNCOV
115
        xml_builder.doc.xpath("//request/service/@session").remove
1✔
UNCOV
116
        xml = xml_builder.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
1✔
UNCOV
117
        xml.strip.gsub("\"","'").gsub("<args>","").gsub("</args>","")
1✔
118
      end
119

120
      # This method is used for transforming iso8601 dates to dates that MediaFlux likes
121
      # Take a string like "2024-02-26T10:33:11-05:00" and convert this string to "22-FEB-2024 13:57:19"
122
      def self.format_date_for_mediaflux(iso8601_date)
2✔
123
        return if iso8601_date.nil?
×
124
        Time.format_date_for_mediaflux(iso8601_date)
×
125
      end
126

127
      private
2✔
128

129
        def http_request
2✔
130
          @http_request ||= build_http_request(name: self.class.service, form_file: @file)
2,741✔
131
        end
132

133
        def http_response
2✔
134
          @http_response ||= resolve
2,630✔
135
        end
136

137
        def build_http_request_body(name:)
2✔
138
          args = { name: name }
5,471✔
139
                                                # must use @session_token here instead of session_token
140
                                                #  for login to not go into an infinaite loop
141
          args[:session] = session_token unless @session_token.nil?
5,471✔
142

143
          Nokogiri::XML::Builder.new(namespace_inheritance: false) do |xml|
5,471✔
144
            xml.request do
5,471✔
145
              xml.service(**args) do
5,471✔
146
                yield xml if block_given?
5,471✔
147
              end
148
            end
149
          end
150
        end
151

152
        # rubocop:disable Metrics/MethodLength
153
        def build_http_request(name:, form_file: nil)
2✔
154
          request = self.class.build_post_request
2,734✔
155

156
          log_xml_request(xml_payload)
2,734✔
157
          set_authentication_headers(request)
2,734✔
158

159
          if form_file.nil?
2,734✔
160
            request["Content-Type"] = "text/xml; charset=utf-8"
2,733✔
161
            request.body = xml_payload(name:)
2,733✔
162
          else
UNCOV
163
            request["Content-Type"] = "multipart/form-data"
1✔
UNCOV
164
            request.set_form({ "request" => xml_payload,
1✔
165
                               "nb-data-attachments" => "1",
166
                               "file_0" => form_file },
167
                          "multipart/form-data",
168
                          "charset" => "UTF-8")
169
          end
170

171
          request
2,734✔
172
        end
173
      # rubocop:enable Metrics/MethodLength
174

175
      def log_xml_request(xml_payload)
2✔
176
        password_element = xml_payload.match(/\<password\>.*\<\/password\>/)
2,734✔
177
        if password_element.nil?
2,734✔
178
          Rails.logger.debug(xml_payload)
1,324✔
179
        else
180
          # Don't log the password
181
          Rails.logger.debug(xml_payload.gsub(password_element.to_s, "<password>***</password>"))
1,410✔
182
        end
183
      end
184

185
      # Authentication code to push a few custom HTTP headers to Mediaflux
186
      # Eventually the `session_user` will need to be an object that provides the timeout value.
187
      def set_authentication_headers(request)
2✔
188
        return if @session_user.nil?
2,734✔
189

UNCOV
190
        request["mediaflux.sso.user"] = @session_user.uid
1✔
191
      end
192
    end
193
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc