Skip to content

RUF102 fix causes syntax error by not handling text following directive #23349

@chasefinch

Description

@chasefinch

Fixing a rule creates a syntax error as of Ruff 0.15 (still an issue in 0.15.1)

Rule code RUF102

ruff.toml:

line-length = 99
target-version = "py311"
output-format = "grouped"
extend-exclude = [".uv-cache"]

[format]
quote-style = "single"

[lint]
allowed-confusables = ["‘", "’", "×", "–"]
select = ["ALL"]
ignore = [
	# General configuration (see https://www.chasefinch.com/nitpick-style.toml)
	"D203", "D213", "TID252", "S101", "INT001",  # Rule conflicts
	"COM812", "ISC001", "Q000", "Q003",  # Recommended when using formatter
	"RUF100",  # Conflicts with WPS
	"ARG",  # Unused arguments often required in heirarchy
	"ERA",  # Commented-out code is OK
	"S105", "S106",  # Don't sniff for secrets
	"S311",  # Random is OK. Don't use them for "cryptographic purpses"
	"S308",  # Mark safe is OK
	"S610",  # Django "extra" is OK
	"E731", "B023",  # Lambda & unbound lambda is OK
	"B024",  # Allow ABCs without abstract methods
	"BLE001",  # Allow catching bare exception explicitly
	"FIX002", "TD003",  # Allow fully-formed TODOs

	# Temporary exclusions
	"G", "EM", "TRY",  # Log formatting
	"PT",  # Test conventions (Use a regular `assert` instead of unittest-style `assertIn`)
	"RUF012",  # Typing convention (Mutable class attributes should be annotated with `typing.ClassVar`)
	"DTZ",  # Timezone enforcement ignored while we use naive datetimes
	"FLY002",  # String joins avoided
	"PERF401",  # Prefer list joins
	"ANN",  # No type annotation stuff yet
	"D", "DOC",  # No docstring requirements stuff yet
	"S108", "S113", "S202", "S301", "S603", "S607", "S611", "SIM115",  # Security notices
	"PLW2901",  # For loop target assignment
	"PLC0415",  # Inline imports, awaiting cleanup

	# BringFido-specific opt-outs
	"SLF",  # Private member access
	"PTH",  # Path conventions
	"CPY",  # Copyright text
	"C", "PLR09",  # Complexity rules
	"PLR2004"  # Magic values
]

[lint.isort]
combine-as-imports = true
detect-same-package = true
section-order = [
	"future",
	"standard-library",
	"third-party",
	"django",
	"first-party",
	"local-folder"
]
known-first-party = ["google_"]
known-third-party = ["google"]
order-by-type = true

[lint.isort.sections]
django = ["django"]

[lint.per-file-ignores]
# General configuration
	# Accept imports, strings & magic numbers generated by Django. Allow Django-style migration naming. Also ignore errors (DJ01) that are enforced at the model definition.
	"*/migrations/*" = ["RUF012", "E501", "DJ01"]
# Temporary exclusions
	# Allow __all__ until we can reorganize things
	"*/admin.py" = ["DJ007"]
	"*/forms.py" = ["DJ007"]
# BringFido-specific opt-outs
	# Allow print in scripts & secrets, and secrets' module name
	"scripts/*" = ["T201"]
	".claude/*" = ["T201"]
	"utilities/secrets.py" = ["A005", "T201"]
	# Allow common module names in `common` and `utilities`
	"common/*" = ["A005"]
	"utilities/*" = ["A005"]

Offending code:

from decimal import Decimal
from http import HTTPStatus
from unittest import mock

import responses

from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase

from ..api import TEST_CARD_NUMBER, TEST_CARD_SECURITY_CODE, TEST_POSTAL_CODE
from ..create_sale import CreateSaleRequest, TransactionError
from ..models import Transaction


