Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ def index

pagination_info = scim_pagination_info(query.count())

page_of_results = query
.order(@id_column => :asc)
.offset(pagination_info.offset)
.limit(pagination_info.limit)
.to_a()
# SCIM 2.0 RFC 7644: When count=0, return metadata only (no Resources).
# This avoids an unnecessary database query for record data.
page_of_results = if pagination_info.limit == 0
[]
else
query
.order(@id_column => :asc)
.offset(pagination_info.offset)
.limit(pagination_info.limit)
.to_a()
end

super(pagination_info, page_of_results) do | record |
record_to_scim(record)
Expand Down
9 changes: 6 additions & 3 deletions app/models/scimitar/lists/count.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ def initialize(*args)

# Set a limit (page size) value.
#
# +value+:: Integer value held in a String. Must be >= 1.
# +value+:: Integer value held in a String. Must be >= 0.
#
# Per SCIM 2.0 RFC 7644 Section 3.4.2.4: "A value of '0' indicates that
# no resource results are to be returned except for 'totalResults'."
#
# Raises exceptions if given non-numeric, zero or negative input.
# Raises exceptions if given non-numeric or negative input.
#
def limit=(value)
value = value&.to_s
return if value.blank? # NOTE EARLY EXIT

validate_numericality(value)
input = value.to_i
raise if input < 1
raise if input < 0
@limit = input
end

Expand Down
11 changes: 8 additions & 3 deletions spec/models/scimitar/lists/count_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@
expect { @instance.limit = 'A' }.to raise_error(RuntimeError)
end

it 'complains about attempts to set zero values' do
expect { @instance.limit = '0' }.to raise_error(RuntimeError)
it 'allows count=0 per SCIM 2.0 specification (RFC 7644)' do
expect { @instance.limit = '0' }.to_not raise_error
expect(@instance.limit).to eql(0)
end

it 'complains about attempts to set zero values' do
it 'allows count=0 as integer' do
expect { @instance.limit = 0 }.to_not raise_error
expect(@instance.limit).to eql(0)
end

it 'complains about attempts to set negative values' do
expect { @instance.limit = '-10' }.to raise_error(RuntimeError)
end
end # "context 'on-read error checking' do"
Expand Down
86 changes: 86 additions & 0 deletions spec/requests/active_record_backed_resources_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,92 @@
usernames = result['Resources'].map { |resource| resource['userName'] }
expect(usernames).to match_array(['2', '3'])
end

# SCIM 2.0 RFC 7644 Section 3.4.2.4: count=0 support
context 'with count=0' do
it 'returns 200 OK' do
get '/Users', params: {
format: :scim,
count: 0
}

expect(response.status).to eql(200)
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
end

it 'returns totalResults with actual count' do
get '/Users', params: {
format: :scim,
count: 0
}

result = JSON.parse(response.body)
expect(result['totalResults']).to eql(3)
end

it 'returns itemsPerPage as 0' do
get '/Users', params: {
format: :scim,
count: 0
}

result = JSON.parse(response.body)
expect(result['itemsPerPage']).to eql(0)
end

it 'returns empty Resources array' do
get '/Users', params: {
format: :scim,
count: 0
}

result = JSON.parse(response.body)
expect(result['Resources']).to eql([])
end

it 'respects startIndex parameter' do
get '/Users', params: {
format: :scim,
count: 0,
startIndex: 5
}

result = JSON.parse(response.body)
expect(result['startIndex']).to eql(5)
end

it 'applies filters when calculating totalResults' do
get '/Users', params: {
format: :scim,
count: 0,
filter: 'name.familyName eq "Bar"'
}

result = JSON.parse(response.body)
expect(result['totalResults']).to eql(1)
expect(result['Resources']).to eql([])
end

it 'does not query for records (performance optimization)' do
# We should get the count but not fetch records
query_double = instance_double(ActiveRecord::Relation)
allow(MockUser).to receive(:all).and_return(query_double)
allow(query_double).to receive(:count).and_return(3)

# Should NOT call order, offset, limit, or to_a when count=0
expect(query_double).not_to receive(:order)
expect(query_double).not_to receive(:offset)
expect(query_double).not_to receive(:limit)
expect(query_double).not_to receive(:to_a)

get '/Users', params: {
format: :scim,
count: 0
}

expect(response.status).to eql(200)
end
end # "context 'with count=0' do"
end # "context 'with items' do"

context 'with bad calls' do
Expand Down