api: More robust tests for log properties.
[arvados.git] / services / api / test / unit / log_test.rb
1 require 'test_helper'
2
3 class LogTest < ActiveSupport::TestCase
4   include CurrentApiClient
5
6   EVENT_TEST_METHODS = {
7     :create => [:created_at, :assert_nil, :assert_not_nil],
8     :update => [:modified_at, :assert_not_nil, :assert_not_nil],
9     :destroy => [nil, :assert_not_nil, :assert_nil],
10   }
11
12   def setup
13     @start_time = Time.now
14     @log_count = 1
15   end
16
17   def assert_properties(test_method, event, props, *keys)
18     verb = (test_method == :assert_nil) ? 'have nil' : 'define'
19     keys.each do |prop_name|
20       assert_includes(props, prop_name, "log properties missing #{prop_name}")
21       self.send(test_method, props[prop_name],
22                 "#{event.to_s} log should #{verb} #{prop_name}")
23     end
24   end
25
26   def get_logs_about(thing)
27     Log.where(object_uuid: thing.uuid).order("created_at ASC").all
28   end
29
30   def assert_logged(thing, event_type)
31     logs = get_logs_about(thing)
32     assert_equal(@log_count, logs.size, "log count mismatch")
33     @log_count += 1
34     log = logs.last
35     props = log.properties
36     assert_equal(current_user.andand.uuid, log.owner_uuid,
37                  "log is not owned by current user")
38     assert_equal(current_user.andand.uuid, log.modified_by_user_uuid,
39                  "log is not 'modified by' current user")
40     assert_equal(current_api_client.andand.uuid, log.modified_by_client_uuid,
41                  "log is not 'modified by' current client")
42     assert_equal(thing.kind, log.object_kind, "log kind mismatch")
43     assert_equal(thing.uuid, log.object_uuid, "log UUID mismatch")
44     assert_equal(event_type.to_s, log.event_type, "log event type mismatch")
45     time_method, old_props_test, new_props_test = EVENT_TEST_METHODS[event_type]
46     if time_method.nil? or (timestamp = thing.send(time_method)).nil?
47       assert(log.event_at >= @start_time, "log timestamp too old")
48     else
49       assert_in_delta(timestamp, log.event_at, 1, "log timestamp mismatch")
50     end
51     assert_properties(old_props_test, event_type, props,
52                       'old_etag', 'old_attributes')
53     assert_properties(new_props_test, event_type, props,
54                       'new_etag', 'new_attributes')
55     yield props if block_given?
56   end
57
58   def set_user_from_auth(auth_name)
59     client_auth = api_client_authorizations(auth_name)
60     Thread.current[:api_client_authorization] = client_auth
61     Thread.current[:api_client] = client_auth.api_client
62     Thread.current[:user] = client_auth.user
63   end
64
65   test "creating a user makes a log" do
66     set_user_from_auth :admin_trustedclient
67     u = User.new(first_name: "Log", last_name: "Test")
68     u.save!
69     assert_logged(u, :create) do |props|
70       assert_equal(u.etag, props['new_etag'], "new user etag mismatch")
71       assert_equal(u.first_name, props['new_attributes']['first_name'],
72                    "new user first name mismatch")
73       assert_equal(u.last_name, props['new_attributes']['last_name'],
74                    "new user first name mismatch")
75     end
76   end
77
78   test "updating a virtual machine makes a log" do
79     set_user_from_auth :admin_trustedclient
80     vm = virtual_machines(:testvm)
81     orig_etag = vm.etag
82     vm.hostname = 'testvm.testshell'
83     vm.save!
84     assert_logged(vm, :update) do |props|
85       assert_equal(orig_etag, props['old_etag'], "updated VM old etag mismatch")
86       assert_equal(vm.etag, props['new_etag'], "updated VM new etag mismatch")
87       assert_equal('testvm.shell', props['old_attributes']['hostname'],
88                    "updated VM old name mismatch")
89       assert_equal('testvm.testshell', props['new_attributes']['hostname'],
90                    "updated VM new name mismatch")
91     end
92   end
93
94   test "destroying an authorization makes a log" do
95     set_user_from_auth :admin_trustedclient
96     auth = api_client_authorizations(:spectator)
97     orig_etag = auth.etag
98     orig_attrs = auth.attributes
99     auth.destroy
100     assert_logged(auth, :destroy) do |props|
101       assert_equal(orig_etag, props['old_etag'], "destroyed auth etag mismatch")
102       assert_equal(orig_attrs, props['old_attributes'],
103                    "destroyed auth attributes mismatch")
104     end
105   end
106
107   test "saving an unchanged client still makes a log" do
108     set_user_from_auth :admin_trustedclient
109     client = api_clients(:untrusted)
110     client.is_trusted = client.is_trusted
111     client.save!
112     assert_logged(client, :update) do |props|
113       ['old', 'new'].each do |age|
114         assert_equal(client.etag, props["#{age}_etag"],
115                      "unchanged client #{age} etag mismatch")
116         assert_equal(client.attributes, props["#{age}_attributes"],
117                      "unchanged client #{age} attributes mismatch")
118       end
119     end
120   end
121
122   test "updating a group twice makes two logs" do
123     set_user_from_auth :admin_trustedclient
124     group = groups(:empty_lonely_group)
125     name1 = group.name
126     name2 = "#{name1} under test"
127     group.name = name2
128     group.save!
129     assert_logged(group, :update) do |props|
130       assert_equal(name1, props['old_attributes']['name'],
131                    "group start name mismatch")
132       assert_equal(name2, props['new_attributes']['name'],
133                    "group updated name mismatch")
134     end
135     group.name = name1
136     group.save!
137     assert_logged(group, :update) do |props|
138       assert_equal(name2, props['old_attributes']['name'],
139                    "group pre-revert name mismatch")
140       assert_equal(name1, props['new_attributes']['name'],
141                    "group final name mismatch")
142     end
143   end
144
145   test "making a log doesn't get logged" do
146     set_user_from_auth :active_trustedclient
147     log = Log.new
148     log.save!
149     assert_equal(0, get_logs_about(log).size, "made a Log about a Log")
150   end
151
152   test "non-admins can't modify or delete logs" do
153     set_user_from_auth :active_trustedclient
154     log = Log.new(summary: "immutable log test")
155     assert_nothing_raised { log.save! }
156     log.summary = "log mutation test should fail"
157     assert_raise(ArvadosModel::PermissionDeniedError) { log.save! }
158     assert_raise(ArvadosModel::PermissionDeniedError) { log.destroy }
159   end
160
161   test "admins can modify and delete logs" do
162     set_user_from_auth :admin_trustedclient
163     log = Log.new(summary: "admin log mutation test")
164     assert_nothing_raised { log.save! }
165     log.summary = "admin mutated log test"
166     assert_nothing_raised { log.save! }
167     assert_nothing_raised { log.destroy }
168   end
169
170   test "failure saving log causes failure saving object" do
171     Log.class_eval do
172       alias_method :_orig_validations, :perform_validations
173       def perform_validations(options)
174         false
175       end
176     end
177     begin
178       set_user_from_auth :active_trustedclient
179       user = users(:active)
180       user.first_name = 'Test'
181       assert_raise(ActiveRecord::RecordInvalid) { user.save! }
182     ensure
183       Log.class_eval do
184         alias_method :perform_validations, :_orig_validations
185       end
186     end
187   end
188
189   test "don't log changes only to ApiClientAuthorization.last_used_*" do
190     set_user_from_auth :admin_trustedclient
191     auth = api_client_authorizations(:spectator)
192     start_log_count = get_logs_about(auth).size
193     auth.last_used_at = Time.now
194     auth.last_used_by_ip_address = '::1'
195     auth.save!
196     assert_equal(start_log_count, get_logs_about(auth).size,
197                  "log count changed after 'using' ApiClientAuthorization")
198     auth.created_by_ip_address = '::1'
199     auth.save!
200     assert_logged(auth, :update)
201   end
202 end