class CreateSaleRequestTest(TestCase):
    is_live_test = True
    maxDiff = 3000  # noqa: WPS115 (overriding config property)

    static_kwargs = {
        'first_name': 'John',
        'last_name': 'Doe',
        'email': 'john.doe@example.com',
        'phone': '(864) 555-1234',
        'payment_security_fields': {'security_code': TEST_CARD_SECURITY_CODE},
    }

    dummy_connexpay_data = {'test': 'data'}

    def make_mock_card(self):
        card = mock.Mock()
        card.name = 'John Doe'
        card.number = TEST_CARD_NUMBER
        card.expiry_month = '04'
        card.expiry_year = 2050
        card.address_1 = '101 Mulberry Ln.'
        card.address_2 = ''
        card.city = 'Whoville'
        card.state = None
        card.country = 'US'
        card.country_iso2 = 'US'
        card.postal_code = TEST_POSTAL_CODE

        card.make_connexpay_data = mock.Mock(return_value=self.dummy_connexpay_data)

        return card

    def setUp(self):
        transaction = mock.Mock(spec=Transaction)
        transaction.number = 'A1B2C3D4E5F6'
        transaction.customer_id = 41296
        transaction.code = None
        transaction.amount = Decimal('250.00')

        self.transaction = transaction

        kwargs = {
            'transaction': self.transaction,
            'payment_method': self.make_mock_card(),
        }

        kwargs.update(self.static_kwargs)

        self.example_request = CreateSaleRequest(**kwargs)

    def test_init(self):
        card = self.make_mock_card()
        kwargs = {
            'transaction': self.transaction,
            'payment_method': card,
        }
        kwargs.update(self.static_kwargs)

        example = CreateSaleRequest(**kwargs)

        expected_body = {
            'Amount': Decimal('250.00'),
            'Card': self.dummy_connexpay_data,
            'CustomerID': 41296,
            'ConnexPayTransaction': {'ExpectedPayments': 1},
            'DeviceGuid': 'f4e178ae-c66c-4ed8-ba0d-6be1ee0121ac',
            'OrderNumber': 'A1B2C3D4E5F6',
            'ProductType': 'BringFido',
            'RiskData': {
                'ProductType': 'Hotel',
                'BillingAddress1': '101 Mulberry Ln.',
                'BillingAddress2': '',
                'BillingCity': 'Whoville',
                'BillingCountryCode': 'US',
                'BillingPhoneNumber': '(864) 555-1234',
                'BillingPostalCode': TEST_POSTAL_CODE,
                'Email': 'john.doe@example.com',
                'Name': 'John Doe',
                'OrderNumber': 'A1B2C3D4E5F6',
            },
            'RiskProcessingOnly': False,
            'SendReceipt': False,
            'StatementDescription': 'BringFido',
        }

        self.assertEqual(expected_body, example.body)

        card.state = 'SC'
        example = CreateSaleRequest(**kwargs)
        self.assertEqual(example.body['RiskData']['BillingState'], card.state)

        # Assert error with processed transaction
        self.transaction.code = 'dummy'
        with self.assertRaises(ImproperlyConfigured):
            example = CreateSaleRequest(**kwargs)

    @responses.activate(registry=responses.registries.OrderedRegistry)
    @mock.patch('connexpay.api.Server.get_token')
    def test_query(self, get_token):
        # Set up mock data
        dummy_token_string = 'qwerty-zxcvbn'
        get_token.return_value = dummy_token_string

        responses.add(
            responses.POST,
            self.example_request.url,
            json={
                'status': 'Transaction - Approved',
                'test_key': 'test_response',
                'guid': '1234-abcd',
                'connexPayTransaction': {
                    'incomingTransCode': 'qwerty123',
                },
            },
            status=HTTPStatus.OK,
        )

        # Assert error before registering a stay
        with self.assertRaises(ImproperlyConfigured):
            self.example_request.query()

        # Assert success cases

        test_hotel_id = 10101
        self.example_request.register_stay(num_nights=3, hotel_id=test_hotel_id)
        self.example_request.query()

        # Assert HTTP error handling
        responses.add(
            responses.POST,
            self.example_request.url,
            json={'message': 'dummy'},
            status=HTTPStatus.UNPROCESSABLE_ENTITY,
        )

        with self.assertRaises(ImproperlyConfigured):
            self.example_request.query()

        responses.add(
            responses.POST,
            self.example_request.url,
            json={'status': 'Transaction - Declined'},
            status=HTTPStatus.CREATED,
        )

        with self.assertRaises(TransactionError):
            self.example_request.query()

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingfixesRelated to suggested fixes for violations

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions