spree_cm_commissioner 2.9.1.pre.pre4 → 2.9.2

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +9 -0
  3. data/Gemfile.lock +2 -2
  4. data/app/controllers/concerns/spree_cm_commissioner/content_cachable.rb +7 -0
  5. data/app/controllers/concerns/spree_cm_commissioner/load_test_account_management.rb +80 -0
  6. data/app/controllers/concerns/spree_cm_commissioner/load_test_account_scope.rb +46 -0
  7. data/app/controllers/concerns/spree_cm_commissioner/load_test_login_token_generation.rb +53 -0
  8. data/app/controllers/spree/admin/guests_controller.rb +4 -2
  9. data/app/controllers/spree/admin/system/load_test_accounts_controller.rb +24 -0
  10. data/app/controllers/spree/admin/system/load_test_flows_controller.rb +11 -0
  11. data/app/controllers/spree/admin/system/load_test_login_tokens_controller.rb +16 -0
  12. data/app/controllers/spree/admin/system/load_testing_controller.rb +9 -0
  13. data/app/controllers/spree/api/v2/storefront/draft_orders_controller.rb +48 -0
  14. data/app/controllers/spree/api/v2/storefront/preview_sections_controller.rb +1 -7
  15. data/app/controllers/spree/api/v2/storefront/telegram_oauth_tokens_controller.rb +28 -0
  16. data/app/controllers/spree/api/v2/tenant/draft_orders_controller.rb +49 -0
  17. data/app/controllers/spree/api/v2/tenant/preview_products_controller.rb +1 -1
  18. data/app/controllers/spree/api/v2/tenant/preview_sections_controller.rb +1 -7
  19. data/app/controllers/spree_cm_commissioner/admin/variants_controller_decorator.rb +11 -0
  20. data/app/controllers/spree_cm_commissioner/api/v2/storefront/checkout_controller_decorator.rb +0 -2
  21. data/app/interactors/spree_cm_commissioner/telegram_oauth_id_token_provider.rb +105 -0
  22. data/app/interactors/spree_cm_commissioner/telegram_oauth_token_exchanger.rb +71 -0
  23. data/app/interactors/spree_cm_commissioner/user_id_token_authenticator.rb +2 -5
  24. data/app/interactors/spree_cm_commissioner/user_password_authenticator.rb +23 -1
  25. data/app/interactors/spree_cm_commissioner/user_telegram_oauth_authenticator.rb +78 -0
  26. data/app/mailers/spree/order_mailer_decorator.rb +1 -0
  27. data/app/models/concerns/spree_cm_commissioner/kyc_bitwise.rb +17 -0
  28. data/app/models/concerns/spree_cm_commissioner/line_items_filter_scope.rb +5 -3
  29. data/app/models/concerns/spree_cm_commissioner/order_state_machine.rb +11 -0
  30. data/app/models/concerns/spree_cm_commissioner/product_delegation.rb +2 -0
  31. data/app/models/spree_cm_commissioner/homepage_section_relatable.rb +25 -0
  32. data/app/models/spree_cm_commissioner/order_decorator.rb +0 -6
  33. data/app/models/spree_cm_commissioner/payment_method_decorator.rb +2 -0
  34. data/app/models/spree_cm_commissioner/product_decorator.rb +1 -0
  35. data/app/models/spree_cm_commissioner/trip.rb +10 -1
  36. data/app/models/spree_cm_commissioner/user_decorator.rb +6 -0
  37. data/app/overrides/spree/admin/payment_methods/index/pagination.html.erb.deface +3 -0
  38. data/app/overrides/spree/admin/payment_methods/index/preserve_tab_in_search.html.erb.deface +4 -0
  39. data/app/overrides/spree/admin/products/_form/enable_social_sharing_email.html.erb.deface +13 -0
  40. data/app/overrides/spree/admin/variants/index/fixture_action.html.erb.deface +9 -0
  41. data/app/queries/spree_cm_commissioner/multi_leg_trips_query.rb +2 -0
  42. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_result_serializer.rb +2 -2
  43. data/app/serializers/spree_cm_commissioner/v2/storefront/trip_serializer.rb +3 -1
  44. data/app/services/spree_cm_commissioner/draft_order/create.rb +86 -0
  45. data/app/services/spree_cm_commissioner/load_test/create_accounts.rb +68 -0
  46. data/app/services/spree_cm_commissioner/load_test/destroy_accounts.rb +50 -0
  47. data/app/services/spree_cm_commissioner/load_test/flow_registry.rb +26 -0
  48. data/app/services/spree_cm_commissioner/load_test/generate_booking_ticket_data.rb +140 -0
  49. data/app/services/spree_cm_commissioner/load_test/generate_voting_fixture_data.rb +178 -0
  50. data/app/services/spree_cm_commissioner/load_test/login_token.rb +46 -0
  51. data/app/services/spree_cm_commissioner/order/guests/update.rb +56 -0
  52. data/app/services/spree_cm_commissioner/trips/update_single_leg.rb +6 -1
  53. data/app/services/spree_cm_commissioner/user_authenticator.rb +22 -1
  54. data/app/services/spree_cm_commissioner/voting_credits/allocate.rb +13 -2
  55. data/app/views/spree/admin/featured_trips/index.html.erb +1 -1
  56. data/app/views/spree/admin/guests/_form.html.erb +37 -0
  57. data/app/views/spree/admin/shared/_system_tabs.html.erb +7 -0
  58. data/app/views/spree/admin/system/load_test_accounts/_login_tokens_modal.html.erb +52 -0
  59. data/app/views/spree/admin/system/load_test_accounts/index.html.erb +175 -0
  60. data/app/views/spree/admin/system/load_test_flows/index.html.erb +66 -0
  61. data/app/views/spree/admin/system/load_testing/index.html.erb +25 -0
  62. data/app/views/spree/admin/variants/fixture.html.erb +49 -0
  63. data/config/initializers/doorkeeper.rb +10 -6
  64. data/config/initializers/spree_permitted_attributes.rb +30 -0
  65. data/config/load_test_flows/platform.yml +25 -0
  66. data/config/load_test_flows/tenant.yml +46 -0
  67. data/config/locales/en.yml +94 -0
  68. data/config/locales/km.yml +2 -0
  69. data/config/routes.rb +19 -0
  70. data/db/migrate/20260703065239_add_position_to_cm_voting_contestants.rb +1 -1
  71. data/db/migrate/20260708150000_add_name_and_short_name_to_cm_trips.rb +6 -0
  72. data/lib/spree_cm_commissioner/trip_result.rb +3 -1
  73. data/lib/spree_cm_commissioner/version.rb +1 -1
  74. metadata +37 -4
