kgrift 1.3.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/KGrift/Gemfile +22 -0
  3. data/KGrift/README.md +66 -0
  4. data/KGrift/bin/kgrift +11 -0
  5. data/KGrift/grifter.yml +224 -0
  6. data/KGrift/internal_test_graphs/basic_test_graph_definition.yml +2915 -0
  7. data/KGrift/internal_test_graphs/unicode_test_graph_definition.yml +3070 -0
  8. data/KGrift/knewton_grifts/analytics_grifts.rb +103 -0
  9. data/KGrift/knewton_grifts/async_helper_grifts.rb +63 -0
  10. data/KGrift/knewton_grifts/authenticator_grifts.rb +46 -0
  11. data/KGrift/knewton_grifts/basic_grifts.rb +29 -0
  12. data/KGrift/knewton_grifts/batch_grifts.rb +14 -0
  13. data/KGrift/knewton_grifts/content_collection_grifts.rb +204 -0
  14. data/KGrift/knewton_grifts/content_collection_v1_grifts.rb +521 -0
  15. data/KGrift/knewton_grifts/content_eid_grifts.rb +41 -0
  16. data/KGrift/knewton_grifts/copy_grifts.rb +151 -0
  17. data/KGrift/knewton_grifts/deprecated_graph_and_taxonomy_grifts.rb +353 -0
  18. data/KGrift/knewton_grifts/goal_grifts.rb +203 -0
  19. data/KGrift/knewton_grifts/graph_and_taxonomy_grifts.rb +136 -0
  20. data/KGrift/knewton_grifts/graph_create_grifts.rb +34 -0
  21. data/KGrift/knewton_grifts/graph_query_grifts.rb +448 -0
  22. data/KGrift/knewton_grifts/graph_tools_grifts.rb +151 -0
  23. data/KGrift/knewton_grifts/graph_validation_grifts.rb +447 -0
  24. data/KGrift/knewton_grifts/helper_grifts.rb +92 -0
  25. data/KGrift/knewton_grifts/jmeter_data_grifts.rb +56 -0
  26. data/KGrift/knewton_grifts/learning_instance_grifts.rb +46 -0
  27. data/KGrift/knewton_grifts/looper_grifts.rb +34 -0
  28. data/KGrift/knewton_grifts/moxy_grifts.rb +64 -0
  29. data/KGrift/knewton_grifts/oauth_grifts.rb +182 -0
  30. data/KGrift/knewton_grifts/partner_grifts.rb +70 -0
  31. data/KGrift/knewton_grifts/partner_support_grifts.rb +85 -0
  32. data/KGrift/knewton_grifts/recommendation_setup_grifts.rb +215 -0
  33. data/KGrift/knewton_grifts/registration_grifts.rb +159 -0
  34. data/KGrift/knewton_grifts/registration_info_grifts.rb +23 -0
  35. data/KGrift/knewton_grifts/report_grifts.rb +122 -0
  36. data/KGrift/knewton_grifts/shell_command_grifts.rb +21 -0
  37. data/KGrift/knewton_grifts/student_flow_grifts.rb +560 -0
  38. data/KGrift/knewton_grifts/tag_grifts.rb +41 -0
  39. data/KGrift/knewton_grifts/test_data_grifts.rb +328 -0
  40. data/KGrift/knewton_grifts/test_user_grifts.rb +264 -0
  41. data/KGrift/lib/dtrace.rb +20 -0
  42. data/KGrift/lib/kgrift.rb +7 -0
  43. data/KGrift/test_data_generators/basic_book_and_taxonomies.rb +35 -0
  44. data/KGrift/test_data_generators/lo_test_graph.rb +34 -0
  45. data/KGrift/test_data_generators/partner_owned_book_and_taxonomies.rb +28 -0
  46. data/KGrift/test_data_generators/partner_owned_book_and_taxonomies_unicode.rb +28 -0
  47. data/KGrift/test_data_generators/sandcastle_book_and_taxonomies.rb +13 -0
  48. data/KGrift/test_data_generators/sandcastle_book_and_taxonomies.yml +3709 -0
  49. data/KGrift/test_data_generators/sandcastle_graph.rb +8 -0
  50. data/KGrift/test_data_generators/sandcastle_graph_definition.json +4483 -0
  51. data/KGrift/test_data_generators/sandcastle_graph_full.rb +7 -0
  52. data/KGrift/test_data_generators/sandcastle_taxonomies.yml +378 -0
  53. data/KGrift/test_data_generators/sandcastle_with_taxons.rb +56 -0
  54. data/KGrift/test_data_generators/sandcastle_with_taxons.yml +3994 -0
  55. data/KGrift/test_data_generators/test_users_and_partners.rb +76 -0
  56. data/kgrift.gemspec +43 -0
  57. metadata +144 -0
