@@ -120,6 +120,131 @@ def setup
120120 assert_not_includes expired_keys , active_key
121121 end
122122
123+ # === Usage Analytics Scopes ===
124+ # These scopes help admin dashboards analyze API key usage patterns
125+
126+ test ".never_used scope returns keys that have never been used" do
127+ used_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Used" )
128+ used_key . update_column ( :last_used_at , 1 . day . ago )
129+
130+ never_used_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Never Used" )
131+ # last_used_at is nil by default
132+
133+ never_used_keys = ApiKeys ::ApiKey . never_used . to_a
134+ assert_includes never_used_keys , never_used_key
135+ assert_not_includes never_used_keys , used_key
136+ end
137+
138+ test ".used scope returns keys that have been used at least once" do
139+ used_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Used" )
140+ used_key . update_column ( :last_used_at , 1 . day . ago )
141+
142+ never_used_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Never Used" )
143+
144+ used_keys = ApiKeys ::ApiKey . used . to_a
145+ assert_includes used_keys , used_key
146+ assert_not_includes used_keys , never_used_key
147+ end
148+
149+ test ".by_requests scope orders by requests_count descending" do
150+ low_usage = ApiKeys ::ApiKey . create! ( owner : @user , name : "Low" )
151+ low_usage . update_column ( :requests_count , 10 )
152+
153+ high_usage = ApiKeys ::ApiKey . create! ( owner : @user , name : "High" )
154+ high_usage . update_column ( :requests_count , 1000 )
155+
156+ medium_usage = ApiKeys ::ApiKey . create! ( owner : @user , name : "Medium" )
157+ medium_usage . update_column ( :requests_count , 100 )
158+
159+ ordered = ApiKeys ::ApiKey . by_requests . to_a
160+ assert_equal [ high_usage , medium_usage , low_usage ] , ordered
161+ end
162+
163+ test ".by_last_used scope orders by last_used_at descending with nulls last" do
164+ old_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Old" )
165+ old_key . update_column ( :last_used_at , 7 . days . ago )
166+
167+ recent_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Recent" )
168+ recent_key . update_column ( :last_used_at , 1 . hour . ago )
169+
170+ never_used_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Never" )
171+ # last_used_at is nil
172+
173+ ordered = ApiKeys ::ApiKey . by_last_used . to_a
174+ # Recent should come first, then old, then never used (nulls last)
175+ assert_equal recent_key , ordered . first
176+ assert_equal old_key , ordered . second
177+ assert_equal never_used_key , ordered . last
178+ end
179+
180+ test ".stale scope returns active keys not used in specified period" do
181+ # Active key used recently - should NOT be stale
182+ recent_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Recent" )
183+ recent_key . update_column ( :last_used_at , 5 . days . ago )
184+
185+ # Active key not used in 30+ days - should be stale
186+ stale_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Stale" )
187+ stale_key . update_column ( :last_used_at , 45 . days . ago )
188+
189+ # Active key never used - should be stale
190+ never_used_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Never Used" )
191+
192+ # Revoked key not used in 30+ days - should NOT be stale (already inactive)
193+ revoked_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Revoked" )
194+ revoked_key . update_column ( :last_used_at , 60 . days . ago )
195+ revoked_key . revoke!
196+
197+ stale_keys = ApiKeys ::ApiKey . stale ( 30 . days ) . to_a
198+ assert_includes stale_keys , stale_key
199+ assert_includes stale_keys , never_used_key
200+ assert_not_includes stale_keys , recent_key
201+ assert_not_includes stale_keys , revoked_key
202+ end
203+
204+ test ".stale scope defaults to 30 days" do
205+ stale_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Stale" )
206+ stale_key . update_column ( :last_used_at , 31 . days . ago )
207+
208+ recent_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Recent" )
209+ recent_key . update_column ( :last_used_at , 29 . days . ago )
210+
211+ stale_keys = ApiKeys ::ApiKey . stale . to_a
212+ assert_includes stale_keys , stale_key
213+ assert_not_includes stale_keys , recent_key
214+ end
215+
216+ test ".most_used is an alias for .by_requests" do
217+ low_usage = ApiKeys ::ApiKey . create! ( owner : @user , name : "Low" )
218+ low_usage . update_column ( :requests_count , 10 )
219+
220+ high_usage = ApiKeys ::ApiKey . create! ( owner : @user , name : "High" )
221+ high_usage . update_column ( :requests_count , 1000 )
222+
223+ assert_equal ApiKeys ::ApiKey . by_requests . to_a , ApiKeys ::ApiKey . most_used . to_a
224+ end
225+
226+ test ".recently_used is an alias for .by_last_used" do
227+ old_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Old" )
228+ old_key . update_column ( :last_used_at , 7 . days . ago )
229+
230+ recent_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Recent" )
231+ recent_key . update_column ( :last_used_at , 1 . hour . ago )
232+
233+ assert_equal ApiKeys ::ApiKey . by_last_used . to_a , ApiKeys ::ApiKey . recently_used . to_a
234+ end
235+
236+ test ".inactive_for_30_days scope is equivalent to .stale with 30 days" do
237+ stale_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Stale" )
238+ stale_key . update_column ( :last_used_at , 31 . days . ago )
239+
240+ recent_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "Recent" )
241+ recent_key . update_column ( :last_used_at , 29 . days . ago )
242+
243+ assert_equal ApiKeys ::ApiKey . stale ( 30 . days ) . to_a , ApiKeys ::ApiKey . inactive_for_30_days . to_a
244+ assert_includes ApiKeys ::ApiKey . inactive_for_30_days . to_a , stale_key
245+ assert_not_includes ApiKeys ::ApiKey . inactive_for_30_days . to_a , recent_key
246+ end
247+
123248 test "revoke! sets revoked_at timestamp" do
124249 api_key = ApiKeys ::ApiKey . create! ( owner : @user , name : "To Revoke" )
125250 assert_nil api_key . revoked_at
0 commit comments