@@ -0,0 +1,52 @@
1
+ <%# Static, always on the page (hidden until the JS in manage_accounts.html.erb adds `.open`). The
2
+ Generate button opens this immediately, then fetches the tokens over AJAX and fills the
3
+ textarea — no page navigation. Fully self-contained: visuals come from the inline <style> here,
4
+ no dependency on Bootstrap's JS/CSS or any external bundle. %>
5
+ <div id="login_tokens_modal_backdrop" class="login-tokens-modal-backdrop"></div>
6
+ <div id="loginTokensModal" class="login-tokens-modal" role="dialog" aria-labelledby="loginTokensModalLabel">
7
+ <div class="login-tokens-modal-header">
8
+ <h5 class="mb-0" id="loginTokensModalLabel"><%= Spree.t('load_test.accounts.login_tokens_json') %></h5>
9
+ <button type="button" class="btn btn-sm btn-outline-secondary login-tokens-modal-close" data-dismiss="modal" aria-label="<%= Spree.t(:close) %>">
10
+ <span aria-hidden="true">&times;</span>
11
+ </button>
12
+ </div>
13
+ <div class="login-tokens-modal-body">
14
+ <div class="d-flex justify-content-between align-items-center mb-2 flex-shrink-0">
15
+ <label class="mb-0"><%= Spree.t('load_test.accounts.copy_into_k6') %></label>
16
+ <button type="button" class="btn btn-sm btn-outline-primary p-1" title="<%= Spree.t('load_test.accounts.copy_json') %>" aria-label="<%= Spree.t('load_test.accounts.copy_json') %>"
17
+ onclick="navigator.clipboard.writeText(document.getElementById('login_tokens_json').value); show_flash('success', <%= Spree.t('load_test.accounts.json_copied').to_json %>)">
18
+ <%= svg_icon name: 'clipboard', width: 16, height: 16, classes: 'm-0' %>
19
+ </button>
20
+ </div>
21
+ <div id="login_tokens_status" class="text-muted small mb-2 flex-shrink-0"></div>
22
+ <%= text_area_tag :login_tokens_json, nil,
23
+ readonly: true, class: 'form-control font-monospace login-tokens-modal-textarea' %>
24
+ </div>
25
+ </div>
26
+
27
+ <style>
28
+ .login-tokens-modal-backdrop {
29
+ position: fixed; inset: 0; background: rgba(0, 0, 0, 0.4);
30
+ opacity: 0; visibility: hidden; transition: opacity .2s ease; z-index: 1040;
31
+ }
32
+ .login-tokens-modal-backdrop.open { opacity: 1; visibility: visible; }
33
+ .login-tokens-modal {
34
+ position: fixed; top: 0; right: 0; height: 100%; width: 560px; max-width: 92vw;
35
+ background: #fff; box-shadow: -2px 0 12px rgba(0, 0, 0, .2);
36
+ transform: translateX(100%); transition: transform .25s ease; z-index: 1050;
37
+ display: flex; flex-direction: column;
38
+ }
39
+ .login-tokens-modal.open { transform: translateX(0); }
40
+ .login-tokens-modal-header {
41
+ display: flex; align-items: center; justify-content: space-between;
42
+ padding: 1rem 1.25rem; border-bottom: 1px solid #e9ecef; flex-shrink: 0;
43
+ }
44
+ .login-tokens-modal-close {
45
+ line-height: 1; font-size: 1.25rem; padding: 0 .5rem;
46
+ }
47
+ .login-tokens-modal-body {
48
+ padding: 1.25rem; overflow: hidden; flex: 1; min-height: 0;
49
+ display: flex; flex-direction: column;
50
+ }
51
+ .login-tokens-modal-textarea { flex: 1; min-height: 0; resize: none; }
52
+ </style>
@@ -0,0 +1,175 @@
1
+ <%= render 'spree/admin/shared/system_tabs', current: :load_testing %>
2
+
3
+ <%= link_to Spree.t('load_test.back_to_load_testing'), admin_system_load_testing_index_path, class: 'btn btn-outline-secondary btn-sm mb-3' %>
4
+
5
+ <div class="row">
6
+ <div class="col-md-12">
7
+ <div class="card mb-3">
8
+ <div class="card-header"><h5 class="mb-0"><%= Spree.t('load_test.accounts.manage_accounts_platform') %></h5></div>
9
+ <div class="card-body">
10
+ <p class="text-muted">
11
+ <%= Spree.t('load_test.accounts.manage_accounts_body_html').html_safe %>
12
+ </p>
13
+ <%= form_tag admin_system_load_test_accounts_path, method: :post do %>
14
+ <div class="row">
15
+ <div class="col-md-3 form-group">
16
+ <label for="count"><%= Spree.t('load_test.accounts.count') %></label>
17
+ <%= number_field_tag :count, params[:count] || 20, class: 'form-control', min: 1 %>
18
+ </div>
19
+ <div class="col-md-4 form-group">
20
+ <label for="email_prefix"><%= Spree.t('load_test.accounts.email_prefix') %></label>
21
+ <%= text_field_tag :email_prefix, params[:email_prefix] || 'loadtest', class: 'form-control' %>
22
+ </div>
23
+ <div class="col-md-5 form-group">
24
+ <label for="email_domain"><%= Spree.t('load_test.accounts.email_domain') %></label>
25
+ <%= text_field_tag :email_domain, params[:email_domain], class: 'form-control', placeholder: Spree.t('load_test.accounts.email_domain_placeholder') %>
26
+ </div>
27
+ </div>
28
+ <%= button_tag Spree.t('load_test.accounts.create_accounts'), type: :submit, class: 'btn btn-primary' %>
29
+ <%= button_tag Spree.t('load_test.accounts.destroy_accounts'), type: :submit,
30
+ formaction: bulk_destroy_admin_system_load_test_accounts_path,
31
+ data: { confirm: Spree.t('load_test.accounts.destroy_accounts_confirm') },
32
+ class: 'btn btn-outline-danger' %>
33
+ <% end %>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="card mb-3">
38
+ <div class="card-header"><h5 class="mb-0"><%= Spree.t('load_test.accounts.generate_login_tokens_platform') %></h5></div>
39
+ <div class="card-body">
40
+ <p class="text-muted">
41
+ <%= Spree.t('load_test.accounts.generate_login_tokens_body_html', ttl: SpreeCmCommissioner::LoadTest::LoginToken::TTL.inspect).html_safe %>
42
+ </p>
43
+
44
+ <div class="d-flex justify-content-between align-items-start flex-wrap">
45
+ <%# Drives the table's own pagination below — a plain GET reload, separate from the AJAX
46
+ Generate Login Tokens form. Changing Per Page resets back to page 1. A typed number
47
+ input rather than Spree's usual 25/50/75 dropdown, since the load-test pool can run
48
+ well past those fixed sizes. %>
49
+ <%= form_tag admin_system_load_test_accounts_path, method: :get, class: 'form-inline mb-3' do %>
50
+ <label for="per_page" class="mr-2"><%= Spree.t('load_test.accounts.per_page') %></label>
51
+ <%= number_field_tag :per_page, @export_accounts.limit_value, class: 'form-control mr-2 mb-2', style: 'width: 100px', min: 1 %>
52
+ <%= button_tag Spree.t('load_test.accounts.apply'), type: :submit, name: nil, class: 'btn btn-outline-secondary mb-2' %>
53
+ <% end %>
54
+
55
+ <%# JS below intercepts submit: opens the drawer immediately, then fetches the tokens as
56
+ JSON into it — no navigation. data-turbo:false keeps Turbo out of the form entirely.
57
+ Hidden page/per_page mirror the table's own pagination below, so the generated tokens
58
+ always match what's on screen — page the table to change which accounts get tokens. %>
59
+ <%= form_tag admin_system_load_test_login_tokens_path,
60
+ method: :post, id: 'login_tokens_form', class: 'form-inline mb-3', data: { turbo: false } do %>
61
+ <%= hidden_field_tag :page, @export_accounts.current_page %>
62
+ <%= hidden_field_tag :per_page, @export_accounts.limit_value %>
63
+ <%= button_tag Spree.t('load_test.accounts.generate_login_tokens'), type: :submit, class: 'btn btn-primary mb-2' %>
64
+ <% end %>
65
+ </div>
66
+
67
+ <% if @export_accounts.any? %>
68
+ <div class="table-responsive">
69
+ <table class="table mb-0">
70
+ <thead class="text-muted">
71
+ <tr>
72
+ <th><%= Spree.t('load_test.accounts.table_no') %></th>
73
+ <th><%= Spree.t('load_test.accounts.table_email') %></th>
74
+ <th><%= Spree.t('load_test.accounts.table_user_id') %></th>
75
+ <th><%= Spree.t('load_test.accounts.table_created_at') %></th>
76
+ <th><%= Spree.t('load_test.accounts.table_last_sign_in_at') %></th>
77
+ <th><%= Spree.t('load_test.accounts.table_current_sign_in_at') %></th>
78
+ </tr>
79
+ </thead>
80
+ <tbody>
81
+ <% @export_accounts.each_with_index do |account, index| %>
82
+ <tr>
83
+ <td><%= @export_accounts.offset_value + index + 1 %></td>
84
+ <td><%= account.email %></td>
85
+ <td><code><%= account.id %></code></td>
86
+ <td><%= account.created_at&.strftime('%Y-%m-%d %H:%M') %></td>
87
+ <td><%= account.last_sign_in_at&.strftime('%Y-%m-%d %H:%M') || '—' %></td>
88
+ <td><%= account.current_sign_in_at&.strftime('%Y-%m-%d %H:%M') || '—' %></td>
89
+ </tr>
90
+ <% end %>
91
+ </tbody>
92
+ </table>
93
+ </div>
94
+
95
+ <%= paginate @export_accounts, theme: 'admin-twitter-bootstrap-4' %>
96
+ <% else %>
97
+ <div class="m-5 text-center no-objects-found">
98
+ <%= Spree.t('load_test.accounts.no_accounts_found') %>
99
+ </div>
100
+ <% end %>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+
107
+ <%# Always on the page (outside the .row/.col so its position:fixed is viewport-relative), hidden
108
+ until the JS below opens it. Populated over AJAX — see the fetch handler. %>
109
+ <%= render "login_tokens_modal" %>
110
+
111
+ <script>
112
+ (function() {
113
+ var i18n = {
114
+ generating: <%= Spree.t('load_test.accounts.generating').to_json.html_safe %>,
115
+ errorPrefix: <%= Spree.t('load_test.accounts.error_prefix').to_json.html_safe %>,
116
+ requestFailedTemplate: <%= Spree.t('load_test.accounts.request_failed', status: '%{status}').to_json.html_safe %>
117
+ };
118
+
119
+ var form = document.getElementById('login_tokens_form');
120
+ var drawer = document.getElementById('loginTokensModal');
121
+ var backdrop = document.getElementById('login_tokens_modal_backdrop');
122
+ var textarea = document.getElementById('login_tokens_json');
123
+ var status = document.getElementById('login_tokens_status');
124
+ if (!form || !drawer || !backdrop) return;
125
+
126
+ function openDrawer() {
127
+ drawer.classList.add('open');
128
+ backdrop.classList.add('open');
129
+ document.body.style.overflow = 'hidden';
130
+ }
131
+ function closeDrawer() {
132
+ drawer.classList.remove('open');
133
+ backdrop.classList.remove('open');
134
+ document.body.style.removeProperty('overflow');
135
+ }
136
+
137
+ backdrop.addEventListener('click', closeDrawer);
138
+ drawer.querySelectorAll('[data-dismiss="modal"]').forEach(function(btn) {
139
+ btn.addEventListener('click', closeDrawer);
140
+ });
141
+
142
+ form.addEventListener('submit', function(event) {
143
+ event.preventDefault();
144
+
145
+ // Open first, show a loading state — the drawer never waits on the request to appear.
146
+ openDrawer();
147
+ textarea.value = '';
148
+ status.textContent = i18n.generating;
149
+
150
+ var csrf = document.querySelector('meta[name="csrf-token"]');
151
+ fetch(form.action, {
152
+ method: 'POST',
153
+ credentials: 'same-origin',
154
+ headers: {
155
+ 'Accept': 'application/json',
156
+ 'X-CSRF-Token': csrf ? csrf.content : ''
157
+ },
158
+ body: new FormData(form)
159
+ })
160
+ .then(function(response) {
161
+ return response.json().then(function(data) {
162
+ if (!response.ok) throw new Error(data.error || i18n.requestFailedTemplate.replace('%{status}', response.status));
163
+ return data;
164
+ });
165
+ })
166
+ .then(function(data) {
167
+ textarea.value = JSON.stringify(data, null, 2);
168
+ status.textContent = '';
169
+ })
170
+ .catch(function(error) {
171
+ status.textContent = i18n.errorPrefix + error.message;
172
+ });
173
+ });
174
+ })();
175
+ </script>
@@ -0,0 +1,66 @@
1
+ <%= render 'spree/admin/shared/system_tabs', current: :load_testing %>
2
+
3
+ <%= link_to Spree.t('load_test.back_to_load_testing'), admin_system_load_testing_index_path, class: 'btn btn-outline-secondary btn-sm mb-3' %>
4
+
5
+ <p class="text-muted">
6
+ <%= Spree.t('load_test.flows.description_html').html_safe %>
7
+ </p>
8
+
9
+ <% @load_test_flows.each_with_index do |(flow_name, flow), index| %>
10
+ <details class="card mb-3" <%= 'open' if index.zero? %>>
11
+ <summary class="card-header d-flex justify-content-between align-items-center" style="cursor: pointer; list-style: none;">
12
+ <div>
13
+ <h6 class="mb-1"><%= flow_name.to_s.humanize %></h6>
14
+ <span class="text-muted small"><%= flow[:description] %></span>
15
+ </div>
16
+ <%= svg_icon name: 'chevron-down', width: 16, height: 16 %>
17
+ </summary>
18
+
19
+ <div class="card-body">
20
+ <div class="mb-3">
21
+ <div class="text-muted text-uppercase small mb-1" style="letter-spacing: .04em;"><%= Spree.t('load_test.flows.save_to') %></div>
22
+ <code><%= flow[:destination] %></code>
23
+ </div>
24
+
25
+ <% if flow[:data_button] %>
26
+ <div class="alert alert-primary d-flex mb-3" role="alert">
27
+ <%= svg_icon name: 'cursor-fill', width: 18, height: 18, classes: 'mr-2 flex-shrink-0' %>
28
+ <div>
29
+ <strong><%= Spree.t('load_test.flows.data_button_label', label: flow[:data_button][:label]) %></strong> — <%= flow[:data_button][:where] %>
30
+ <% if flow[:data_button][:note] %>
31
+ <div class="text-muted small mt-1"><%= flow[:data_button][:note] %></div>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+
37
+ <% if flow[:source] %>
38
+ <div class="mb-3">
39
+ <div class="text-muted text-uppercase small mb-2" style="letter-spacing: .04em;"><%= Spree.t('load_test.flows.look_up_manually') %></div>
40
+ <div class="table-responsive">
41
+ <table class="table table-sm mb-0">
42
+ <thead class="text-muted">
43
+ <tr>
44
+ <th><%= Spree.t('load_test.flows.field') %></th>
45
+ <th><%= Spree.t('load_test.flows.where_to_find_it') %></th>
46
+ </tr>
47
+ </thead>
48
+ <tbody>
49
+ <% flow[:source].each do |field, instructions| %>
50
+ <tr>
51
+ <td class="align-top text-nowrap"><code><%= field %></code></td>
52
+ <td class="text-muted"><%= instructions %></td>
53
+ </tr>
54
+ <% end %>
55
+ </tbody>
56
+ </table>
57
+ </div>
58
+ </div>
59
+ <% end %>
60
+
61
+ <% if flow[:doc] %>
62
+ <p class="text-muted small mb-0"><%= Spree.t('load_test.flows.full_request_table_html', doc: flow[:doc]).html_safe %></p>
63
+ <% end %>
64
+ </div>
65
+ </details>
66
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <%= render 'spree/admin/shared/system_tabs', current: :load_testing %>
2
+
3
+ <div class="row">
4
+ <div class="col-md-12">
5
+ <div class="list-group">
6
+ <%= link_to admin_system_load_test_accounts_path,
7
+ class: 'list-group-item list-group-item-action d-flex justify-content-between align-items-center' do %>
8
+ <div>
9
+ <strong><%= Spree.t('load_test.index.manage_accounts') %></strong>
10
+ <div class="text-muted small"><%= Spree.t('load_test.index.manage_accounts_description') %></div>
11
+ </div>
12
+ <%= svg_icon name: 'chevron-right', width: 20, height: 20 %>
13
+ <% end %>
14
+
15
+ <%= link_to admin_system_load_test_flows_path,
16
+ class: 'list-group-item list-group-item-action d-flex justify-content-between align-items-center' do %>
17
+ <div>
18
+ <strong><%= Spree.t('load_test.index.test_data_sources') %></strong>
19
+ <div class="text-muted small"><%= Spree.t('load_test.index.test_data_sources_description') %></div>
20
+ </div>
21
+ <%= svg_icon name: 'chevron-right', width: 20, height: 20 %>
22
+ <% end %>
23
+ </div>
24
+ </div>
25
+ </div>
@@ -0,0 +1,49 @@
1
+ <%= render partial: 'spree/admin/shared/product_tabs', locals: { current: :variants } %>
2
+
3
+ <% content_for :page_title do %>
4
+ <%= Spree.t(:fixture, default: 'Fixture') %> &mdash; <%= @variant.options_text.presence || @variant.sku %>
5
+ <% end %>
6
+
7
+ <% filename = "#{@fixture_env}.json" %>
8
+
9
+ <div class="card mb-4">
10
+ <div class="card-body">
11
+ <% if @fixture_issues.blank? %>
12
+ <div class="alert alert-success" role="alert">
13
+ <strong>Ready for load testing.</strong> This variant is purchasable and sits under an event, so the
14
+ booking flow can run against it end-to-end.
15
+ </div>
16
+ <% else %>
17
+ <div class="alert alert-warning" role="alert">
18
+ <strong>Not ready for load testing.</strong> The booking flow will not complete against this variant until
19
+ the following is fixed:
20
+ <ul class="mb-0 mt-2">
21
+ <% @fixture_issues.each do |issue| %>
22
+ <li><%= issue %></li>
23
+ <% end %>
24
+ </ul>
25
+ </div>
26
+ <% end %>
27
+
28
+ <p class="text-muted">
29
+ Save this as <code><%= filename %></code> in
30
+ <code>load_testing/config/fixture/platform/booking_ticket_flow/</code>
31
+ (git-ignored).
32
+ </p>
33
+
34
+ <div class="position-relative" data-controller="clipboard">
35
+ <div class="position-absolute d-flex" style="top: .5rem; right: .5rem;">
36
+ <button type="button" class="btn btn-sm btn-primary"
37
+ data-action="clipboard#copy"
38
+ onclick="show_flash('success', '<%= j Spree.t(:copied_to_clipboard, default: 'Copied to clipboard') %>')">
39
+ <%= Spree.t(:copy, default: 'Copy') %>
40
+ </button>
41
+ </div>
42
+ <textarea readonly data-clipboard-target="source" rows="12"
43
+ class="form-control text-monospace bg-light"
44
+ style="resize: vertical;"><%= JSON.pretty_generate(@fixture) %></textarea>
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <%= link_to Spree.t(:back), spree.admin_product_variants_path(@product), class: 'btn btn-outline-secondary' %>
@@ -41,13 +41,17 @@ Doorkeeper.configure do
41
41
  # custom configuration https://github.com/doorkeeper-gem/doorkeeper/wiki/Customizing-Token-Expiration
