Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorSean McGivern <sean@mcgivern.me.uk>2017-04-19 17:48:31 +0300
committerLin Jen-Shin <godfat@godfat.org>2017-04-20 15:19:22 +0300
commitb9fff8153960a01926ab74d06e6c75c99b9bd895 (patch)
tree0b42a002f2a0c7508210fcc62e6808e6119c58af /spec
parent5c0237019daea03f1fe66e7eb277292bd9a325ac (diff)
Merge branch 'usage-ping-port' into 'master'
Usage ping port Closes #27750 See merge request !10481
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb37
-rw-r--r--spec/controllers/sessions_controller_spec.rb10
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb70
-rw-r--r--spec/lib/gitlab/user_activities_spec.rb127
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb49
-rw-r--r--spec/requests/api/internal_spec.rb15
-rw-r--r--spec/requests/api/users_spec.rb119
-rw-r--r--spec/requests/git_http_spec.rb9
-rw-r--r--spec/services/cohorts_service_spec.rb99
-rw-r--r--spec/services/event_create_service_spec.rb15
-rw-r--r--spec/services/users/activity_service_spec.rb48
-rw-r--r--spec/support/matchers/user_activity_matchers.rb5
-rw-r--r--spec/support/user_activities_helpers.rb7
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb23
-rw-r--r--spec/workers/schedule_update_user_activity_worker_spec.rb25
-rw-r--r--spec/workers/update_user_activity_worker_spec.rb35
16 files changed, 653 insertions, 40 deletions
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 5dd8f66343f..2565622f8df 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -3,12 +3,49 @@ require 'spec_helper'
describe Admin::ApplicationSettingsController do
include StubENV
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
let(:admin) { create(:admin) }
+ let(:user) { create(:user)}
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
+ describe 'GET #usage_data with no access' do
+ before do
+ sign_in(user)
+ end
+
+ it 'returns 404' do
+ get :usage_data, format: :html
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ describe 'GET #usage_data' do
+ before do
+ sign_in(admin)
+ end
+
+ it 'returns HTML data' do
+ get :usage_data, format: :html
+
+ expect(response.body).to start_with('<span')
+ expect(response.status).to eq(200)
+ end
+
+ it 'returns JSON data' do
+ get :usage_data, format: :json
+
+ body = JSON.parse(response.body)
+ expect(body["version"]).to eq(Gitlab::VERSION)
+ expect(body).to include('counts')
+ expect(response.status).to eq(200)
+ end
+ end
+
describe 'PUT #update' do
before do
sign_in(admin)
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 9c16a7bc08b..038132cffe0 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -16,7 +16,9 @@ describe SessionsController do
end
end
- context 'when using valid password' do
+ context 'when using valid password', :redis do
+ include UserActivitiesHelpers
+
let(:user) { create(:user) }
it 'authenticates user correctly' do
@@ -37,6 +39,12 @@ describe SessionsController do
subject.sign_out user
end
end
+
+ it 'updates the user activity' do
+ expect do
+ post(:create, user: { login: user.username, password: user.password })
+ end.to change { user_activity(user) }
+ end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
new file mode 100644
index 00000000000..7f21288cf88
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+describe Gitlab::UsageData do
+ let!(:project) { create(:empty_project) }
+ let!(:project2) { create(:empty_project) }
+ let!(:board) { create(:board, project: project) }
+
+ describe '#data' do
+ subject { Gitlab::UsageData.data }
+
+ it "gathers usage data" do
+ expect(subject.keys).to match_array(%i(
+ active_user_count
+ counts
+ recorded_at
+ mattermost_enabled
+ edition
+ version
+ uuid
+ ))
+ end
+
+ it "gathers usage counts" do
+ count_data = subject[:counts]
+
+ expect(count_data[:boards]).to eq(1)
+ expect(count_data[:projects]).to eq(2)
+
+ expect(count_data.keys).to match_array(%i(
+ boards
+ ci_builds
+ ci_pipelines
+ ci_runners
+ ci_triggers
+ deploy_keys
+ deployments
+ environments
+ groups
+ issues
+ keys
+ labels
+ lfs_objects
+ merge_requests
+ milestones
+ notes
+ projects
+ projects_prometheus_active
+ pages_domains
+ protected_branches
+ releases
+ services
+ snippets
+ todos
+ uploads
+ web_hooks
+ ))
+ end
+ end
+
+ describe '#license_usage_data' do
+ subject { Gitlab::UsageData.license_usage_data }
+
+ it "gathers license data" do
+ expect(subject[:uuid]).to eq(current_application_settings.uuid)
+ expect(subject[:version]).to eq(Gitlab::VERSION)
+ expect(subject[:active_user_count]).to eq(User.active.count)
+ expect(subject[:recorded_at]).to be_a(Time)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/user_activities_spec.rb b/spec/lib/gitlab/user_activities_spec.rb
new file mode 100644
index 00000000000..187d88c8c58
--- /dev/null
+++ b/spec/lib/gitlab/user_activities_spec.rb
@@ -0,0 +1,127 @@
+require 'spec_helper'
+
+describe Gitlab::UserActivities, :redis, lib: true do
+ let(:now) { Time.now }
+
+ describe '.record' do
+ context 'with no time given' do
+ it 'uses Time.now and records an activity in Redis' do
+ Timecop.freeze do
+ now # eager-load now
+ described_class.record(42)
+ end
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
+ end
+ end
+ end
+
+ context 'with a time given' do
+ it 'uses the given time and records an activity in Redis' do
+ described_class.record(42, now)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
+ end
+ end
+ end
+ end
+
+ describe '.delete' do
+ context 'with a single key' do
+ context 'and key exists' do
+ it 'removes the pair from Redis' do
+ described_class.record(42, now)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
+ end
+
+ subject.delete(42)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
+ end
+ end
+ end
+
+ context 'and key does not exist' do
+ it 'removes the pair from Redis' do
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
+ end
+
+ subject.delete(42)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
+ end
+ end
+ end
+ end
+
+ context 'with multiple keys' do
+ context 'and all keys exist' do
+ it 'removes the pair from Redis' do
+ described_class.record(41, now)
+ described_class.record(42, now)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['41', now.to_i.to_s], ['42', now.to_i.to_s]]])
+ end
+
+ subject.delete(41, 42)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
+ end
+ end
+ end
+
+ context 'and some keys does not exist' do
+ it 'removes the existing pair from Redis' do
+ described_class.record(42, now)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
+ end
+
+ subject.delete(41, 42)
+
+ Gitlab::Redis.with do |redis|
+ expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
+ end
+ end
+ end
+ end
+ end
+
+ describe 'Enumerable' do
+ before do
+ described_class.record(40, now)
+ described_class.record(41, now)
+ described_class.record(42, now)
+ end
+
+ it 'allows to read the activities sequentially' do
+ expected = { '40' => now.to_i.to_s, '41' => now.to_i.to_s, '42' => now.to_i.to_s }
+
+ actual = described_class.new.each_with_object({}) do |(key, time), actual|
+ actual[key] = time
+ end
+
+ expect(actual).to eq(expected)
+ end
+
+ context 'with many records' do
+ before do
+ 1_000.times { |i| described_class.record(i, now) }
+ end
+
+ it 'is possible to loop through all the records' do
+ expect(described_class.new.count).to eq(1_000)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
new file mode 100644
index 00000000000..1db9bc002ae
--- /dev/null
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -0,0 +1,49 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
+
+describe MigrateUserActivitiesToUsersLastActivityOn, :redis do
+ let(:migration) { described_class.new }
+ let!(:user_active_1) { create(:user) }
+ let!(:user_active_2) { create(:user) }
+
+ def record_activity(user, time)
+ Gitlab::Redis.with do |redis|
+ redis.zadd(described_class::USER_ACTIVITY_SET_KEY, time.to_i, user.username)
+ end
+ end
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ record_activity(user_active_1, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months)
+ record_activity(user_active_2, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months)
+ mute_stdout { migration.up }
+ end
+
+ describe '#up' do
+ it 'fills last_activity_on from the legacy Redis Sorted Set' do
+ expect(user_active_1.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months).to_date)
+ expect(user_active_2.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months).to_date)
+ end
+ end
+
+ describe '#down' do
+ it 'sets last_activity_on to NULL for all users' do
+ mute_stdout { migration.down }
+
+ expect(user_active_1.reload.last_activity_on).to be_nil
+ expect(user_active_2.reload.last_activity_on).to be_nil
+ end
+ end
+
+ def mute_stdout
+ orig_stdout = $stdout
+ $stdout = StringIO.new
+ yield
+ $stdout = orig_stdout
+ end
+end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 4be67df5a00..3d6010ede73 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -147,10 +147,15 @@ describe API::Internal, api: true do
end
end
- describe "POST /internal/allowed" do
+ describe "POST /internal/allowed", :redis do
context "access granted" do
before do
project.team << [user, :developer]
+ Timecop.freeze
+ end
+
+ after do
+ Timecop.return
end
context 'with env passed as a JSON' do
@@ -176,6 +181,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
+ expect(user).not_to have_an_activity_record
end
end
@@ -186,6 +192,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
+ expect(user).to have_an_activity_record
end
end
@@ -196,6 +203,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(user).to have_an_activity_record
end
end
@@ -206,6 +214,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(user).not_to have_an_activity_record
end
context 'project as /namespace/project' do
@@ -241,6 +250,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_falsey
+ expect(user).not_to have_an_activity_record
end
end
@@ -250,6 +260,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_falsey
+ expect(user).not_to have_an_activity_record
end
end
end
@@ -267,6 +278,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_falsey
+ expect(user).not_to have_an_activity_record
end
end
@@ -276,6 +288,7 @@ describe API::Internal, api: true do
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_falsey
+ expect(user).not_to have_an_activity_record
end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index f793c0db2f3..ea9b886e995 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe API::Users, api: true do
+describe API::Users, api: true do
include ApiHelpers
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
let(:admin) { create(:admin) }
- let(:key) { create(:key, user: user) }
- let(:email) { create(:email, user: user) }
+ let(:key) { create(:key, user: user) }
+ let(:email) { create(:email, user: user) }
let(:omniauth_user) { create(:omniauth_user) }
let(:ldap_user) { create(:omniauth_user, provider: 'ldapmain') }
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
@@ -129,7 +129,7 @@ describe API::Users, api: true do
end
describe "POST /users" do
- before{ admin }
+ before { admin }
it "creates user" do
expect do
@@ -214,9 +214,9 @@ describe API::Users, api: true do
it "does not create user with invalid email" do
post api('/users', admin),
- email: 'invalid email',
- password: 'password',
- name: 'test'
+ email: 'invalid email',
+ password: 'password',
+ name: 'test'
expect(response).to have_http_status(400)
end
@@ -242,12 +242,12 @@ describe API::Users, api: true do
it 'returns 400 error if user does not validate' do
post api('/users', admin),
- password: 'pass',
- email: 'test@example.com',
- username: 'test!',
- name: 'test',
- bio: 'g' * 256,
- projects_limit: -1
+ password: 'pass',
+ email: 'test@example.com',
+ username: 'test!',
+ name: 'test',
+ bio: 'g' * 256,
+ projects_limit: -1
expect(response).to have_http_status(400)
expect(json_response['message']['password']).
to eq(['is too short (minimum is 8 characters)'])
@@ -267,19 +267,19 @@ describe API::Users, api: true do
context 'with existing user' do
before do
post api('/users', admin),
- email: 'test@example.com',
- password: 'password',
- username: 'test',
- name: 'foo'
+ email: 'test@example.com',
+ password: 'password',
+ username: 'test',
+ name: 'foo'
end
it 'returns 409 conflict error if user with same email exists' do
expect do
post api('/users', admin),
- name: 'foo',
- email: 'test@example.com',
- password: 'password',
- username: 'foo'
+ name: 'foo',
+ email: 'test@example.com',
+ password: 'password',
+ username: 'foo'
end.to change { User.count }.by(0)
expect(response).to have_http_status(409)
expect(json_response['message']).to eq('Email has already been taken')
@@ -288,10 +288,10 @@ describe API::Users, api: true do
it 'returns 409 conflict error if same username exists' do
expect do
post api('/users', admin),
- name: 'foo',
- email: 'foo@example.com',
- password: 'password',
- username: 'test'
+ name: 'foo',
+ email: 'foo@example.com',
+ password: 'password',
+ username: 'test'
end.to change { User.count }.by(0)
expect(response).to have_http_status(409)
expect(json_response['message']).to eq('Username has already been taken')
@@ -416,12 +416,12 @@ describe API::Users, api: true do
it 'returns 400 error if user does not validate' do
put api("/users/#{user.id}", admin),
- password: 'pass',
- email: 'test@example.com',
- username: 'test!',
- name: 'test',
- bio: 'g' * 256,
- projects_limit: -1
+ password: 'pass',
+ email: 'test@example.com',
+ username: 'test!',
+ name: 'test',
+ bio: 'g' * 256,
+ projects_limit: -1
expect(response).to have_http_status(400)
expect(json_response['message']['password']).
to eq(['is too short (minimum is 8 characters)'])
@@ -488,7 +488,7 @@ describe API::Users, api: true do
key_attrs = attributes_for :key
expect do
post api("/users/#{user.id}/keys", admin), key_attrs
- end.to change{ user.keys.count }.by(1)
+ end.to change { user.keys.count }.by(1)
end
it "returns 400 for invalid ID" do
@@ -580,7 +580,7 @@ describe API::Users, api: true do
email_attrs = attributes_for :email
expect do
post api("/users/#{user.id}/emails", admin), email_attrs
- end.to change{ user.emails.count }.by(1)
+ end.to change { user.emails.count }.by(1)
end
it "returns a 400 for invalid ID" do
@@ -842,7 +842,7 @@ describe API::Users, api: true do
key_attrs = attributes_for :key
expect do
post api("/user/keys", user), key_attrs
- end.to change{ user.keys.count }.by(1)
+ end.to change { user.keys.count }.by(1)
expect(response).to have_http_status(201)
end
@@ -880,7 +880,7 @@ describe API::Users, api: true do
delete api("/user/keys/#{key.id}", user)
expect(response).to have_http_status(204)
- end.to change{user.keys.count}.by(-1)
+ end.to change { user.keys.count}.by(-1)
end
it "returns 404 if key ID not found" do
@@ -963,7 +963,7 @@ describe API::Users, api: true do
email_attrs = attributes_for :email
expect do
post api("/user/emails", user), email_attrs
- end.to change{ user.emails.count }.by(1)
+ end.to change { user.emails.count }.by(1)
expect(response).to have_http_status(201)
end
@@ -989,7 +989,7 @@ describe API::Users, api: true do
delete api("/user/emails/#{email.id}", user)
expect(response).to have_http_status(204)
- end.to change{user.emails.count}.by(-1)
+ end.to change { user.emails.count}.by(-1)
end
it "returns 404 if email ID not found" do
@@ -1158,6 +1158,49 @@ describe API::Users, api: true do
end
end
+ context "user activities", :redis do
+ let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) }
+ let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) }
+
+ context 'last activity as normal user' do
+ it 'has no permission' do
+ get api("/user/activities", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'as admin' do
+ it 'returns the activities from the last 6 months' do
+ get api("/user/activities", admin)
+
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(1)
+
+ activity = json_response.last
+
+ expect(activity['username']).to eq(newly_active_user.username)
+ expect(activity['last_activity_on']).to eq(2.days.ago.to_date.to_s)
+ expect(activity['last_activity_at']).to eq(2.days.ago.to_date.to_s)
+ end
+
+ context 'passing a :from parameter' do
+ it 'returns the activities from the given date' do
+ get api("/user/activities?from=2000-1-1", admin)
+
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(2)
+
+ activity = json_response.first
+
+ expect(activity['username']).to eq(old_active_user.username)
+ expect(activity['last_activity_on']).to eq(Time.utc(2000, 1, 1).to_date.to_s)
+ expect(activity['last_activity_at']).to eq(Time.utc(2000, 1, 1).to_date.to_s)
+ end
+ end
+ end
+ end
+
describe 'GET /users/:user_id/impersonation_tokens' do
let!(:active_personal_access_token) { create(:personal_access_token, user: user) }
let!(:revoked_personal_access_token) { create(:personal_access_token, :revoked, user: user) }
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 006d6a6af1c..316742ff076 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -3,6 +3,7 @@ require "spec_helper"
describe 'Git HTTP requests', lib: true do
include GitHttpHelpers
include WorkhorseHelpers
+ include UserActivitiesHelpers
it "gives WWW-Authenticate hints" do
clone_get('doesnt/exist.git')
@@ -255,6 +256,14 @@ describe 'Git HTTP requests', lib: true do
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end
+
+ it 'updates the user last activity', :redis do
+ expect(user_activity(user)).to be_nil
+
+ download(path, env) do |response|
+ expect(user_activity(user)).to be_present
+ end
+ end
end
context "when an oauth token is provided" do
diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb
new file mode 100644
index 00000000000..1e99442fdcb
--- /dev/null
+++ b/spec/services/cohorts_service_spec.rb
@@ -0,0 +1,99 @@
+require 'spec_helper'
+
+describe CohortsService do
+ describe '#execute' do
+ def month_start(months_ago)
+ months_ago.months.ago.beginning_of_month.to_date
+ end
+
+ # In the interests of speed and clarity, this example has minimal data.
+ it 'returns a list of user cohorts' do
+ 6.times do |months_ago|
+ months_ago_time = (months_ago * 2).months.ago
+
+ create(:user, created_at: months_ago_time, last_activity_on: Time.now)
+ create(:user, created_at: months_ago_time, last_activity_on: months_ago_time)
+ end
+
+ create(:user) # this user is inactive and belongs to the current month
+
+ expected_cohorts = [
+ {
+ registration_month: month_start(11),
+ activity_months: Array.new(12) { { total: 0, percentage: 0 } },
+ total: 0,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(10),
+ activity_months: [{ total: 2, percentage: 100 }] + Array.new(10) { { total: 1, percentage: 50 } },
+ total: 2,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(9),
+ activity_months: Array.new(10) { { total: 0, percentage: 0 } },
+ total: 0,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(8),
+ activity_months: [{ total: 2, percentage: 100 }] + Array.new(8) { { total: 1, percentage: 50 } },
+ total: 2,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(7),
+ activity_months: Array.new(8) { { total: 0, percentage: 0 } },
+ total: 0,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(6),
+ activity_months: [{ total: 2, percentage: 100 }] + Array.new(6) { { total: 1, percentage: 50 } },
+ total: 2,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(5),
+ activity_months: Array.new(6) { { total: 0, percentage: 0 } },
+ total: 0,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(4),
+ activity_months: [{ total: 2, percentage: 100 }] + Array.new(4) { { total: 1, percentage: 50 } },
+ total: 2,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(3),
+ activity_months: Array.new(4) { { total: 0, percentage: 0 } },
+ total: 0,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(2),
+ activity_months: [{ total: 2, percentage: 100 }] + Array.new(2) { { total: 1, percentage: 50 } },
+ total: 2,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(1),
+ activity_months: Array.new(2) { { total: 0, percentage: 0 } },
+ total: 0,
+ inactive: 0
+ },
+ {
+ registration_month: month_start(0),
+ activity_months: [{ total: 2, percentage: 100 }],
+ total: 2,
+ inactive: 1
+ },
+ ]
+
+ expect(described_class.new.execute).to eq(months_included: 12,
+ cohorts: expected_cohorts)
+ end
+ end
+end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index f2c2009bcbf..b06cefe071d 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe EventCreateService, services: true do
+ include UserActivitiesHelpers
+
let(:service) { EventCreateService.new }
describe 'Issues' do
@@ -111,6 +113,19 @@ describe EventCreateService, services: true do
end
end
+ describe '#push', :redis do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ it 'creates a new event' do
+ expect { service.push(project, user, {}) }.to change { Event.count }
+ end
+
+ it 'updates user last activity' do
+ expect { service.push(project, user, {}) }.to change { user_activity(user) }
+ end
+ end
+
describe 'Project' do
let(:user) { create :user }
let(:project) { create(:empty_project) }
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
new file mode 100644
index 00000000000..8d67ebe3231
--- /dev/null
+++ b/spec/services/users/activity_service_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Users::ActivityService, services: true do
+ include UserActivitiesHelpers
+
+ let(:user) { create(:user) }
+
+ subject(:service) { described_class.new(user, 'type') }
+
+ describe '#execute', :redis do
+ context 'when last activity is nil' do
+ before do
+ service.execute
+ end
+
+ it 'sets the last activity timestamp for the user' do
+ expect(last_hour_user_ids).to eq([user.id])
+ end
+
+ it 'updates the same user' do
+ service.execute
+
+ expect(last_hour_user_ids).to eq([user.id])
+ end
+
+ it 'updates the timestamp of an existing user' do
+ Timecop.freeze(Date.tomorrow) do
+ expect { service.execute }.to change { user_activity(user) }.to(Time.now.to_i.to_s)
+ end
+ end
+
+ describe 'other user' do
+ it 'updates other user' do
+ other_user = create(:user)
+ described_class.new(other_user, 'type').execute
+
+ expect(last_hour_user_ids).to match_array([user.id, other_user.id])
+ end
+ end
+ end
+ end
+
+ def last_hour_user_ids
+ Gitlab::UserActivities.new.
+ select { |k, v| v >= 1.hour.ago.to_i.to_s }.
+ map { |k, _| k.to_i }
+ end
+end
diff --git a/spec/support/matchers/user_activity_matchers.rb b/spec/support/matchers/user_activity_matchers.rb
new file mode 100644
index 00000000000..ce3b683b6d2
--- /dev/null
+++ b/spec/support/matchers/user_activity_matchers.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :have_an_activity_record do |expected|
+ match do |user|
+ expect(Gitlab::UserActivities.new.find { |k, _| k == user.id.to_s }).to be_present
+ end
+end
diff --git a/spec/support/user_activities_helpers.rb b/spec/support/user_activities_helpers.rb
new file mode 100644
index 00000000000..f7ca9a31edd
--- /dev/null
+++ b/spec/support/user_activities_helpers.rb
@@ -0,0 +1,7 @@
+module UserActivitiesHelpers
+ def user_activity(user)
+ Gitlab::UserActivities.new.
+ find { |k, _| k == user.id.to_s }&.
+ second
+ end
+end
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
new file mode 100644
index 00000000000..b6c080f36f4
--- /dev/null
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe GitlabUsagePingWorker do
+ subject { GitlabUsagePingWorker.new }
+
+ it "sends POST request" do
+ stub_application_setting(usage_ping_enabled: true)
+
+ stub_request(:post, "https://version.gitlab.com/usage_data").
+ to_return(status: 200, body: '', headers: {})
+ expect(Gitlab::UsageData).to receive(:to_json).with({ force_refresh: true }).and_call_original
+ expect(subject).to receive(:try_obtain_lease).and_return(true)
+
+ expect(subject.perform.response.code.to_i).to eq(200)
+ end
+
+ it "does not run if usage ping is disabled" do
+ stub_application_setting(usage_ping_enabled: false)
+
+ expect(subject).not_to receive(:try_obtain_lease)
+ expect(subject).not_to receive(:perform)
+ end
+end
diff --git a/spec/workers/schedule_update_user_activity_worker_spec.rb b/spec/workers/schedule_update_user_activity_worker_spec.rb
new file mode 100644
index 00000000000..e583c3203aa
--- /dev/null
+++ b/spec/workers/schedule_update_user_activity_worker_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe ScheduleUpdateUserActivityWorker, :redis do
+ let(:now) { Time.now }
+
+ before do
+ Gitlab::UserActivities.record('1', now)
+ Gitlab::UserActivities.record('2', now)
+ end
+
+ it 'schedules UpdateUserActivityWorker once' do
+ expect(UpdateUserActivityWorker).to receive(:perform_async).with({ '1' => now.to_i.to_s, '2' => now.to_i.to_s })
+
+ subject.perform
+ end
+
+ context 'when specifying a batch size' do
+ it 'schedules UpdateUserActivityWorker twice' do
+ expect(UpdateUserActivityWorker).to receive(:perform_async).with({ '1' => now.to_i.to_s })
+ expect(UpdateUserActivityWorker).to receive(:perform_async).with({ '2' => now.to_i.to_s })
+
+ subject.perform(1)
+ end
+ end
+end
diff --git a/spec/workers/update_user_activity_worker_spec.rb b/spec/workers/update_user_activity_worker_spec.rb
new file mode 100644
index 00000000000..43e9511f116
--- /dev/null
+++ b/spec/workers/update_user_activity_worker_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe UpdateUserActivityWorker, :redis do
+ let(:user_active_2_days_ago) { create(:user, current_sign_in_at: 10.months.ago) }
+ let(:user_active_yesterday_1) { create(:user) }
+ let(:user_active_yesterday_2) { create(:user) }
+ let(:user_active_today) { create(:user) }
+ let(:data) do
+ {
+ user_active_2_days_ago.id.to_s => 2.days.ago.at_midday.to_i.to_s,
+ user_active_yesterday_1.id.to_s => 1.day.ago.at_midday.to_i.to_s,
+ user_active_yesterday_2.id.to_s => 1.day.ago.at_midday.to_i.to_s,
+ user_active_today.id.to_s => Time.now.to_i.to_s
+ }
+ end
+
+ it 'updates users.last_activity_on' do
+ subject.perform(data)
+
+ aggregate_failures do
+ expect(user_active_2_days_ago.reload.last_activity_on).to eq(2.days.ago.to_date)
+ expect(user_active_yesterday_1.reload.last_activity_on).to eq(1.day.ago.to_date)
+ expect(user_active_yesterday_2.reload.last_activity_on).to eq(1.day.ago.to_date)
+ expect(user_active_today.reload.reload.last_activity_on).to eq(Date.today)
+ end
+ end
+
+ it 'deletes the pairs from Redis' do
+ data.each { |id, time| Gitlab::UserActivities.record(id, time) }
+
+ subject.perform(data)
+
+ expect(Gitlab::UserActivities.new.to_a).to be_empty
+ end
+end