From 4fef4a22883ec2be62952006cf0df3b469b8ad58 Mon Sep 17 00:00:00 2001 From: jsklan Date: Mon, 18 Aug 2025 12:03:23 -0400 Subject: [PATCH 1/2] pull in test helper infra from demo --- .../catalog}/test_catalog_serial.rb | 0 test/{ => square}/integration/helpers.rb | 0 .../integration/integration_test_base.rb | 0 .../integration/test_cash_drawers.rb | 0 .../integration/test_client_utils.rb | 0 .../integration/test_customer_groups.rb | 0 .../integration/test_customer_segments.rb | 0 test/{ => square}/integration/test_devices.rb | 0 test/{ => square}/integration/test_legacy.rb | 0 .../integration/test_locations.rb | 0 .../integration/test_merchants.rb | 0 .../integration/test_mobile_authorization.rb | 0 test/{ => square}/integration/test_orders.rb | 0 .../{ => square}/integration/test_payments.rb | 0 test/{ => square}/integration/test_refunds.rb | 0 test/{ => square}/integration/test_teams.rb | 0 .../{ => square}/integration/test_terminal.rb | 0 .../integration/testdata/image.jpeg | Bin .../internal/multipart/test_form_data.rb | 315 ++++++++++++++++++ test/square/internal/types/test_array.rb | 37 ++ test/square/internal/types/test_boolean.rb | 35 ++ test/square/internal/types/test_enum.rb | 42 +++ test/square/internal/types/test_hash.rb | 50 +++ test/square/internal/types/test_model.rb | 124 +++++++ test/square/internal/types/test_union.rb | 52 +++ test/square/internal/types/test_utils.rb | 216 ++++++++++++ test/square/test_helper.rb | 19 ++ test/{ => square_legacy}/api/api_test_base.rb | 0 .../api/test_catalog_api.rb | 0 .../api/test_customers_api.rb | 0 .../api/test_employees_api.rb | 0 .../{ => square_legacy}/api/test_labor_api.rb | 0 .../api/test_locations_api.rb | 0 .../api/test_merchants_api.rb | 0 .../api/test_payments_api.rb | 0 .../api/test_refunds_api.rb | 0 .../http_response_catcher.rb | 0 .../webhooks/test_webhooks_helper.rb | 0 38 files changed, 890 insertions(+) rename test/{integration => square/integration/catalog}/test_catalog_serial.rb (100%) rename test/{ => square}/integration/helpers.rb (100%) rename test/{ => square}/integration/integration_test_base.rb (100%) rename test/{ => square}/integration/test_cash_drawers.rb (100%) rename test/{ => square}/integration/test_client_utils.rb (100%) rename test/{ => square}/integration/test_customer_groups.rb (100%) rename test/{ => square}/integration/test_customer_segments.rb (100%) rename test/{ => square}/integration/test_devices.rb (100%) rename test/{ => square}/integration/test_legacy.rb (100%) rename test/{ => square}/integration/test_locations.rb (100%) rename test/{ => square}/integration/test_merchants.rb (100%) rename test/{ => square}/integration/test_mobile_authorization.rb (100%) rename test/{ => square}/integration/test_orders.rb (100%) rename test/{ => square}/integration/test_payments.rb (100%) rename test/{ => square}/integration/test_refunds.rb (100%) rename test/{ => square}/integration/test_teams.rb (100%) rename test/{ => square}/integration/test_terminal.rb (100%) rename test/{ => square}/integration/testdata/image.jpeg (100%) create mode 100644 test/square/internal/multipart/test_form_data.rb create mode 100644 test/square/internal/types/test_array.rb create mode 100644 test/square/internal/types/test_boolean.rb create mode 100644 test/square/internal/types/test_enum.rb create mode 100644 test/square/internal/types/test_hash.rb create mode 100644 test/square/internal/types/test_model.rb create mode 100644 test/square/internal/types/test_union.rb create mode 100644 test/square/internal/types/test_utils.rb create mode 100644 test/square/test_helper.rb rename test/{ => square_legacy}/api/api_test_base.rb (100%) rename test/{ => square_legacy}/api/test_catalog_api.rb (100%) rename test/{ => square_legacy}/api/test_customers_api.rb (100%) rename test/{ => square_legacy}/api/test_employees_api.rb (100%) rename test/{ => square_legacy}/api/test_labor_api.rb (100%) rename test/{ => square_legacy}/api/test_locations_api.rb (100%) rename test/{ => square_legacy}/api/test_merchants_api.rb (100%) rename test/{ => square_legacy}/api/test_payments_api.rb (100%) rename test/{ => square_legacy}/api/test_refunds_api.rb (100%) rename test/{ => square_legacy}/http_response_catcher.rb (100%) rename test/{ => square_legacy}/webhooks/test_webhooks_helper.rb (100%) diff --git a/test/integration/test_catalog_serial.rb b/test/square/integration/catalog/test_catalog_serial.rb similarity index 100% rename from test/integration/test_catalog_serial.rb rename to test/square/integration/catalog/test_catalog_serial.rb diff --git a/test/integration/helpers.rb b/test/square/integration/helpers.rb similarity index 100% rename from test/integration/helpers.rb rename to test/square/integration/helpers.rb diff --git a/test/integration/integration_test_base.rb b/test/square/integration/integration_test_base.rb similarity index 100% rename from test/integration/integration_test_base.rb rename to test/square/integration/integration_test_base.rb diff --git a/test/integration/test_cash_drawers.rb b/test/square/integration/test_cash_drawers.rb similarity index 100% rename from test/integration/test_cash_drawers.rb rename to test/square/integration/test_cash_drawers.rb diff --git a/test/integration/test_client_utils.rb b/test/square/integration/test_client_utils.rb similarity index 100% rename from test/integration/test_client_utils.rb rename to test/square/integration/test_client_utils.rb diff --git a/test/integration/test_customer_groups.rb b/test/square/integration/test_customer_groups.rb similarity index 100% rename from test/integration/test_customer_groups.rb rename to test/square/integration/test_customer_groups.rb diff --git a/test/integration/test_customer_segments.rb b/test/square/integration/test_customer_segments.rb similarity index 100% rename from test/integration/test_customer_segments.rb rename to test/square/integration/test_customer_segments.rb diff --git a/test/integration/test_devices.rb b/test/square/integration/test_devices.rb similarity index 100% rename from test/integration/test_devices.rb rename to test/square/integration/test_devices.rb diff --git a/test/integration/test_legacy.rb b/test/square/integration/test_legacy.rb similarity index 100% rename from test/integration/test_legacy.rb rename to test/square/integration/test_legacy.rb diff --git a/test/integration/test_locations.rb b/test/square/integration/test_locations.rb similarity index 100% rename from test/integration/test_locations.rb rename to test/square/integration/test_locations.rb diff --git a/test/integration/test_merchants.rb b/test/square/integration/test_merchants.rb similarity index 100% rename from test/integration/test_merchants.rb rename to test/square/integration/test_merchants.rb diff --git a/test/integration/test_mobile_authorization.rb b/test/square/integration/test_mobile_authorization.rb similarity index 100% rename from test/integration/test_mobile_authorization.rb rename to test/square/integration/test_mobile_authorization.rb diff --git a/test/integration/test_orders.rb b/test/square/integration/test_orders.rb similarity index 100% rename from test/integration/test_orders.rb rename to test/square/integration/test_orders.rb diff --git a/test/integration/test_payments.rb b/test/square/integration/test_payments.rb similarity index 100% rename from test/integration/test_payments.rb rename to test/square/integration/test_payments.rb diff --git a/test/integration/test_refunds.rb b/test/square/integration/test_refunds.rb similarity index 100% rename from test/integration/test_refunds.rb rename to test/square/integration/test_refunds.rb diff --git a/test/integration/test_teams.rb b/test/square/integration/test_teams.rb similarity index 100% rename from test/integration/test_teams.rb rename to test/square/integration/test_teams.rb diff --git a/test/integration/test_terminal.rb b/test/square/integration/test_terminal.rb similarity index 100% rename from test/integration/test_terminal.rb rename to test/square/integration/test_terminal.rb diff --git a/test/integration/testdata/image.jpeg b/test/square/integration/testdata/image.jpeg similarity index 100% rename from test/integration/testdata/image.jpeg rename to test/square/integration/testdata/image.jpeg diff --git a/test/square/internal/multipart/test_form_data.rb b/test/square/internal/multipart/test_form_data.rb new file mode 100644 index 000000000..d51a9e724 --- /dev/null +++ b/test/square/internal/multipart/test_form_data.rb @@ -0,0 +1,315 @@ +# frozen_string_literal: true + +require "minitest/autorun" +require "stringio" +require "json" + +require_relative "../../../../lib/square/file_param" +require_relative "../../../../lib/square/internal/multipart/multipart_form_data_part" +require_relative "../../../../lib/square/internal/multipart/multipart_form_data" +require_relative "../../../../lib/square/internal/multipart/multipart_encoder" + +class MockFile + attr_reader :name, :content, :content_type + + def initialize(name:, content:, content_type: nil) + @name = name + @content = content + @content_type = content_type + end + + def read + @content + end +end + +class MultipartTest < Minitest::Test + def test_empty_form_data + form_data = Square::Internal::Multipart::FormData.new + body = form_data.encode + + assert_equal "", body + end + + def test_write_field + [ + { + desc: "empty field", + field: "empty", + value: "" + }, + { + desc: "simple field", + field: "greeting", + value: "hello world" + }, + { + desc: "field with content type", + field: "message", + value: "hello", + content_type: "text/plain" + } + ].each do |test_case| + form_data = Square::Internal::Multipart::FormData.new + content_type_arg = test_case[:content_type] + + form_data.add( + name: test_case[:field], + value: test_case[:value], + content_type: content_type_arg + ) + + content_type = form_data.content_type + body = form_data.encode + + # Extract the boundary from the content type + boundary = content_type.match(/boundary=([^;]+)/)[1] + + # Verify the field was encoded properly + assert_includes body, "--#{boundary}" + assert_includes body, "Content-Disposition: form-data; name=\"#{test_case[:field]}\"" + + assert_includes body, "Content-Type: #{content_type_arg}" if content_type_arg + + assert_includes body, test_case[:value] + end + end + + def test_write_file_param + [ + { + desc: "file param with content type", + field: "file", + filename: "test.txt", + content: "hello world", + content_type: "text/plain" + }, + { + desc: "file param with override content type", + field: "file", + filename: "test.txt", + content: "hello world", + content_type: "text/plain", + override_content_type: "application/octet-stream" + } + ].each do |test_case| + form_data = Square::Internal::Multipart::FormData.new + + file_param = Square::FileParam.from_string( + content: test_case[:content], + filename: test_case[:filename], + content_type: test_case[:content_type] + ) + + form_data.add_part( + file_param.to_form_data_part( + name: test_case[:field], + content_type: test_case[:override_content_type] + ) + ) + + content_type = form_data.content_type + body = form_data.encode + + # Extract the boundary from the content type + boundary = content_type.match(/boundary=([^;]+)/)[1] + + # Verify the file param was encoded properly + assert_includes body, "--#{boundary}" + assert_includes body, + "Content-Disposition: form-data; name=\"#{test_case[:field]}\"; filename=\"#{test_case[:filename]}\"" + + expected_content_type = test_case[:override_content_type] || test_case[:content_type] + assert_includes body, "Content-Type: #{expected_content_type}" if expected_content_type + + assert_includes body, test_case[:content] + end + end + + def test_write_json + [ + { + desc: "struct", + field: "data", + value: { name: "test", value: 123 } + }, + { + desc: "map", + field: "data", + value: { key: "value" } + } + ].each do |test_case| + form_data = Square::Internal::Multipart::FormData.new + + form_data.add( + name: test_case[:field], + value: test_case[:value].to_json, + content_type: "application/json" + ) + + content_type = form_data.content_type + body = form_data.encode + + # Extract the boundary from the content type + boundary = content_type.match(/boundary=([^;]+)/)[1] + + # Verify the JSON was encoded properly + assert_includes body, "--#{boundary}" + assert_includes body, "Content-Disposition: form-data; name=\"#{test_case[:field]}\"" + assert_includes body, "Content-Type: application/json" + assert_includes body, test_case[:value].to_json + end + end + + def test_complex_form + form_data = Square::Internal::Multipart::FormData.new + + # Add multiple fields and files + form_data.add(name: "foo", value: "bar") + form_data.add(name: "baz", value: "qux") + + part = Square::FileParam.from_string( + content: "Hello, world!", + filename: "file.txt", + content_type: "text/plain" + ) + + form_data.add_part(part.to_form_data_part(name: "file")) + form_data.add(name: "data", value: { key: "value" }.to_json, content_type: "application/json") + + content_type = form_data.content_type + body = form_data.encode + + # Extract the boundary from the content type + boundary = content_type.match(/boundary=([^;]+)/)[1] + + # Verify all parts are in the body + assert_includes body, "--#{boundary}" + assert_includes body, "Content-Disposition: form-data; name=\"foo\"" + assert_includes body, "bar" + assert_includes body, "Content-Disposition: form-data; name=\"baz\"" + assert_includes body, "qux" + assert_includes body, "Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"" + assert_includes body, "Content-Type: text/plain" + assert_includes body, "Hello, world!" + assert_includes body, "Content-Disposition: form-data; name=\"data\"" + assert_includes body, "Content-Type: application/json" + assert_includes body, "{\"key\":\"value\"}" + end + + def test_file_param_from_filepath + # Create a temporary file for testing + file_content = "Test file content" + temp_file = Tempfile.new(["test", ".txt"]) + begin + temp_file.write(file_content) + temp_file.rewind + + file_param = Square::FileParam.from_filepath( + filepath: temp_file.path, + content_type: "text/plain" + ) + + # Verify the file param was created properly + form_data = Square::Internal::Multipart::FormData.new + form_data.add_part(file_param.to_form_data_part(name: "file")) + + content_type = form_data.content_type + body = form_data.encode + + # Extract the boundary from the content type + boundary = content_type.match(/boundary=([^;]+)/)[1] + + # Verify the file was encoded properly + assert_includes body, "--#{boundary}" + assert_includes body, + "Content-Disposition: form-data; name=\"file\"; filename=\"#{File.basename(temp_file.path)}\"" + assert_includes body, "Content-Type: text/plain" + assert_includes body, file_content + ensure + # Close and delete the temporary file + temp_file.close + temp_file.unlink + end + end + + def test_file_param_from_string + file_content = "Test string content" + filename = "string.txt" + content_type = "text/plain" + + file_param = Square::FileParam.from_string( + content: file_content, + filename: filename, + content_type: content_type + ) + + # Verify the file param was created properly + form_data = Square::Internal::Multipart::FormData.new + form_data.add_part(file_param.to_form_data_part(name: "file")) + + content_type = form_data.content_type + body = form_data.encode + + # Extract the boundary from the content type + boundary = content_type.match(/boundary=([^;]+)/)[1] + + # Verify the file was encoded properly + assert_includes body, "--#{boundary}" + assert_includes body, "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"" + assert_includes body, "Content-Type: text/plain" + assert_includes body, file_content + end + + def test_encoder_file_reading_in_chunks + file_size = 1024 * 100 # 100KB + large_content = "x" * file_size + + temp_file = Tempfile.new(["large", ".dat"]) + begin + temp_file.write(large_content) + temp_file.rewind + + form_data = Square::Internal::Multipart::FormData.new + form_data.add_file( + name: "large_file", + file: temp_file, + content_type: "application/octet-stream" + ) + + content_type = form_data.content_type + body = form_data.encode + + boundary = content_type.match(/boundary=([^;]+)/)[1] + + assert_includes body, "--#{boundary}" + assert_includes body, + "Content-Disposition: form-data; name=\"large_file\"; filename=\"#{File.basename(temp_file.path)}\"" + assert_includes body, "Content-Type: application/octet-stream" + + content_start = body.index("Content-Type: application/octet-stream\r\n\r\n") + + "Content-Type: application/octet-stream\r\n\r\n".length + + content_end = body.index("\r\n--#{boundary}", content_start) + + actual_content = body[content_start...content_end] + + assert_equal file_size, actual_content.length + + # Verify the content is correct (checking first, middle, and last parts). + assert_equal "x" * 100, actual_content[0...100] + assert_equal "x" * 100, actual_content[((file_size / 2) - 50)...((file_size / 2) + 50)] + assert_equal "x" * 100, actual_content[(file_size - 100)...file_size] + ensure + # Close and delete the temporary file. + temp_file.close + temp_file.unlink + end + end + + def test_error_handling_for_missing_file + assert_raises(StandardError) do + Square::FileParam.from_filepath(filepath: "nonexistent_file.txt") + end + end +end diff --git a/test/square/internal/types/test_array.rb b/test/square/internal/types/test_array.rb new file mode 100644 index 000000000..1cb863d9f --- /dev/null +++ b/test/square/internal/types/test_array.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Internal::Types::Array do + module TestArray + StringArray = Square::Internal::Types::Array[String] + end + + describe "#initialize" do + it "sets the type" do + assert_equal String, TestArray::StringArray.type + end + end + + describe "#coerce" do + it "does not perform coercion if not an array" do + assert_equal 1, TestArray::StringArray.coerce(1) + end + + it "raises an error if not an array and strictness is on" do + assert_raises Square::Internal::Errors::TypeError do + TestArray::StringArray.coerce(1, strict: true) + end + end + + it "coerces the elements" do + assert_equal %w[foobar 1 true], TestArray::StringArray.coerce(["foobar", 1, true]) + end + + it "raises an error if element of array is not coercable and strictness is on" do + assert_raises Square::Internal::Errors::TypeError do + TestArray::StringArray.coerce([Object.new], strict: true) + end + end + end +end diff --git a/test/square/internal/types/test_boolean.rb b/test/square/internal/types/test_boolean.rb new file mode 100644 index 000000000..eaa552add --- /dev/null +++ b/test/square/internal/types/test_boolean.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Internal::Types::Boolean do + describe ".coerce" do + it "coerces true/false" do + assert Square::Internal::Types::Boolean.coerce(true) + refute Square::Internal::Types::Boolean.coerce(false) + end + + it "coerces an Integer" do + assert Square::Internal::Types::Boolean.coerce(1) + refute Square::Internal::Types::Boolean.coerce(0) + end + + it "coerces a String" do + assert Square::Internal::Types::Boolean.coerce("1") + assert Square::Internal::Types::Boolean.coerce("true") + refute Square::Internal::Types::Boolean.coerce("0") + end + + it "passes through other values with strictness off" do + obj = Object.new + + assert_equal obj, Square::Internal::Types::Boolean.coerce(obj) + end + + it "raises an error with other values with strictness on" do + assert_raises Square::Internal::Errors::TypeError do + Square::Internal::Types::Boolean.coerce(Object.new, strict: true) + end + end + end +end diff --git a/test/square/internal/types/test_enum.rb b/test/square/internal/types/test_enum.rb new file mode 100644 index 000000000..e920cff01 --- /dev/null +++ b/test/square/internal/types/test_enum.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Internal::Types::Enum do + module EnumTest + module ExampleEnum + extend Square::Internal::Types::Enum + + FOO = :foo + BAR = :bar + + finalize! + end + end + + describe "#values" do + it "defines values" do + assert_equal %i[foo bar].sort, EnumTest::ExampleEnum.values.sort + end + end + + describe "#coerce" do + it "coerces an existing member" do + assert_equal :foo, EnumTest::ExampleEnum.coerce(:foo) + end + + it "coerces a string version of a member" do + assert_equal :foo, EnumTest::ExampleEnum.coerce("foo") + end + + it "returns the value if not a member with strictness off" do + assert_equal 1, EnumTest::ExampleEnum.coerce(1) + end + + it "raises an error if value is not a member with strictness on" do + assert_raises Square::Internal::Errors::TypeError do + EnumTest::ExampleEnum.coerce(1, strict: true) + end + end + end +end diff --git a/test/square/internal/types/test_hash.rb b/test/square/internal/types/test_hash.rb new file mode 100644 index 000000000..c454f985a --- /dev/null +++ b/test/square/internal/types/test_hash.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Internal::Types::Hash do + module TestHash + SymbolStringHash = Square::Internal::Types::Hash[Symbol, String] + end + + describe ".[]" do + it "defines the key and value type" do + assert_equal Symbol, TestHash::SymbolStringHash.key_type + assert_equal String, TestHash::SymbolStringHash.value_type + end + end + + describe "#coerce" do + it "coerces the keys" do + assert_equal %i[foo bar], TestHash::SymbolStringHash.coerce({ "foo" => "1", :bar => "2" }).keys + end + + it "coerces the values" do + assert_equal %w[foo 1], TestHash::SymbolStringHash.coerce({ foo: :foo, bar: 1 }).values + end + + it "passes through other values with strictness off" do + obj = Object.new + + assert_equal obj, TestHash::SymbolStringHash.coerce(obj) + end + + it "raises an error with other values with strictness on" do + assert_raises Square::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce(Object.new, strict: true) + end + end + + it "raises an error with non-coercable key types with strictness on" do + assert_raises Square::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce({ Object.new => 1 }, strict: true) + end + end + + it "raises an error with non-coercable value types with strictness on" do + assert_raises Square::Internal::Errors::TypeError do + TestHash::SymbolStringHash.coerce({ "foobar" => Object.new }, strict: true) + end + end + end +end diff --git a/test/square/internal/types/test_model.rb b/test/square/internal/types/test_model.rb new file mode 100644 index 000000000..5b612bbb5 --- /dev/null +++ b/test/square/internal/types/test_model.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Internal::Types::Model do + module StringInteger + extend Square::Internal::Types::Union + + member String + member Integer + end + + class ExampleModel < Square::Internal::Types::Model + field :name, String + field :rating, StringInteger, optional: true + field :year, Integer, optional: true, nullable: true, api_name: "yearOfRelease" + end + + class ExampleModelInheritance < ExampleModel + field :director, String + end + + class ExampleWithDefaults < ExampleModel + field :type, String, default: "example" + end + + class ExampleChild < Square::Internal::Types::Model + field :value, String + end + + class ExampleParent < Square::Internal::Types::Model + field :child, ExampleChild + end + + describe ".field" do + before do + @example = ExampleModel.new(name: "Inception", rating: 4) + end + + it "defines fields on model" do + assert_equal %i[name rating year], ExampleModel.fields.keys + end + + it "defines fields from parent models" do + assert_equal %i[name rating year director], ExampleModelInheritance.fields.keys + end + + it "sets the field's type" do + assert_equal String, ExampleModel.fields[:name].type + assert_equal StringInteger, ExampleModel.fields[:rating].type + end + + it "sets the `default` option" do + assert_equal "example", ExampleWithDefaults.fields[:type].default + end + + it "defines the accessors" do + assert_respond_to @example, :name + assert_respond_to @example, :rating + end + + it "defines the setters" do + assert_respond_to @example, :name= + assert_respond_to @example, :rating= + end + end + + describe "#initialize" do + it "sets the data" do + example = ExampleModel.new(name: "Inception", rating: 4) + + assert_equal "Inception", example.name + assert_equal 4, example.rating + end + + it "allows extra fields to be set" do + example = ExampleModel.new(name: "Inception", rating: 4, director: "Christopher Nolan") + + assert_equal "Christopher Nolan", example.director + end + + it "sets the defaults where applicable" do + example_using_defaults = ExampleWithDefaults.new + + assert_equal "example", example_using_defaults.type + + example_without_defaults = ExampleWithDefaults.new(type: "not example") + + assert_equal "not example", example_without_defaults.type + end + + it "coerces child models" do + parent = ExampleParent.new(child: { value: "foobar" }) + + assert_kind_of ExampleChild, parent.child + end + + it "uses the api_name to pull the value" do + example = ExampleModel.new({ name: "Inception", yearOfRelease: 2014 }) + + assert_equal 2014, example.year + refute_respond_to example, :yearOfRelease + end + end + + describe "#to_h" do + it "adds optional and nullable fields to output" do + example = ExampleModel.new( + name: "Inception" + ) + + output = example.to_h + + assert_equal "Inception", output[:name] + + # `rating` is optional but not nullable, so we don't return it at all if it is not present + refute_includes output.keys, :rating + + # `year` is optional AND nullable, so we return a nil value + assert_includes output.keys, :yearOfRelease + assert_nil output[:yearOfRelease] + end + end +end diff --git a/test/square/internal/types/test_union.rb b/test/square/internal/types/test_union.rb new file mode 100644 index 000000000..44df2446e --- /dev/null +++ b/test/square/internal/types/test_union.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require "test_helper" +describe Square::Internal::Types::Union do + class Rectangle < Square::Internal::Types::Model + literal :type, "square" + + field :area, Float + end + + class Circle < Square::Internal::Types::Model + literal :type, "circle" + + field :area, Float + end + + module Shape + extend Square::Internal::Types::Union + + discriminant :type + + member -> { Rectangle }, key: "rect" + member -> { Circle }, key: "circle" + end + + module StringOrInteger + extend Square::Internal::Types::Union + + member String + member Integer + end + + describe "#coerce" do + it "coerces hashes into member models with discriminated unions" do + circle = Shape.coerce({ type: "circle", area: 4.0 }) + + assert_instance_of Circle, circle + end + end + + describe "#member" do + it "defines Model members" do + assert Shape.member?(Rectangle) + assert Shape.member?(Circle) + end + + it "defines other members" do + assert StringOrInteger.member?(String) + assert StringOrInteger.member?(Integer) + end + end +end diff --git a/test/square/internal/types/test_utils.rb b/test/square/internal/types/test_utils.rb new file mode 100644 index 000000000..5de25395b --- /dev/null +++ b/test/square/internal/types/test_utils.rb @@ -0,0 +1,216 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Internal::Types::Utils do + Utils = Square::Internal::Types::Utils + + module TestUtils + class M < Square::Internal::Types::Model + field :value, String + end + + class UnionMemberA < Square::Internal::Types::Model + literal :type, "A" + field :only_on_a, String + end + + class UnionMemberB < Square::Internal::Types::Model + literal :type, "B" + field :only_on_b, String + end + + module U + extend Square::Internal::Types::Union + + discriminant :type + + member -> { UnionMemberA }, key: "A" + member -> { UnionMemberB }, key: "B" + end + + SymbolStringHash = Square::Internal::Types::Hash[Symbol, String] + SymbolModelHash = -> { Square::Internal::Types::Hash[Symbol, TestUtils::M] } + end + + describe ".coerce" do + describe "NilClass" do + it "always returns nil" do + assert_nil Utils.coerce(NilClass, "foobar") + assert_nil Utils.coerce(NilClass, 1) + assert_nil Utils.coerce(NilClass, Object.new) + end + end + + describe "String" do + it "coerces from String, Symbol, Numeric, or Boolean" do + assert_equal "foobar", Utils.coerce(String, "foobar") + assert_equal "foobar", Utils.coerce(String, :foobar) + assert_equal "1", Utils.coerce(String, 1) + assert_equal "1.0", Utils.coerce(String, 1.0) + assert_equal "true", Utils.coerce(String, true) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(String, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Square::Internal::Errors::TypeError do + Utils.coerce(String, Object.new, strict: true) + end + end + end + + describe "Symbol" do + it "coerces from Symbol, String" do + assert_equal :foobar, Utils.coerce(Symbol, :foobar) + assert_equal :foobar, Utils.coerce(Symbol, "foobar") + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Symbol, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Square::Internal::Errors::TypeError do + Utils.coerce(Symbol, Object.new, strict: true) + end + end + end + + describe "Integer" do + it "coerces from Numeric, String, Time" do + assert_equal 1, Utils.coerce(Integer, 1) + assert_equal 1, Utils.coerce(Integer, 1.0) + assert_equal 1, Utils.coerce(Integer, Complex.rect(1)) + assert_equal 1, Utils.coerce(Integer, Rational(1)) + assert_equal 1, Utils.coerce(Integer, "1") + assert_equal 1_713_916_800, Utils.coerce(Integer, Time.utc(2024, 4, 24)) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Integer, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Square::Internal::Errors::TypeError do + Utils.coerce(Integer, Object.new, strict: true) + end + end + end + + describe "Float" do + it "coerces from Numeric, Time" do + assert_in_delta(1.0, Utils.coerce(Float, 1.0)) + assert_in_delta(1.0, Utils.coerce(Float, 1)) + assert_in_delta(1.0, Utils.coerce(Float, Complex.rect(1))) + assert_in_delta(1.0, Utils.coerce(Float, Rational(1))) + assert_in_delta(1_713_916_800.0, Utils.coerce(Integer, Time.utc(2024, 4, 24))) + end + + it "passes through value if it cannot be coerced and not strict" do + obj = Object.new + + assert_equal obj, Utils.coerce(Float, obj) + end + + it "raises an error if value cannot be coerced and strict" do + assert_raises Square::Internal::Errors::TypeError do + Utils.coerce(Float, Object.new, strict: true) + end + end + end + + describe "Model" do + it "coerces a hash" do + result = Utils.coerce(TestUtils::M, { value: "foobar" }) + + assert_kind_of TestUtils::M, result + assert_equal "foobar", result.value + end + + it "coerces a hash when the target is a type function" do + result = Utils.coerce(-> { TestUtils::M }, { value: "foobar" }) + + assert_kind_of TestUtils::M, result + assert_equal "foobar", result.value + end + + it "will not coerce non-hashes" do + assert_equal "foobar", Utils.coerce(TestUtils::M, "foobar") + end + end + + describe "Enum" do + module ExampleEnum + extend Square::Internal::Types::Enum + + FOO = :FOO + BAR = :BAR + + finalize! + end + + it "coerces into a Symbol version of the member value" do + assert_equal :FOO, Utils.coerce(ExampleEnum, "FOO") + end + + it "returns given value if not a member" do + assert_equal "NOPE", Utils.coerce(ExampleEnum, "NOPE") + end + end + + describe "Array" do + StringArray = Square::Internal::Types::Array[String] + ModelArray = -> { Square::Internal::Types::Array[TestUtils::M] } + UnionArray = -> { Square::Internal::Types::Array[TestUtils::U] } + + it "coerces an array of literals" do + skip "TODO: Fix me!" + + assert_equal %w[a b c], Utils.coerce(StringArray, %w[a b c]) + assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, [1, 2.0, true]) + assert_equal ["1", "2.0", "true"], Utils.coerce(StringArray, Set.new([1, 2.0, true])) + end + + it "coerces an array of Models" do + assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], + Utils.coerce(ModelArray, [{ value: "foobar" }, { value: "bizbaz" }]) + + assert_equal [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")], + Utils.coerce(ModelArray, [TestUtils::M.new(value: "foobar"), TestUtils::M.new(value: "bizbaz")]) + end + + it "coerces an array of model unions" do + assert_equal [TestUtils::UnionMemberA.new(type: "A", only_on_a: "A"), TestUtils::UnionMemberB.new(type: "B", only_on_b: "B")], + Utils.coerce(UnionArray, [{ type: "A", only_on_a: "A" }, { type: "B", only_on_b: "B" }]) + end + + it "returns given value if not an array" do + skip "TODO: Fix me!" + + assert_equal 1, Utils.coerce(StringArray, 1) + end + end + + describe "Hash" do + it "coerces the keys and values" do + ssh_res = Utils.coerce(TestUtils::SymbolStringHash, { "foo" => "bar", "biz" => "2" }) + + assert_equal "bar", ssh_res[:foo] + assert_equal "2", ssh_res[:biz] + + smh_res = Utils.coerce(TestUtils::SymbolModelHash, { "foo" => { "value" => "foo" } }) + + assert_equal TestUtils::M.new(value: "foo"), smh_res[:foo] + end + end + end +end diff --git a/test/square/test_helper.rb b/test/square/test_helper.rb new file mode 100644 index 000000000..70b6da8ee --- /dev/null +++ b/test/square/test_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) +require "square" + +def test_token + @test_token ||= ENV.fetch("SQUARE_TOKEN", nil) || ENV.fetch("TEST_SQUARE_TOKEN", nil) || "test_token" +end + +def client + @client ||= Square::Client.new( + token: test_token + ) +end + +require "minitest/autorun" +require "minitest/rg" + + diff --git a/test/api/api_test_base.rb b/test/square_legacy/api/api_test_base.rb similarity index 100% rename from test/api/api_test_base.rb rename to test/square_legacy/api/api_test_base.rb diff --git a/test/api/test_catalog_api.rb b/test/square_legacy/api/test_catalog_api.rb similarity index 100% rename from test/api/test_catalog_api.rb rename to test/square_legacy/api/test_catalog_api.rb diff --git a/test/api/test_customers_api.rb b/test/square_legacy/api/test_customers_api.rb similarity index 100% rename from test/api/test_customers_api.rb rename to test/square_legacy/api/test_customers_api.rb diff --git a/test/api/test_employees_api.rb b/test/square_legacy/api/test_employees_api.rb similarity index 100% rename from test/api/test_employees_api.rb rename to test/square_legacy/api/test_employees_api.rb diff --git a/test/api/test_labor_api.rb b/test/square_legacy/api/test_labor_api.rb similarity index 100% rename from test/api/test_labor_api.rb rename to test/square_legacy/api/test_labor_api.rb diff --git a/test/api/test_locations_api.rb b/test/square_legacy/api/test_locations_api.rb similarity index 100% rename from test/api/test_locations_api.rb rename to test/square_legacy/api/test_locations_api.rb diff --git a/test/api/test_merchants_api.rb b/test/square_legacy/api/test_merchants_api.rb similarity index 100% rename from test/api/test_merchants_api.rb rename to test/square_legacy/api/test_merchants_api.rb diff --git a/test/api/test_payments_api.rb b/test/square_legacy/api/test_payments_api.rb similarity index 100% rename from test/api/test_payments_api.rb rename to test/square_legacy/api/test_payments_api.rb diff --git a/test/api/test_refunds_api.rb b/test/square_legacy/api/test_refunds_api.rb similarity index 100% rename from test/api/test_refunds_api.rb rename to test/square_legacy/api/test_refunds_api.rb diff --git a/test/http_response_catcher.rb b/test/square_legacy/http_response_catcher.rb similarity index 100% rename from test/http_response_catcher.rb rename to test/square_legacy/http_response_catcher.rb diff --git a/test/webhooks/test_webhooks_helper.rb b/test/square_legacy/webhooks/test_webhooks_helper.rb similarity index 100% rename from test/webhooks/test_webhooks_helper.rb rename to test/square_legacy/webhooks/test_webhooks_helper.rb From 6787cbd7cace24baf7a2fb2caa99cfcc4a932073 Mon Sep 17 00:00:00 2001 From: jsklan Date: Mon, 18 Aug 2025 12:04:09 -0400 Subject: [PATCH 2/2] pull in base catalog tests --- .../square/integration/catalog/test_client.rb | 77 +++++++++++++++++++ .../square/integration/catalog/test_object.rb | 42 ++++++++++ 2 files changed, 119 insertions(+) create mode 100644 test/square/integration/catalog/test_client.rb create mode 100644 test/square/integration/catalog/test_object.rb diff --git a/test/square/integration/catalog/test_client.rb b/test/square/integration/catalog/test_client.rb new file mode 100644 index 000000000..671692024 --- /dev/null +++ b/test/square/integration/catalog/test_client.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Catalog::Client do + describe "#batch_upsert" do + it "creates multiple catalog objects" do + skip "Skipping for now." + + response = client.catalog.batch_upsert( + request: { + idempotency_key: SecureRandom.uuid, + batches: [ + { + objects: [ + { + type: "ITEM", + id: "##{SecureRandom.uuid}", + present_at_all_locations: true, + item_data: { + name: "Coffee", + description: "Strong coffee", + abbreviation: "C", + variations: [ + { + type: "ITEM_VARIATION", + id: "##{SecureRandom.uuid}", + present_at_all_locations: true, + item_variation_data: { + name: "Kona Coffee", + track_inventory: false, + pricing_type: "FIXED_PRICING", + price_money: { + amount: 1000, + currency: "USD" + } + } + } + ] + } + }, + { + type: "ITEM", + id: "##{SecureRandom.uuid}", + present_at_all_locations: true, + item_data: { + name: "Tea", + description: "Strong tea", + abbreviation: "T", + variations: [ + { + type: "ITEM_VARIATION", + id: "##{SecureRandom.uuid}", + present_at_all_locations: true, + item_variation_data: { + name: "Gunpowder Green", + track_inventory: false, + pricing_type: "FIXED_PRICING", + price_money: { + amount: 2000, + currency: "USD" + } + } + } + ] + } + } + ] + } + ] + } + ) + + refute_nil response + end + end +end diff --git a/test/square/integration/catalog/test_object.rb b/test/square/integration/catalog/test_object.rb new file mode 100644 index 000000000..5a4288084 --- /dev/null +++ b/test/square/integration/catalog/test_object.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "test_helper" + +describe Square::Catalog::Object::Client do + describe "#upsert" do + it "upserts an object" do + skip "Skipping for now." + + response = client.catalog.object.upsert(request: { + idempotency_key: SecureRandom.uuid, + object: { + type: "ITEM", + id: "##{SecureRandom.uuid}", + present_at_all_locations: true, + item_data: { + name: "Coffee", + description: "Strong coffee", + abbreviation: "C", + variations: [ + { + type: "ITEM_VARIATION", + id: "##{SecureRandom.uuid}", + present_at_all_locations: true, + item_variation_data: { + name: "Kona Coffee", + track_inventory: false, + pricing_type: "FIXED_PRICING", + price_money: { + amount: 1000, + currency: "USD" + } + } + } + ] + } + } + }) + refute_nil response + end + end +end