42
42
  custom_access_token_expires_in do |context|
43
43
  # context.grant_type for grant_type, context.client for client, context.scopes for scopes
44
- case context.grant_type
45
- when Doorkeeper::OAuth::CLIENT_CREDENTIALS # see Doorkeeper::OAuth::GRANT_TYPES for other types
46
- 7.days.to_i
47
- when Doorkeeper::OAuth::PASSWORD
48
- 1.year.to_i
49
- else
44
+ if context.resource_owner.respond_to?(:load_test_account?) && context.resource_owner.load_test_account?
50
45
  2.hours.to_i
46
+ else
47
+ case context.grant_type
48
+ when Doorkeeper::OAuth::CLIENT_CREDENTIALS # see Doorkeeper::OAuth::GRANT_TYPES for other types
49
+ 7.days.to_i
50
+ when Doorkeeper::OAuth::PASSWORD
51
+ 1.year.to_i
52
+ else
53
+ 2.hours.to_i
54
+ end
51
55
  end
52
56
  end
53
57
  end
@@ -10,6 +10,7 @@ module Spree
10
10
  # store_accessor key (public_metadata), not a DB column, so it must be permitted explicitly
11
11
  @@product_attributes << :enable_telegram_alert unless @@product_attributes.include?(:enable_telegram_alert)