@@ -0,0 +1,92 @@
1
+ require 'securerandom'
2
+ require 'time'
3
+ #magic date math methods come from active support
4
+ require 'active_support/core_ext/date/calculations'
5
+ require 'active_support/core_ext/numeric/time'
6
+ require 'digest'
7
+
8
+ module ::TestDataHelpers
9
+ def random_uuid
10
+ SecureRandom.uuid
11
+ end
12
+
13
+ def uuid_to_bit_string uuid
14
+ #remove dashes
15
+ #convert into hex, and back into a base 2 number/string
16
+ #pad the left with 0s
17
+ uuid.gsub('-','').to_i(16).to_s(2).rjust(128, '0')
18
+ end
19
+
20
+ def is_test_uuid? uuid
21
+ #the 41st bit is the test bit
22
+ uuid_to_bit_string(uuid)[40] == '1'
23
+ end
24
+
25
+ def random_string length=32
26
+ # to get the right length, for both both even and odd lengths,
27
+ # we have to pull a couple little tricks given that
28
+ # SecureRandom.hex returns twice the length requested
29
+ # SecureRandom.hex though is the best way I've found to get
30
+ # good random printable strings with a basic character set
31
+ s = SecureRandom.hex((length/2) + 1).to_s # add one so get a bit more than we need
32
+ s = s.force_encoding 'utf-8'
33
+ s[0...length] #chop the string to the length needed
34
+ end
35
+
36
+ # uses chinese characters
37
+ ChineseChars = [*"\u4E00".."\u9FFF"]
38
+ def random_chinese_string length=32
39
+ length.times.map{ ChineseChars.sample }.join('')
40
+ end
41
+
42
+ def timestamp_string
43
+ Time.now.strftime('%m%d%y%H%M%S')
44
+ end
45
+
46
+ def random_graph_name
47
+ "TestGraph_#{random_string}"
48
+ end
49
+
50
+ def random_taxonomy_name
51
+ "TestTaxonomy_#{random_string}"
52
+ end
53
+
54
+ def random_learning_instance_name
55
+ "TestLearningInstance_#{random_string}"
56
+ end
57
+
58
+ def datetime_now
59
+ #rfc-3339 format 2013-03-13T16:06:06-04:00
60
+ #which is same as iso8601 (except in the very limited case of 00 timezones)
61
+ #http://en.wikipedia.org/wiki/ISO_8601#Usage
62
+ Time.now.utc.round.iso8601(3)
63
+ end
64
+ alias :datetime :datetime_now
65
+
66
+ def days_from_now num_days
67
+ #for cleanliness days timestamps are all at midnight
68
+ num_days.to_i.days.from_now.utc.beginning_of_day.round.iso8601(3)
69
+ end
70
+
71
+ def bad_json_payloads
72
+ ["", "boomdeyada"]
73
+ end
74
+
75
+ def get_file_sha1 path
76
+ Digest::SHA1.hexdigest(File.read(path))
77
+ end
78
+
79
+ def get_mref_eid label
80
+ "mref-#{label}-#{random_string(20)}"
81
+ end
82
+
83
+ def get_gref_eid label
84
+ "gref-#{label}-#{random_string(20)}"
85
+ end
86
+
87
+ def get_tref_eid label
88
+ "tref-#{label}-#{random_string(20)}"
89
+ end
90
+
91
+ end
92
+ include ::TestDataHelpers
@@ -0,0 +1,56 @@
1
+
2
+
3
+ def csv_to_hash csv_filename
4
+ CSV.read(csv_filename, headers: true).map {|a| Hash[a] }
5
+ end
6
+
7
+ def csv_data_set filename, &blk
8
+ if not File.exist? filename
9
+ data = yield blk
10
+ columns = data.first.keys
11
+
12
+ csv = CSV.generate do |csv|
13
+ csv << columns
14
+ data.each do |row|
15
+ csv << row.values
16
+ end
17
+ end
18
+ File.open(filename, 'w') {|f| f.write csv}
19
+ end
20
+
21
+ csv_to_hash filename
22
+ end
23
+
24
+
25
+ # returns a flat hash that can be written right into a csv
26
+ def jmeter_partner
27
+
28
+ partner = create_partner_with_admin
29
+
30
+ flat_data = {
31
+ 'partner_id' => partner['partner_id'],
32
+ 'client_id' => partner['client_credentials']['client_id'],
33
+ 'client_secret' => partner['client_credentials']['client_secret'],
34
+ 'system_user_1' => partner['external_user_id'],
35
+ }
36
+
37
+ as_account :knerd do
38
+ set_partner_rate_limit_multiplier partner['partner_id'], 1000.0
39
+ end
40
+
41
+ flat_data['basic_auth_header'] = 'Basic ' + Base64.strict_encode64(
42
+ flat_data['client_id'] + ':' + flat_data['client_secret'])
43
+
44
+ tokens = get_access_token 'code' => nil,
45
+ 'redirect_uri' => nil,
46
+ 'scope' => flat_data['system_user_1'],
47
+ 'grant_type' => "client_credentials",
48
+ 'client_credentials' => partner['client_credentials'],
49
+ 'external_user_id' => flat_data['system_user_1']
50
+
51
+ # the access token isnt really useful, just the refresh token
52
+ # flat_data['access_token'] = tokens['access_token']
53
+ flat_data['refresh_token'] = tokens['refresh_token']
54
+
55
+ flat_data
56
+ end
@@ -0,0 +1,46 @@
1
+ def get_learning_instances params={}
2
+ Log.info "Getting all learning instances, with params '#{params.keys.join(', ')}'"
3
+ kapi.get '/v0/learning-instances?' + URI.encode_www_form(params)
4
+ end
5
+
6
+ def create_learning_instance overrides={}
7
+ Log.info "Creating a learning instance"
8
+ kapi.post '/v0/learning-instances', {
9
+ 'name' => random_learning_instance_name,
10
+ 'graph_id' => '123'
11
+ }.merge(overrides)
12
+ end
13
+
14
+ # Get a learning instance.
15
+ # If you also want to the partner_id of the learning instance,
16
+ # then send this for get_opts: additional_headers: { 'X-Include-Partner-Id' => 'true' }
17
+ # You can then access the partner_id with: kapi.last_response['X-Partner-Id']
18
+ def get_learning_instance id, get_opts={}
19
+ Log.info "Getting a learning instance: " + id
20
+ kapi.get "/v0/learning-instances/#{id}", get_opts
21
+ end
22
+
23
+ def update_learning_instance id, overrides
24
+ Log.info "Updating a learning instance: " + id
25
+ kapi.put "/v0/learning-instances/#{id}", {
26
+ #'name' => 'updated name',
27
+ }.merge(overrides)
28
+ end
29
+
30
+ def delete_learning_instance id
31
+ Log.info "Deleting a learning instance: " + id
32
+ kapi.delete "/v0/learning-instances/#{id}"
33
+ end
34
+
35
+ def get_learning_instance_registrations id, params={}
36
+ Log.info "Getting registrations for learning instance, with params '#{params.keys.join(', ')}'"
37
+ kapi.get "/v0/learning-instances/#{id}/registrations?" + URI.encode_www_form(params)
38
+ end
39
+
40
+ # get a group recommendation (vox)
41
+ def get_group_recommendation learning_instance_id, goal_id
42
+ Log.info "Getting a recommendation for learning instance: " + learning_instance_id + " goal: " + goal_id
43
+ query_params= { 'goal_id' => goal_id }
44
+ query_string = URI.encode_www_form(query_params)
45
+ kapi.get "/v0/learning-instances/#{learning_instance_id}/recommendation?#{query_string}"
46
+ end
@@ -0,0 +1,34 @@
1
+ # Bivins has some special looper only endpoints at /0/looper/_/
2
+ #
3
+ # These grifts can be used to test those endpoints
4
+
5
+
6
+ # looper_update_partner_display_name is the only supported way to
7
+ # update the display_name of a partner. PUT /v0/partners/:id does not work for display_name field
8
+ #
9
+ def looper_update_partner_display_name partner_id, new_display_name
10
+ kapi.put "/v0/looper/_/partners/#{partner_id}", {
11
+ 'display_name' => new_display_name,
12
+ }
13
+ end
14
+
15
+ def looper_get_graph_internal_id partner_id, graph_external_id
16
+ kapi.get "/v0/looper/_/graph_uuid_from_partner/#{partner_id}/label/#{URI.encode_www_form_component(graph_external_id)}"
17
+ end
18
+
19
+ def looper_get_graph_module_internal_id graph_id, module_external_id
20
+ kapi.get("/v0/looper/_/module_uuid_from_graph/#{graph_id}/label/#{URI.encode_www_form_component(module_external_id)}")
21
+ end
22
+
23
+ def looper_get_graph_learning_objective_internal_id graph_id, learning_objective_eid
24
+ kapi.get("/v0/looper/_/learning_objective_uuid_from_graph/#{graph_id}/label/#{URI.encode_www_form_component(learning_objective_eid)}")
25
+ end
26
+
27
+ def looper_get_graph_taxon_internal_id partner_id, taxon_eid, graph_id
28
+ kapi.get("/v0/looper/_/taxon_uuid_from_partner/#{partner_id}/label/#{URI.encode_www_form_component(taxon_eid)}/graph/#{graph_id}")
29
+ end
30
+
31
+ def looper_get_indexed_graph graph_id
32
+ kapi.get("/v0/looper/_/indexed_graphs/#{graph_id}")
33
+ end
34
+
@@ -0,0 +1,64 @@
1
+ ## Grifts to make requests against the Moxy endpoints for partner load testing
2
+ ## with randomized responses.
3
+
4
+ def moxy_send_ungraded_event registration_id, module_id, overrides={}
5
+ Log.info "Sending a moxy ungraded event"
6
+ kapi.post "/v0/moxy/registrations/#{registration_id}/ungraded-events", {
7
+ 'duration' => 876543,
8
+ 'module_id' => module_id,
9
+ 'is_complete' => true,
10
+ 'interaction_end_time' => datetime,
11
+ }.merge(overrides)
12
+ end
13
+
14
+ def moxy_send_graded_event registration_id, module_id, overrides={}
15
+ Log.info "Sending a moxy graded event"
16
+ kapi.post "/v0/moxy/registrations/#{registration_id}/graded-events", {
17
+ 'response' => "sample of a response",
18
+ 'duration' => 4321,
19
+ 'module_id' => module_id,
20
+ 'interaction_end_time' => datetime,
21
+ 'score' => 0.99,
22
+ 'is_correct' => true,
23
+ 'is_complete' => true,
24
+ }.merge(overrides)
25
+ end
26
+
27
+ def moxy_send_batch_events registration_id, events, overrides={}
28
+ Log.info "Sending a moxy batch of focus events"
29
+ kapi.post "/v0/moxy/registrations/#{registration_id}/batch-events", {
30
+ 'goal_id' => nil,
31
+ 'events' => events
32
+ }.merge(overrides)
33
+ end
34
+
35
+ def moxy_send_focus_event registration_id, goal_id, overrides={}
36
+ Log.info "Sending a moxy focus event"
37
+ kapi.post "/v0/moxy/registrations/#{registration_id}/focus-events", {
38
+ 'goal_id' => goal_id,
39
+ }.merge(overrides)
40
+ end
41
+
42
+ def moxy_get_recommendation registration_id, goal_id=nil, continued_recommendations=nil
43
+ goal_id = random_uuid unless goal_id #this only works in local mode, considering removing it
44
+ Log.info "Getting a moxy recommendation for registration: " + registration_id + " goal: " + goal_id
45
+ query_params= { 'goal_id' => goal_id }
46
+ query_params['continued_recommendations'] = continued_recommendations if continued_recommendations
47
+ query_string = URI.encode_www_form(query_params)
48
+ kapi.get "/v0/moxy/registrations/#{registration_id}/recommendation?#{query_string}"
49
+ end
50
+
51
+ def moxy_get_registration_metrics registration_id, metric_name, options
52
+ Log.info "Getting moxy analytics for registration: " + registration_id + " metric: " + metric_name
53
+ kapi.get "/v0/moxy/registrations/#{registration_id}/metrics/#{metric_name}" + '?' + options.join('&')
54
+ end
55
+
56
+ def moxy_get_learning_instance_metrics learning_instance_id, metric_name, request_body={}
57
+ Log.info "Getting moxy analytics for learning instance: " + learning_instance_id + " metric: " + metric_name
58
+ kapi.post "/v0/moxy/learning-instances/#{learning_instance_id}/metrics/#{metric_name}/rows", request_body
59
+ end
60
+
61
+ def moxy_get_learning_instance_group_metrics learning_instance_id, request_body={}
62
+ Log.info "Getting moxy groups analytics for learning instance: " + learning_instance_id
63
+ kapi.post "/v0/moxy/learning-instances/#{learning_instance_id}/metrics/groups", request_body
64
+ end
@@ -0,0 +1,182 @@
1
+ def knerd_token
2
+ #this is a handy helper for other apps to quickly get a bearer token
3
+ #run it like: grift -e qa -q -n knerd_token
4
+ set_account :knerd
5
+ kapi.headers['Authorization']
6
+ end
7
+
8
+ def authenticate profile
9
+ #if !profile.has_key?('username')
10
+ # profile.merge!('username'=>random_string)
11
+ #end
12
+ #if !profile.has_key?('password')
13
+ # profile.merge!('password'=>random_string)
14
+ #end
15
+
16
+ code = get_authorization_grant(profile)['code']
17
+ token = get_access_token(
18
+ 'code' => code,
19
+ 'client_credentials' => profile.select{|k| k=='client_id' or k=='client_secret'},
20
+ 'external_user_id' => profile['external_user_id'],
21
+ )['access_token']
22
+ set_authorization_headers token
23
+ end
24
+
25
+ def get_authorization_grant parameters={}
26
+ Log.info "Authenticating as user '#{parameters['username']}'"
27
+ parameters = {
28
+ 'response_type' => "code",
29
+ 'client_id' => nil,
30
+ 'redirect_uri' => "http://client-redirect-uri",
31
+ }.merge(parameters).delete_if{|k,v| v.nil?}
32
+ kapi.post_form "/v0/oauth/authorize", parameters
33
+ end
34
+
35
+ def get_access_token options
36
+ raise ArgumentError.new("options must be a Hash") unless options.is_a? Hash
37
+ auth_header = construct_basic_auth_header_overrides options.delete 'client_credentials'
38
+ Log.info "Getting an access token"
39
+ params = {
40
+ 'code' => options['code'],
41
+ 'grant_type' => "authorization_code",
42
+ 'redirect_uri' => "http://client-redirect-uri",
43
+ 'external_user_id' => random_string,
44
+ }.merge(options).delete_if{|k,v| v.nil?}
45
+ kapi.post_form '/v0/oauth/token', params, additional_headers: auth_header
46
+ end
47
+
48
+ def get_access_token_from_refresh_token options
49
+ raise ArgumentError.new("options must be a Hash") unless options.is_a? Hash
50
+ auth_header = construct_basic_auth_header_overrides options.delete 'client_credentials'
51
+ Log.info "Getting an access token from a refresh token"
52
+ params = {
53
+ 'refresh_token' => options['refresh_token'],
54
+ 'grant_type' => "refresh_token",
55
+ 'redirect_uri' => "http://client-redirect-uri",
56
+ 'external_user_id' => random_string,
57
+ }.merge(options)
58
+ kapi.post_form '/v0/oauth/token', params, additional_headers: auth_header
59
+ end
60
+
61
+ def unauthenticate
62
+ set_authorization_headers nil
63
+ end
64
+
65
+ def get_current_account
66
+ kapi.get '/v0/accounts/current'
67
+ end
68
+
69
+ def get_account_by_id account_id
70
+ kapi.get "/v0/accounts/#{account_id}", additional_headers: { 'X-Include-Partner-Id' => 'true' }
71
+ end
72
+
73
+ def get_account_by_username partner_id, username
74
+ kapi.get "/v0/accounts/?partner_id=#{partner_id}&username=#{username}"
75
+ end
76
+
77
+ def get_account_id_for external_user_id, client_credentials=nil
78
+ as_account external_user_id, client_credentials do
79
+ get_current_account['id']
80
+ end
81
+ end
82
+
83
+ def update_account account_id, overrides={}
84
+ kapi.put "/v0/accounts/#{account_id}", {}.merge(overrides)
85
+ end
86
+
87
+ def create_account overrides={}
88
+ kapi.post "/v0/accounts", {
89
+ 'external_user_id' => random_string,
90
+ 'entitlements' => []
91
+ }.merge(overrides)
92
+ end
93
+
94
+ def delete_account account_id
95
+ response = kapi.delete "/v0/accounts/#{account_id}"
96
+ # a deleted account shouldn't have access to the api
97
+ # so any tokens we have cached are no good
98
+ # throwing them away is important to ensure certain integration tests work correctly
99
+ delete_account_id_from_token_cache account_id
100
+ response
101
+ end
102
+
103
+ def create_account_with_username_password_for_partner username, password, partner_id, overrides={}
104
+ kapi.post "/v0/looper/_/accounts/", {
105
+ 'partner_id' => partner_id,
106
+ 'username' => username,
107
+ 'password' => password,
108
+ 'external_user_id' => random_string,
109
+ 'entitlements' => []
110
+ }.merge(overrides)
111
+ end
112
+
113
+ def make_account_partner_admin account_id
114
+ update_account account_id, 'entitlements' => ['all']
115
+ end
116
+
117
+ def update_looper_account_entitlements_by_username looper_partner_id, username, entitled_partner_ids
118
+ as_account :knerd do
119
+ account_response = get_account_by_username looper_partner_id, username
120
+ update_looper_account_entitlements account_response['id'], entitled_partner_ids
121
+ end
122
+ end
123
+
124
+ def update_looper_account_entitlements account_id, entitled_partner_ids
125
+ kapi.put "/v0/accounts/#{account_id}", {
126
+ 'id' => account_id,
127
+ 'entitlements' => ['partner_admin'],
128
+ 'entitlements_metadata' => { 'partner_admin' => entitled_partner_ids.map { |id| { 'partner_id' => id } } }
129
+ }
130
+ end
131
+
132
+
133
+ # This is a list of services that is not authenticated using knewton oauth
134
+ # graphtools is authenticated using google oauth2
135
+ # see graphtools_authenticate in graphtools_grifts
136
+ NON_AUTH_SERVICES=[:graphtools]
137
+
138
+ def set_authorization_headers token
139
+ if token
140
+ formatted_token = case token.to_sym
141
+ when /^\S+$/ #no spaces
142
+ 'Bearer ' + token
143
+ else
144
+ token
145
+ end
146
+ end
147
+ svcs = grifter_configuration[:services].keys
148
+ svcs.each do |svc|
149
+ # do not set the header for the special services authenticated in other ways
150
+ next if NON_AUTH_SERVICES.include? svc
151
+ if formatted_token
152
+ self.send(svc).headers['Authorization'] = formatted_token
153
+ else
154
+ self.send(svc).headers.delete 'Authorization'
155
+ end
156
+ end
157
+ formatted_token #return the actual token, I guess
158
+ end
159
+
160
+ #this helper is used for adding the basic http auth parameter where needed in a standardized way
161
+ #it can be given either client_credentials or client_id/client_secret separately
162
+ require 'base64'
163
+ def construct_basic_auth_header_overrides options={}
164
+ options={} unless options!=nil
165
+ #client credentials given
166
+ client_id=nil
167
+ client_secret=nil
168
+ if options.has_key? 'client_credentials'
169
+ client_id = options['client_credentials']['client_id']
170
+ client_secret = options['client_credentials']['client_secret']
171
+ #client id and secret given separately
172
+ elsif options.has_key? 'client_id' and options.has_key? 'client_secret'
173
+ client_id = options['client_id']
174
+ client_secret = options['client_secret']
175
+ #no sufficient information (use default partner)
176
+ else
177
+ client_id = get_client_id
178
+ client_secret = get_client_secret
179
+ end
180
+ encoded_token = Base64.strict_encode64(client_id + ':' + client_secret)
181
+ {'Authorization' => 'Basic ' + encoded_token}
182
+ end