practical 0.1.0
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.
- checksums.yaml +7 -0
- data/README.md +37 -0
- data/Rakefile +10 -0
- data/app/components/practical/views/base_component.rb +6 -0
- data/app/components/practical/views/button_component.rb +27 -0
- data/app/components/practical/views/datatable/filter_applied.rb +25 -0
- data/app/components/practical/views/datatable/filter_section_component.html.erb +9 -0
- data/app/components/practical/views/datatable/filter_section_component.rb +19 -0
- data/app/components/practical/views/datatable/sort_link_component.rb +48 -0
- data/app/components/practical/views/datatable.rb +36 -0
- data/app/components/practical/views/flash_messages_component.rb +65 -0
- data/app/components/practical/views/form/error_list_component.rb +15 -0
- data/app/components/practical/views/form/error_list_item_component.rb +20 -0
- data/app/components/practical/views/form/error_list_item_template_component.rb +9 -0
- data/app/components/practical/views/form/fallback_errors_section_component.html.erb +7 -0
- data/app/components/practical/views/form/fallback_errors_section_component.rb +21 -0
- data/app/components/practical/views/form/field_errors_component.rb +28 -0
- data/app/components/practical/views/form/field_title_component.rb +23 -0
- data/app/components/practical/views/form/fieldset_title_component.rb +20 -0
- data/app/components/practical/views/form/input_component.html.erb +7 -0
- data/app/components/practical/views/form/input_component.rb +22 -0
- data/app/components/practical/views/form/option_label_component.rb +21 -0
- data/app/components/practical/views/form/practical_editor_component.rb +26 -0
- data/app/components/practical/views/form/required_radio_collection_wrapper_component.rb +23 -0
- data/app/components/practical/views/form_wrapper.rb +21 -0
- data/app/components/practical/views/icon_component.rb +36 -0
- data/app/components/practical/views/icon_for_file_extension_component.rb +53 -0
- data/app/components/practical/views/modal_dialog_component.html.erb +10 -0
- data/app/components/practical/views/modal_dialog_component.rb +16 -0
- data/app/components/practical/views/navigation/breadcrumb_item_component.rb +20 -0
- data/app/components/practical/views/navigation/breadcrumbs_component.html.erb +31 -0
- data/app/components/practical/views/navigation/breadcrumbs_component.rb +41 -0
- data/app/components/practical/views/navigation/navigation_link_component.rb +39 -0
- data/app/components/practical/views/navigation/pagination/goto_form_component.html.erb +31 -0
- data/app/components/practical/views/navigation/pagination/goto_form_component.rb +34 -0
- data/app/components/practical/views/navigation/pagination_component.html.erb +11 -0
- data/app/components/practical/views/navigation/pagination_component.rb +98 -0
- data/app/components/practical/views/open_dialog_button_component.rb +16 -0
- data/app/components/practical/views/page_component.html.erb +53 -0
- data/app/components/practical/views/page_component.rb +12 -0
- data/app/components/practical/views/relative_time_component.rb +13 -0
- data/app/components/practical/views/tiptap_document_component.rb +311 -0
- data/app/components/practical/views/toast_component.html.erb +26 -0
- data/app/components/practical/views/toast_component.rb +19 -0
- data/app/controllers/concerns/practical/auth/passkeys/emergency_registrations.rb +57 -0
- data/app/controllers/concerns/practical/auth/passkeys/web_authn_debug_context.rb +13 -0
- data/app/controllers/concerns/practical/views/flash_helpers.rb +37 -0
- data/app/controllers/concerns/practical/views/json_redirection.rb +7 -0
- data/app/lib/practical/defaults/shrine.rb +48 -0
- data/app/lib/practical/test/helpers/administrator/test_helpers.rb +7 -0
- data/app/lib/practical/test/helpers/extra_assertions.rb +7 -0
- data/app/lib/practical/test/helpers/flash_assertions.rb +8 -0
- data/app/lib/practical/test/helpers/integration/assertions.rb +23 -0
- data/app/lib/practical/test/helpers/passkey/system/base.rb +52 -0
- data/app/lib/practical/test/helpers/passkey/system/rack_test.rb +45 -0
- data/app/lib/practical/test/helpers/passkey/system/selenium.rb +107 -0
- data/app/lib/practical/test/helpers/passkey/test_helper.rb +128 -0
- data/app/lib/practical/test/helpers/postmark.rb +11 -0
- data/app/lib/practical/test/helpers/relation_builder_assertions.rb +18 -0
- data/app/lib/practical/test/helpers/setup/debug.rb +8 -0
- data/app/lib/practical/test/helpers/setup/faker_seed_pinning.rb +8 -0
- data/app/lib/practical/test/helpers/setup/simplecov.rb +17 -0
- data/app/lib/practical/test/helpers/shrine/test_data.rb +101 -0
- data/app/lib/practical/test/helpers/spy_assertions.rb +7 -0
- data/app/lib/practical/test/helpers/system/assertions.rb +33 -0
- data/app/lib/practical/test/helpers/system/capybara_prep.rb +10 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/base.rb +372 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/emergency_registration/self_service.rb +66 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/reauthentication/base.rb +119 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/no_self_destroy.rb +13 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/no_self_signup.rb +22 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_destroy.rb +134 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/self_signup.rb +221 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/registrations/update.rb +220 -0
- data/app/lib/practical/test/shared/auth/passkeys/controllers/sessions/base.rb +108 -0
- data/app/lib/practical/test/shared/auth/passkeys/forms/emergency_registration.rb +82 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/emergency_registration/base.rb +89 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/emergency_registration/use_for_and_notify.rb +48 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/passkey.rb +101 -0
- data/app/lib/practical/test/shared/auth/passkeys/models/resource_with_passkeys.rb +57 -0
- data/app/lib/practical/test/shared/auth/passkeys/policies/passkey.rb +18 -0
- data/app/lib/practical/test/shared/auth/passkeys/services/send_emergency_registration.rb +41 -0
- data/app/lib/practical/test/shared/models/normalized_email.rb +23 -0
- data/app/lib/practical/test/shared/models/user.rb +27 -0
- data/app/lib/practical/test/shared/models/utility/ip_address.rb +42 -0
- data/app/lib/practical/test/shared/models/utility/user_agent.rb +43 -0
- data/app/lib/practical/views/button/styling.rb +23 -0
- data/app/lib/practical/views/error_handling.rb +33 -0
- data/app/lib/practical/views/form_builders/base.rb +152 -0
- data/app/lib/practical/views/icon_set.rb +156 -0
- data/app/lib/practical/views/web_awesome/style_utility/appearance_variant.rb +19 -0
- data/app/lib/practical/views/web_awesome/style_utility/base.rb +21 -0
- data/app/lib/practical/views/web_awesome/style_utility/color_variant.rb +17 -0
- data/app/lib/practical/views/web_awesome/style_utility/size.rb +31 -0
- data/config/locales/auth.en.yml +38 -0
- data/config/locales/devise.passkeys.en.yml +18 -0
- data/config/locales/practical_framework.en.yml +9 -0
- data/config/routes.rb +4 -0
- data/db/seeds/setup.rb +13 -0
- data/db/seeds/users/default.rb +34 -0
- data/lib/generators/practical/test/helper/USAGE +8 -0
- data/lib/generators/practical/test/helper/helper_generator.rb +9 -0
- data/lib/generators/practical/test/helper/templates/helper.rb.tt +4 -0
- data/lib/generators/practical/test/shared_test/USAGE +9 -0
- data/lib/generators/practical/test/shared_test/shared_test_generator.rb +7 -0
- data/lib/generators/practical/test/shared_test/templates/shared_test.rb.tt +9 -0
- data/lib/generators/practical/views/component/USAGE +9 -0
- data/lib/generators/practical/views/component/component_generator.rb +20 -0
- data/lib/practical/framework/engine.rb +35 -0
- data/lib/practical/helpers/form_with_helper.rb +10 -0
- data/lib/practical/helpers/icon_helper.rb +18 -0
- data/lib/practical/helpers/text_helper.rb +20 -0
- data/lib/practical/helpers/translation_helper.rb +25 -0
- data/lib/practical/version.rb +5 -0
- data/lib/practical/views/element_helper.rb +48 -0
- data/lib/practical.rb +21 -0
- data/lib/tasks/practical/coverage.rake +19 -0
- data/lib/tasks/practical/framework_tasks.rake +6 -0
- metadata +303 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webauthn/fake_client"
|
4
|
+
|
5
|
+
module Practical::Test::Helpers::Passkey::System::Selenium
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include Practical::Test::Helpers::Passkey::System::Base
|
8
|
+
|
9
|
+
included do
|
10
|
+
teardown do
|
11
|
+
if @virtual_authenticator.present? && @virtual_authenticator.valid?
|
12
|
+
@virtual_authenticator.remove!
|
13
|
+
end
|
14
|
+
rescue Selenium::WebDriver::Error::InvalidArgumentError
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.selenium_driver_key
|
18
|
+
if ENV.has_key?("HEADLESS_TESTS")
|
19
|
+
return :headless_chrome
|
20
|
+
else
|
21
|
+
return :chrome
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
driven_by :selenium, using: selenium_driver_key, screen_size: [1400, 1400] do |options|
|
26
|
+
options.accept_insecure_certs = true
|
27
|
+
options.args << "--auto-open-devtools-for-tabs"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_passkey_for_user_and_return_webauthn_credential(user:)
|
32
|
+
webauthn_credential = create_webauthn_credential_from_scratch(webauthn_client: user_webauthn_client,
|
33
|
+
rp_id: user_relying_party_id,
|
34
|
+
relying_party: user_relying_party
|
35
|
+
)
|
36
|
+
# rubocop:disable Layout/LineLength
|
37
|
+
keypair = fake_authenticator.instance_variable_get("@credentials")[user_relying_party_id][webauthn_credential.id][:credential_key]
|
38
|
+
# rubocop:enable Layout/LineLength
|
39
|
+
|
40
|
+
resident_credential = create_credential(
|
41
|
+
rp_id: user_relying_party_id,
|
42
|
+
id: webauthn_credential.id.bytes,
|
43
|
+
keypair: keypair
|
44
|
+
)
|
45
|
+
|
46
|
+
authenticator = add_virtual_authenticator
|
47
|
+
authenticator.add_credential(resident_credential)
|
48
|
+
|
49
|
+
user.passkeys.create!(
|
50
|
+
label: SecureRandom.hex,
|
51
|
+
external_id: Base64.strict_encode64(webauthn_credential.id),
|
52
|
+
public_key: Base64.strict_encode64(webauthn_credential.public_key),
|
53
|
+
sign_count: 0
|
54
|
+
)
|
55
|
+
|
56
|
+
return webauthn_credential
|
57
|
+
end
|
58
|
+
|
59
|
+
def authenticator_options(options: {})
|
60
|
+
options = options.reverse_merge({
|
61
|
+
protocol: :ctap2,
|
62
|
+
resident_key: true,
|
63
|
+
user_verification: true,
|
64
|
+
user_verified: true
|
65
|
+
})
|
66
|
+
Selenium::WebDriver::VirtualAuthenticatorOptions.new(**options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_virtual_authenticator(options: authenticator_options)
|
70
|
+
@virtual_authenticator ||= Capybara.current_session.driver.browser.add_virtual_authenticator(options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def default_authenticator
|
74
|
+
@virtual_authenticator
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_credential(rp_id:, user_handle: generate_user_handle, id:, keypair:)
|
78
|
+
private_key = Base64.strict_encode64(keypair.private_to_der)
|
79
|
+
decoded_private_key = Base64.strict_decode64(private_key).bytes
|
80
|
+
|
81
|
+
return Selenium::WebDriver::Credential.resident(
|
82
|
+
id: id,
|
83
|
+
private_key: decoded_private_key,
|
84
|
+
rp_id: rp_id,
|
85
|
+
user_handle: user_handle
|
86
|
+
)
|
87
|
+
|
88
|
+
Selenium::WebDriver::Credential.resident(
|
89
|
+
id: generate_credential_id,
|
90
|
+
private_key: decoded_private_key,
|
91
|
+
rp_id: "localhost",
|
92
|
+
user_handle: generate_user_handle
|
93
|
+
)
|
94
|
+
|
95
|
+
keypair.public_key.to_bn.to_s(2)
|
96
|
+
private_key = keypair.private_key.to_bn.to_s(2)
|
97
|
+
return Selenium::WebDriver::Credential.resident(id: id.bytes,
|
98
|
+
private_key: private_key.bytes,
|
99
|
+
rp_id: rp_id,
|
100
|
+
user_handle: user_handle.bytes
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate_user_handle
|
105
|
+
SecureRandom.uuid.bytes
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webauthn/fake_client"
|
4
|
+
|
5
|
+
module Practical::Test::Helpers::Passkey::TestHelper
|
6
|
+
def assert_passkey_registration_challenge(
|
7
|
+
data:,
|
8
|
+
stored_challenge:,
|
9
|
+
relying_party_data:,
|
10
|
+
user_data:,
|
11
|
+
credentials_to_exclude: []
|
12
|
+
)
|
13
|
+
authentication_selection = {"residentKey"=>"required", "userVerification"=>"required"}
|
14
|
+
|
15
|
+
assert_equal stored_challenge, data["challenge"]
|
16
|
+
assert_equal relying_party_data, data["rp"]
|
17
|
+
assert_equal user_data, data["user"]
|
18
|
+
assert_equal authentication_selection, data["authenticatorSelection"]
|
19
|
+
|
20
|
+
assert_equal credentials_to_exclude, data["excludeCredentials"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def assert_passkey_authentication_challenge(
|
24
|
+
data:,
|
25
|
+
stored_challenge:,
|
26
|
+
credentials_to_allow: []
|
27
|
+
)
|
28
|
+
assert_equal stored_challenge, data["challenge"]
|
29
|
+
assert_equal "required", data["userVerification"]
|
30
|
+
|
31
|
+
assert_equal credential_id_for_challenge(passkeys: credentials_to_allow), data["allowCredentials"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def credential_id_for_challenge(passkeys:)
|
35
|
+
Array.wrap(passkeys).map{|x| {"type" => "public-key", "id" => x.external_id} }
|
36
|
+
end
|
37
|
+
|
38
|
+
def administrator_user_data(administrator:)
|
39
|
+
{"name"=>administrator.email, "id"=>administrator.webauthn_id, "displayName"=>administrator.email}
|
40
|
+
end
|
41
|
+
|
42
|
+
def user_data(user:)
|
43
|
+
{"name"=>user.email, "id"=>user.webauthn_id, "displayName"=>user.name}
|
44
|
+
end
|
45
|
+
|
46
|
+
def credential_data_for_passkey(passkey:)
|
47
|
+
{"type"=>"public-key", "id"=> passkey.external_id}
|
48
|
+
end
|
49
|
+
|
50
|
+
def relying_party_origin
|
51
|
+
AppSettings.relying_party_origin
|
52
|
+
end
|
53
|
+
|
54
|
+
def admin_relying_party_origin
|
55
|
+
AppSettings.admin_relying_party_origin
|
56
|
+
end
|
57
|
+
|
58
|
+
def admin_relying_party_id
|
59
|
+
URI.parse(admin_relying_party_origin).host
|
60
|
+
end
|
61
|
+
|
62
|
+
def user_relying_party_id
|
63
|
+
URI.parse(relying_party_origin).host
|
64
|
+
end
|
65
|
+
|
66
|
+
def admin_relying_party(options: {})
|
67
|
+
return WebAuthn::RelyingParty.new(**{
|
68
|
+
allowed_origins: admin_relying_party_origin,
|
69
|
+
name: I18n.translate("administrator.app_title.text")
|
70
|
+
}.merge(options))
|
71
|
+
end
|
72
|
+
|
73
|
+
def relying_party(options: {})
|
74
|
+
return WebAuthn::RelyingParty.new(**{
|
75
|
+
allowed_origins: relying_party_origin,
|
76
|
+
name: I18n.translate("app_title.text")
|
77
|
+
}.merge(options))
|
78
|
+
end
|
79
|
+
|
80
|
+
def fake_authenticator
|
81
|
+
return WebAuthn::FakeAuthenticator.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def fake_client(origin: "https://example.test", authenticator: WebAuthn::FakeAuthenticator.new)
|
85
|
+
return WebAuthn::FakeClient.new(origin, authenticator: authenticator)
|
86
|
+
end
|
87
|
+
|
88
|
+
def encode_challenge(raw_challenge:)
|
89
|
+
Base64.strict_encode64(raw_challenge)
|
90
|
+
end
|
91
|
+
|
92
|
+
def assertion_from_client(client:, challenge:, user_verified: true)
|
93
|
+
client.get(challenge: challenge, user_verified: user_verified)
|
94
|
+
end
|
95
|
+
|
96
|
+
def hydrate_response_from_raw_credential(client:, relying_party:, raw_credential:)
|
97
|
+
attestation_object = if client.encoding
|
98
|
+
relying_party.encoder.decode(raw_credential["response"]["attestationObject"])
|
99
|
+
else
|
100
|
+
raw_credential["response"]["attestationObject"]
|
101
|
+
end
|
102
|
+
|
103
|
+
client_data_json =
|
104
|
+
if client.encoding
|
105
|
+
relying_party.encoder.decode(raw_credential["response"]["clientDataJSON"])
|
106
|
+
else
|
107
|
+
raw_credential["response"]["clientDataJSON"]
|
108
|
+
end
|
109
|
+
|
110
|
+
return WebAuthn::AuthenticatorAttestationResponse.new(
|
111
|
+
attestation_object: attestation_object,
|
112
|
+
client_data_json: client_data_json,
|
113
|
+
relying_party: relying_party
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def create_credential_and_return_payload_from_challenge(client:, challenge:, user_verified: true)
|
118
|
+
return client.create(challenge: challenge, user_verified: user_verified)
|
119
|
+
end
|
120
|
+
|
121
|
+
def get_credential_payload_from_challenge(client:, challenge:, user_verified: true)
|
122
|
+
client.get(challenge: challenge, user_verified: user_verified)
|
123
|
+
end
|
124
|
+
|
125
|
+
def encoded_public_key(credential:, relying_party:)
|
126
|
+
relying_party.encoder.encode(credential.public_key)
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Helpers::Postmark
|
4
|
+
def extract_template_model(mail:)
|
5
|
+
scanner = StringScanner.new(mail.body.encoded)
|
6
|
+
scanner.skip_until(/Model:\r\n/)
|
7
|
+
|
8
|
+
result = scanner.scan_until(/^Use the #prerender/)
|
9
|
+
return JSON.parse(result.gsub("Use the #prerender", ''))
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Helpers::RelationBuilderAssertions
|
4
|
+
def assert_relation_ordering_matches(expected:, relation:, instance:)
|
5
|
+
assert_not_equal expected, relation
|
6
|
+
assert_sql_transformation(expected: expected, actual: instance.apply_ordering(scope: relation))
|
7
|
+
end
|
8
|
+
|
9
|
+
def assert_relation_filtering_matches(expected:, relation:, instance:)
|
10
|
+
assert_not_equal expected, relation
|
11
|
+
assert_sql_transformation(expected: expected, actual: instance.apply_filtering(scope: relation))
|
12
|
+
end
|
13
|
+
|
14
|
+
def assert_sql_transformation(expected:, actual:)
|
15
|
+
message = {expected_sql: expected.to_sql, actual_sql: actual.to_sql}
|
16
|
+
assert_equal expected, actual, message
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Helpers::Setup::Debug
|
4
|
+
# rubocop:disable Rails/Output
|
5
|
+
puts "MINITEST_PARALLEL_EXECUTOR_SIZE: #{Minitest.parallel_executor.size}"
|
6
|
+
puts "PARALLEL_WORKERS: #{ENV["PARALLEL_WORKERS"]}"
|
7
|
+
# rubocop:enable Rails/Output
|
8
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Helpers::Setup::Simplecov
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
if ENV['COVERAGE']
|
7
|
+
included do
|
8
|
+
parallelize_setup do |worker|
|
9
|
+
SimpleCov.command_name "#{SimpleCov.command_name}-#{worker}"
|
10
|
+
end
|
11
|
+
|
12
|
+
parallelize_teardown do |worker|
|
13
|
+
SimpleCov.result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shrine/storage/memory"
|
4
|
+
|
5
|
+
Shrine.storages = {
|
6
|
+
cache: Shrine::Storage::Memory.new,
|
7
|
+
store: Shrine::Storage::Memory.new
|
8
|
+
}
|
9
|
+
|
10
|
+
module Practical::Test::Helpers::Shrine::TestData
|
11
|
+
def image_data
|
12
|
+
attacher = Shrine::Attacher.new
|
13
|
+
attacher.set(uploaded_image)
|
14
|
+
|
15
|
+
attacher.data
|
16
|
+
end
|
17
|
+
|
18
|
+
def uploaded_image
|
19
|
+
file = image_file
|
20
|
+
uploaded_file = Shrine.upload(file, :store, metadata: false)
|
21
|
+
uploaded_file.metadata.merge!(
|
22
|
+
"size" => File.size(file.path),
|
23
|
+
"mime_type" => "image/jpeg",
|
24
|
+
"filenae" => "test.jpeg"
|
25
|
+
)
|
26
|
+
|
27
|
+
uploaded_file
|
28
|
+
end
|
29
|
+
|
30
|
+
def image_filepath
|
31
|
+
file_fixture("dog.jpeg")
|
32
|
+
end
|
33
|
+
|
34
|
+
def image_file
|
35
|
+
File.open(image_filepath)
|
36
|
+
end
|
37
|
+
|
38
|
+
def heic_filepath
|
39
|
+
file_fixture("island.heic")
|
40
|
+
end
|
41
|
+
|
42
|
+
def heic_file
|
43
|
+
File.open(heic_filepath)
|
44
|
+
end
|
45
|
+
|
46
|
+
def csv_filepath
|
47
|
+
file_fixture("example.csv")
|
48
|
+
end
|
49
|
+
|
50
|
+
def csv_file
|
51
|
+
File.open(csv_filepath)
|
52
|
+
end
|
53
|
+
|
54
|
+
def excel_filepath
|
55
|
+
file_fixture("example.xlsx")
|
56
|
+
end
|
57
|
+
|
58
|
+
def excel_file
|
59
|
+
File.open(excel_filepath)
|
60
|
+
end
|
61
|
+
|
62
|
+
def word_filepath
|
63
|
+
file_fixture("example.docx")
|
64
|
+
end
|
65
|
+
|
66
|
+
def pdf_file
|
67
|
+
File.open(pdf_filepath)
|
68
|
+
end
|
69
|
+
|
70
|
+
def pdf_filepath
|
71
|
+
file_fixture("example.pdf")
|
72
|
+
end
|
73
|
+
|
74
|
+
def word_file
|
75
|
+
File.open(word_filepath)
|
76
|
+
end
|
77
|
+
|
78
|
+
def numbers_filepath
|
79
|
+
file_fixture("numbers-example.numbers")
|
80
|
+
end
|
81
|
+
|
82
|
+
def numbers_file
|
83
|
+
File.open(numbers_filepath)
|
84
|
+
end
|
85
|
+
|
86
|
+
def plaintext_filepath
|
87
|
+
file_fixture("example.txt")
|
88
|
+
end
|
89
|
+
|
90
|
+
def plaintext_file
|
91
|
+
File.open(plaintext_filepath)
|
92
|
+
end
|
93
|
+
|
94
|
+
def rtf_filepath
|
95
|
+
file_fixture("example.rtf")
|
96
|
+
end
|
97
|
+
|
98
|
+
def rtf_file
|
99
|
+
File.open(rtf_filepath)
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Helpers::System::Assertions
|
4
|
+
def assert_toast_message(text:)
|
5
|
+
within(".notification-messages") do
|
6
|
+
assert_selector("dialog", text: text)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def assert_sign_in_user(user:)
|
11
|
+
create_passkey_for_user_and_return_webauthn_credential(
|
12
|
+
user: user
|
13
|
+
)
|
14
|
+
|
15
|
+
if Capybara.current_driver == :rack_test
|
16
|
+
page.driver.browser.post(new_user_session_challenge_url, as: :json)
|
17
|
+
challenge_data = JSON.parse(page.driver.response.body)
|
18
|
+
end
|
19
|
+
|
20
|
+
visit new_user_session_url
|
21
|
+
|
22
|
+
fill_in "Email", with: user.email
|
23
|
+
|
24
|
+
if Capybara.current_driver == :rack_test
|
25
|
+
credential_data = user_webauthn_client.get(challenge: challenge_data["challenge"], user_verified: true)
|
26
|
+
find("input[type=hidden]#user_passkey_credential", visible: false).set(JSON.generate(credential_data))
|
27
|
+
end
|
28
|
+
|
29
|
+
click_on "Sign in"
|
30
|
+
|
31
|
+
assert_toast_message(text: I18n.translate("devise.sessions.signed_in"))
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Practical::Test::Helpers::System::CapybaraPrep
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
included do
|
6
|
+
Capybara.save_path = Rails.root.join("tmp", "capybara").to_s
|
7
|
+
Capybara.server_host = AppSettings.default_host
|
8
|
+
Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}"}
|
9
|
+
end
|
10
|
+
end
|