12
12
  @@product_attributes << :enable_send_receipt unless @@product_attributes.include?(:enable_send_receipt)
13
+ @@product_attributes << :enable_social_sharing_email unless @@product_attributes.include?(:enable_social_sharing_email)
13
14
 
14
15
  # Permitted all guest attributes for now as permitting only some guest attributes is not working by design
15
16
  # of spree add_item service, if we want to only permit some guest attributes,
@@ -92,6 +93,35 @@ module Spree
92
93
  age_group
93
94
  nationality_group
94
95
  ]
96
+ },
97
+ {
98
+ line_items_attributes: [
99
+ :id,
100
+ {
101
+ guests_attributes: [
102
+ :id,
103
+ :first_name,
104
+ :last_name,
105
+ :address,
106
+ :dob,
107
+ :gender,
108
+ :occupation_id,
109
+ :other_occupation,
110
+ :nationality_id,
111
+ :age,
112
+ :emergency_contact,
113
+ :phone_number,
114
+ :other_organization,
115
+ :social_contact,
116
+ :social_contact_platform,
117
+ :expectation,
118
+ :country_code,
119
+ :upload_later,
120
+ :contact,
121
+ { guest_dynamic_fields_attributes: %i[id dynamic_field_id dynamic_field_option_id value _destroy] }
122
+ ]
123
+ }
124
+ ]
95
125
  }
