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

MushroomObserver / mushroom-observer / 28293327797

27 Jun 2026 03:21PM UTC coverage: 98.397% (+0.006%) from 98.391%
28293327797

Pull #4612

github

JoeCohen
Implement Copilot suggestions

Implements:

- https://github.com/MushroomObserver/mushroom-observer/pull/4612#discussion_r3486225964
- https://github.com/MushroomObserver/mushroom-observer/pull/4612#discussion_r3486225973
Pull Request #4612: Add API2Controller#index to handle bad /api2 root requests

17 of 17 new or added lines in 1 file covered. (100.0%)

3 existing lines in 1 file now uncovered.

49899 of 50712 relevant lines covered (98.4%)

733.25 hits per line

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

97.03
/app/controllers/api2_controller.rb
1
# frozen_string_literal: true
2

3
#
4
#  = API2 Controller
5
#
6
#  This controller handles the JSON and XML interfaces
7
#
8
#  == Actions
9
#
10
#  observations, etc.::   Entry point for REST requests.
11
#
12
################################################################################
13
#
14
class API2Controller < ApplicationController
4✔
15
  require("xmlrpc/client")
4✔
16
  require("api2")
4✔
17

18
  disable_filters
4✔
19

20
  # wrapped parameters break JSON requests in the unit tests.
21
  wrap_parameters false
4✔
22

23
  def index
4✔
24
    @start_time = Time.zone.now
2✔
25
    @api = API2.execute(method: "GET", action: "index")
2✔
26
    set_cors_headers
2✔
27
    request.format = "json" if request.format == "html"
2✔
28
    respond_to do |format|
2✔
29
      format.xml  do
2✔
30
        render(layout: false, template: "/api2/results",
1✔
31
               status: :bad_request)
32
      end
33
      format.json do
2✔
34
        render(layout: false, template: "/api2/results",
1✔
35
               status: :bad_request)
36
      end
37
    end
38
  end
39

40
  # Standard entry point for REST requests.
41
  def api_keys
4✔
42
    rest_query(:api_key)
2✔
43
  end
44

45
  def collection_numbers
4✔
46
    rest_query(:collection_number)
6✔
47
  end
48

49
  def comments
4✔
50
    rest_query(:comment)
6✔
51
  end
52

53
  def external_links
4✔
54
    rest_query(:external_link)
6✔
55
  end
56

57
  def external_sites
4✔
58
    rest_query(:external_site)
6✔
59
  end
60

61
  def field_slips
4✔
62
    rest_query(:field_slip)
10✔
63
  end
64

65
  def herbaria
4✔
66
    rest_query(:herbarium)
6✔
67
  end
68

69
  def herbarium_records
4✔
70
    rest_query(:herbarium_record)
6✔
71
  end
72

73
  def images
4✔
74
    rest_query(:image)
11✔
75
  end
76

77
  def locations
4✔
78
    rest_query(:location)
7✔
79
  end
80

81
  def location_descriptions
4✔
82
    rest_query(:location_description)
6✔
83
  end
84

85
  def names
4✔
86
    rest_query(:name)
7✔
87
  end
88

89
  def name_descriptions
4✔
90
    rest_query(:name_description)
6✔
91
  end
92

93
  def observations
4✔
94
    rest_query(:observation)
30✔
95
  end
96

97
  def occurrences
4✔
98
    rest_query(:occurrence)
6✔
99
  end
100

101
  def projects
4✔
102
    rest_query(:project)
6✔
103
  end
104

105
  def sequences
4✔
106
    rest_query(:sequence)
7✔
107
  end
108

109
  def species_lists
4✔
110
    rest_query(:species_list)
6✔
111
  end
112

113
  def users
4✔
114
    rest_query(:user)
7✔
115
  end
116

117
  ##############################################################################
118

119
  private
4✔
120

121
  def rest_query(type)
4✔
122
    @start_time = Time.zone.now
147✔
123
    args = params_to_api_args(type)
147✔
124

125
    if request.method == "POST"
147✔
126
      if args[:upload].present?
13✔
127
        args[:upload] = upload_from_multipart_form_data(args[:upload])
2✔
128
        logger.warn("API UPLOAD: #{args[:upload].inspect}")
2✔
129
      elsif is_request_body_an_upload?
11✔
130
        args[:upload] = upload_from_request_body
2✔
131
      end
132
      # Special exception to let caller who creates new user to see that user's
133
      # new API keys.  Otherwise there is no way to get that info via the API.
134
      @show_api_keys_for_new_user = true if type == :user
13✔
135
    end
136

137
    render_api_results(args)
147✔
138
  end
139

140
  # Massage params hash to proper args hash for api
141
  def params_to_api_args(type)
4✔
142
    args = params.to_unsafe_h.symbolize_keys.except(:controller)
147✔
143
    args[:method] = request.method
147✔
144
    args[:action] = type
147✔
145
    args.delete(:format)
147✔
146
    args
147✔
147
  end
148

149
  def upload_from_multipart_form_data(data)
4✔
150
    API2::Upload.new(data: data)
2✔
151
  end
152

153
  def is_request_body_an_upload?
4✔
154
    request.content_length.positive? &&
11✔
155
      request.media_type.present? &&
156
      request.media_type != "application/x-www-form-urlencoded" &&
157
      request.media_type != "multipart/form-data" &&
158
      request.body.present?
159
  end
160

161
  def upload_from_request_body
4✔
162
    API2::Upload.new(
2✔
163
      data: request.body,
164
      length: request.content_length,
165
      content_type: request.media_type,
166
      checksum: request.headers["CONTENT_MD5"].to_s
167
    )
168
  end
169

170
  def render_api_results(args)
4✔
171
    @user = user_from_key(args[:api_key])
147✔
172
    @api = API2.execute(args)
147✔
173
    do_render
147✔
174
  rescue StandardError => e
UNCOV
175
    @api ||= API2.new
×
UNCOV
176
    @api.errors << API2::RenderFailed.new(e)
×
UNCOV
177
    do_render
×
178
  end
179

180
  def user_from_key(key)
4✔
181
    APIKey.find_by(key: key)&.user
147✔
182
  end
183

184
  def do_render
4✔
185
    set_cors_headers
147✔
186
    request.format = "json" if request.format == "html"
147✔
187
    respond_to do |format|
147✔
188
      format.xml  { do_render_xml  }
217✔
189
      format.json { do_render_json }
224✔
190
    end
191
  end
192

193
  def do_render_xml
4✔
194
    render(layout: false, template: "/api2/results")
70✔
195
  end
196

197
  def do_render_json
4✔
198
    render(layout: false, template: "/api2/results")
77✔
199
  end
200

201
  def set_cors_headers
4✔
202
    return unless request.method == "GET"
149✔
203

204
    response.set_header("Access-Control-Allow-Origin", "*")
136✔
205
    response.set_header("Access-Control-Allow-Headers",
136✔
206
                        "Origin, X-Requested-With, Content-Type, Accept")
207
    response.set_header("Access-Control-Allow-Methods", "GET")
136✔
208
  end
209
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

© 2026 Coveralls, Inc