96
126
  ]
97
127
 
@@ -0,0 +1,25 @@
1
+ # Registry of k6 load-test flows (../../../load_testing) for the `platform` workflow.
2
+ # See load_test_flows/tenant.yml for the tenant-scoped counterpart and the shared field docs.
3
+ #
4
+ # Fields:
5
+ # workflow: platform | tenant — matches load_testing/{endpoints,tests}/<workflow>/
6
+ # description: one-line summary of what the flow exercises
7
+ # destination: where the JSON goes, relative to load_testing/ (git-ignored, <env>.json)
8
+ # example: the matching example.json/example fixture, for the expected shape
9
+ # data_button: present when an admin/organizer page can generate the JSON directly
10
+ # (Fixture-style copy/paste button) — label + where to click
11
+ # source: present when there's no generator yet — plain instructions for where in
12
+ # admin to look up each field by hand
13
+ # doc: path to the flow's own doc.md for the full request table
14
+
15
+ booking_ticket_flow:
16
+ description: Browse → event → product → cart → checkout → cash payment → booking.
17
+ destination: config/fixture/platform/booking_ticket_flow/<env>.json
18
+ example: config/fixture/platform/booking_ticket_flow/example.json
19
+ data_button:
20
+ label: Fixture
21
+ where: Admin → pick the ticket product → Variants → the variant's row → "Fixture" button
22
+ note: >-
23
+ Runs a purchasable/under-event readiness check and resolves event_permalink,
24
+ product_slug, product_id, variant_id for you — click Copy and paste the whole thing in.
25
+ doc: tests/platform/booking_ticket_flow/doc.md
@@ -0,0 +1,46 @@
1
+ # Registry of k6 load-test flows (../../../load_testing) for the `tenant` workflow.
2
+ # See load_test_flows/platform.yml for the platform-scoped counterpart and the shared field docs.
3
+ #
4
+ # Not every tenant supports every flow (e.g. a tenant with no ABA payment method has no
5
+ # aba_topup_vote_flow) — the fixture generators flag an issue at request time when the
6
+ # current tenant is missing what a flow needs, rather than this file trying to enumerate it.
7
+
8
+ voting_flow:
9
+ description: Homepage → show detail (contestants + judges) → cast vote → refresh.
10
+ destination: config/fixture/tenant/voting_flow/<env>.json
11
+ example: config/fixture/tenant/voting_flow/example.json
12
+ data_button:
13
+ label: Fixture
14
+ where: >-
15
+ Organizer → Shows → Season → Episode → Voting Sessions table → row "⋮" menu →
16
+ Fixture → pick "Voting flow"
17
+ note: Resolves show_slug and voting_session_id straight off the voting session you picked.
18
+ doc: tests/tenant/voting_flow/doc.md
19
+
20
+ aba_topup_vote_flow:
21
+ description: Top-up Vote screen paid via ABA (PayWay v2 sandbox).
22
+ destination: config/fixture/tenant/aba_topup_vote_flow/<env>.json
23
+ example: config/fixture/tenant/aba_topup_vote_flow/example.json
24
+ data_button:
25
+ label: Fixture
26
+ where: >-
27
+ Organizer → Shows → Season → Episode → Voting Sessions table → row "⋮" menu →
28
+ Fixture → pick "Aba topup vote flow"
29
+ note: >-
30
+ Resolves show_slug, voting_session_id, payment_method_id, and the payway config off
31
+ the tenant's active ABA/PayWay payment method — flags an issue if none is configured yet.
32
+ doc: tests/tenant/aba_topup_vote_flow/doc.md
33
+
34
+ cash_on_topup_vote_flow:
35
+ description: Top-up Vote screen paid via a Cash On payment method (no external gateway).
36
+ destination: config/fixture/tenant/cash_on_topup_vote_flow/<env>.json
37
+ example: config/fixture/tenant/cash_on_topup_vote_flow/example.json
38
+ data_button:
39
+ label: Fixture
40
+ where: >-
41
+ Organizer → Shows → Season → Episode → Voting Sessions table → row "⋮" menu →
42
+ Fixture → pick "Cash on topup vote flow"
43
+ note: >-
44
+ Resolves show_slug, voting_session_id, and payment_method_id off the tenant's active
45
+ Cash On payment method — flags an issue if none is configured yet.
46
+ doc: tests/tenant/cash_on_topup_vote_flow/doc.md
@@ -40,6 +40,34 @@ en:
40
40
  invalid_client_credentials: "Invalid client credentials"
41
41
  success_disabled_2fa: "Successfully disabled 2FA"
42
42
 
43
+ load_test:
44
+ generate_voting_fixture_data:
45
+ unknown_flow: "Unknown flow: %{flow}. Must be one of %{flows}"
46
+ no_show: "Voting session has no show — show_slug will be blank."
47
+ no_payment_method: "No active %{label} payment method found for this tenant — set one up first (Shared Console > Payment Methods)."
48
+ sandbox_host_mismatch: >
49
+ Payment method's host (%{host}) doesn't look like ABA's sandbox — PayWay v2 has no
50
+ sandbox/production flag to check programmatically, so double check by eye before running
51
+ this against anything but a throwaway account.
52
+ session_status_invalid: 'Voting session status is %{status}, but casting a vote requires status "enabled".'
53
+ manual_advance_never_votes: 'Voting session type is "manual_advance", which never accepts direct votes.'
54
+ voting_not_opened: "Voting hasn't opened yet — it opens %{opens_at}."
55
+ voting_already_closed: "Voting has already closed — it closed %{closes_at}."
56
+ no_contestants: >
57
+ Voting session has no contestants assigned yet — the flow picks a random contestant ID
58
+ from GET voting_contestants and has nothing to pick from.
59
+ no_eligible_credit: >
60
+ No active VotingCredit rows exist for this session (or its episode/show) at all — the
61
+ load-test accounts casting votes will each still need their own credit (VoteCreditDeductor
62
+ rejects a vote with no eligible, unused credit).
63
+ no_vote_package: >
64
+ No active, purchasable paid vote package found for this session (or its episode/show) —
65
+ the topup flow adds one to cart and has nothing to add. Create one under this show/season
66
+ (status=active, priced above 0, in stock or backorderable).
67
+ payment_method_labels:
68
+ aba: "ABA (PayWay)"
69
+ cash_on: "Cash On"
70
+
43
71
  hello: "Hello world"
44
72
 
45
73
  recaptcha:
@@ -227,6 +255,7 @@ en:
227
255
  description: "Description"
228
256
  enable_telegram_alert: "Enable Telegram Alert"
229
257
  enable_send_receipt: "Enable Send Receipt"
258
+ enable_social_sharing_email: "Enable Social Sharing Email"
230
259
  registered_by:
231
260
  system_registered: "System Registered"
232
261
  self_registered: "Self Registered"
@@ -284,6 +313,11 @@ en:
284
313
  invalid_session_token: "Invalid session token"
285
314
  intel_phone_number: "Intel Phone Number"
286
315
  phone_number: "Phone Number"
316
+ contact: "Contact"
317
+ organization: "Organization"
318
+ expectation: "Expectation"
319
+ social_contact: "Social Contact"
320
+ social_contact_platform: "Social Contact Platform"
287
321
  new_customer: "New Customer"
288
322
  listing_price: "Listing Price"
289
323
  length: "Length"
@@ -509,6 +543,64 @@ en:
509
543
  already_checked_in: "Guest has already checked in for this session"
510
544
  check_in_session_required: "Check-in session required"
511
545
  check_in_session_is_not_allowed_or_invalid: "Check-in session is not allowed or invalid for this guest/ticket"
546
+ load_test:
547
+ nav_label: "Load Testing"
548
+ back_to_load_testing: "Back to Load Testing"
549
+ index:
550
+ manage_accounts: "Manage Accounts"
551
+ manage_accounts_description: "Create/destroy load-test accounts and mint their login tokens."
552
+ test_data_sources: "Test Data Sources"
553
+ test_data_sources_description: "Where to find/generate the JSON each k6 flow needs."
554
+ accounts:
555
+ manage_accounts_platform: "Manage Accounts — Platform"
556
+ manage_accounts_body_html: >
557
+ Ensures N load-test accounts exist (idempotent — existing accounts are skipped), or
558
+ soft-deletes them after production testing (not needed on staging — accounts are
559
+ reusable across runs). Accounts can never log in with a real password; each authenticates
560
+ with a stateless, signed JWT instead. Mint those below with
561
+ <strong>Generate Login Tokens</strong> — k6 sends the JWT as <code>user_id_token</code>
562
+ when it logs in, no separate token-minting step needed.
563
+ count: "Count"
564
+ email_prefix: "Email Prefix"
565
+ email_domain: "Email Domain (optional)"
566
+ email_domain_placeholder: "<tenant-slug>.test or bookmeplus.test"
567
+ create_accounts: "Create Accounts"
568
+ destroy_accounts: "Destroy Accounts"
569
+ destroy_accounts_confirm: "Destroy this account pool?"
570
+ generate_login_tokens_platform: "Generate Login Tokens — Platform"
571
+ generate_login_tokens_body_html: >
572
+ Mints tokens for exactly the accounts listed in the table below — set Per Page and page
573
+ through it first, then generate. Each token is a stateless, signed JWT
574
+ (<code>sub</code> = the account, <code>aud</code> = you, plus an expiry) valid for
575
+ %{ttl} — nothing is stored.
576
+ per_page: "Per Page"
577
+ apply: "Apply"
578
+ generate_login_tokens: "Generate Login Tokens"
579
+ table_no: "No."
580
+ table_email: "Email"
581
+ table_user_id: "User ID"
582
+ table_created_at: "Created At"
583
+ table_last_sign_in_at: "Last Sign In At"
584
+ table_current_sign_in_at: "Current Sign In At"
585
+ no_accounts_found: "No load-test accounts found."
586
+ login_tokens_json: "Login Tokens JSON"
587
+ copy_into_k6: "Copy into k6 (Platform)"
588
+ copy_json: "Copy JSON"
589
+ json_copied: "JSON copied"
590
+ generating: "Generating…"
591
+ error_prefix: "Error: "
592
+ request_failed: "Request failed (%{status})"
593
+ flows:
594
+ description_html: >
595
+ Each k6 flow reads its data from a git-ignored JSON file under <code>load_testing/config/</code>.
596
+ Expand a flow below for where to find its real values — see
597
+ <code>gems/spree_cm_commissioner/config/load_test_flows/{platform,tenant}.yml</code> to add or edit flows.
598
+ save_to: "Save to"
599
+ data_button_label: '"%{label}" button'
600
+ look_up_manually: "Look up manually"
601
+ field: "Field"
602
+ where_to_find_it: "Where to find it"
603
+ full_request_table_html: "Full request table: <code>load_testing/%{doc}</code>"
512
604
 
513
605
  activerecord:
514
606
  attributes:
@@ -823,6 +915,8 @@ en:
823
915
  verification_failed: "Human verification failed"
824
916
  service_unavailable: "Verification service is temporarily unavailable"
825
917
  spree_cm_commissioner:
918
+ draft_order:
919
+ line_items_missing: "Line items are missing"
826
920
  voting_contestants:
827
921
  assigner:
828
922
  not_eliminated: "Only eliminated contestants can be added to a Save Contestants session. %{name} is %{status}."
@@ -666,6 +666,8 @@ km:
666
666
  service_unavailable: "សេវាកម្មពេលនេះមិនមានការផ្ទៀងផ្ទាត់"
667
667
 
668
668
  spree_cm_commissioner:
669
+ draft_order:
670
+ line_items_missing: "គ្មានទំនិញនៅក្នុងកន្ត្រកទេ"
669
671
  voting_contestants:
670
672
  assigner:
671
673
  not_eliminated: "តែអ្នកចូលប្រកួតដែលត្រូវបានលុបចោលប៉ុណ្ណោះអាចបន្ថែមទៅ Save Contestants session បាន។ %{name} មានស្ថានភាព %{status}។"