# Test proxy credential encoding offline

Prepared with AI assistance for Portproof, which sells proxy traffic and mobile-port rentals. Every credential in this article is a fixed synthetic fixture. The checks run offline; they do not authenticate to a gateway or measure a service.

A configuration migration can change a password without showing an obvious typo. One client accepts separate authentication fields; another expects credentials inside a URL. Reusing the same string in both places can turn an encoded component into the literal password sent by the second client.

Test that representation boundary before involving a network. The useful question is whether the original credential survives serialization and parsing, rather than whether a permissive parser accepts the URL.

## Choose one input contract

The helper below accepts **original text values**. It does not accept a partly encoded component or a completed proxy URL. A literal `%40` in an original password is three characters, so its percent sign must be encoded. It is not permission to substitute an `@`.

Python's [URL quoting documentation](https://docs.python.org/3/library/urllib.parse.html#url-quoting) distinguishes component quoting from form-query quoting. Here, `quote` uses an empty safe set and UTF-8. Slashes are encoded, spaces become `%20`, and a plus sign stays distinguishable from a space. The test reads the serialized components back and explicitly decodes them for comparison with the inputs.

The example fixes the gateway to `proxy.example.invalid:8080` so the test cannot accidentally use a working service. It rejects control characters, non-text values and a missing username as its own input policy. Leading and trailing spaces remain data. This policy is illustrative: a real credential issuer and client may have different supported inputs.

| Fixture | Boundary being tested | Expected result |
| --- | --- | --- |
| Plain ASCII | Baseline representation | Exact round trip |
| Separators in both fields | URI punctuation stays credential data | Exact round trip |
| Literal `%40` | Existing percent text is not decoded first | `%2540` in the component |
| Space and plus | Form encoding is not substituted | `%20` and `%2B` stay distinct |
| Non-ASCII text | Encoding has an explicit byte basis | UTF-8 round trip |
| Leading/trailing space | Copy handling does not silently trim | Space preserved |
| Newline | Control input is rejected by this helper | Defined error |
| Missing username | Incomplete input is rejected | Defined error |
| Non-text password | No implicit conversion of `None` | Defined error |
| Double-encoding control | Encoding an encoded component changes the value | Mismatch detected |

## Run the complete fixture

Save this as `fixture.py` and run `python3 fixture.py`. It uses Python 3.9 or newer and the standard library. The output contains case labels and pass states, not assembled credential-bearing URLs. Assertions intentionally fail the process if a case changes.

```python
"""Offline credential serialization fixtures. All values are synthetic."""
from urllib.parse import quote, unquote, urlsplit
import json
import platform

HOST = "proxy.example.invalid"
PORT = 8080


def proxy_url(raw_user, raw_password):
    for value in (raw_user, raw_password):
        if not isinstance(value, str):
            raise ValueError("credential_not_text")
        if any(ord(char) < 32 or ord(char) == 127 for char in value):
            raise ValueError("credential_contains_control")
    if not raw_user:
        raise ValueError("username_missing")
    user = quote(raw_user, safe="", encoding="utf-8", errors="strict")
    password = quote(raw_password, safe="", encoding="utf-8", errors="strict")
    return f"http://{user}:{password}@{HOST}:{PORT}"


def check_case(label, raw_user, raw_password, expected_password):
    assembled = proxy_url(raw_user, raw_password)
    parts = urlsplit(assembled)
    assert parts.scheme == "http", label
    assert parts.hostname == HOST and parts.port == PORT, label
    assert unquote(parts.username, errors="strict") == raw_user, label
    assert unquote(parts.password, errors="strict") == raw_password, label
    encoded_password = assembled.split("@", 1)[0].split(":", 2)[2]
    assert encoded_password == expected_password, label
    return {"case": label, "result": "pass"}


def expect_rejected(label, user, password, expected_error):
    try:
        proxy_url(user, password)
    except ValueError as error:
        assert str(error) == expected_error, label
        return {"case": label, "result": "pass"}
    raise AssertionError(label)


def main():
    accepted = [
        ("ascii", "demo", "plain", "plain"),
        ("separators", "user:name", "p@ss/#:?", "p%40ss%2F%23%3A%3F"),
        ("literal_percent", "demo", "literal%40", "literal%2540"),
        ("space_and_plus", "demo", "two words+", "two%20words%2B"),
        ("unicode", "d\u00e9mo", "caf\u00e9", "caf%C3%A9"),
        ("preserved_whitespace", "demo", " pass ", "%20pass%20"),
    ]
    results = [check_case(*case) for case in accepted]
    results += [
        expect_rejected("newline", "demo", "line\nbreak", "credential_contains_control"),
        expect_rejected("missing_user", "", "demo", "username_missing"),
        expect_rejected("non_text", "demo", None, "credential_not_text"),
    ]
    # A negative control proves why re-encoding an assembled component is wrong.
    once = quote("p@ss", safe="")
    twice = quote(once, safe="")
    assert unquote(twice) != "p@ss"
    results.append({"case": "double_encoding_detected", "result": "pass"})
    print(json.dumps({
        "python": platform.python_version(),
        "scope": "offline synthetic serialization only; no network request",
        "checks": results,
        "passed": len(results),
    }, indent=2))


if __name__ == "__main__":
    main()

```

## What the passing result establishes

The recorded run on 30 October 2026 passed ten checks. Six accepted inputs returned their original username and password after one serialization/parse/decode cycle. Three rejected inputs produced the expected error. The negative control demonstrated that encoding the already encoded `p%40ss` again does not preserve the original `p@ss` after one decode.

This is deliberately narrower than an integration test. `urlsplit` separates fields; it does not prove a particular application supports embedded proxy authentication. [curl documents URL-decoding credentials in a proxy string](https://curl.se/docs/manpage.html#--proxy), while [Playwright documents separate proxy username and password fields](https://playwright.dev/docs/network#http-proxy). Use each client's contract and test the actual configured client separately with a bounded authorised request.

Keep production inputs separate from the fixed test vector list. Do not adapt this fixture into a command that prints real URLs, commits credentials or uploads them to a conversion website. RFC3986's [userinfo discussion](https://www.rfc-editor.org/rfc/rfc3986) also explains why sensitive credential-bearing URIs need care when displayed or stored.

For the gateway/protocol decision and a comparison of raw fields with URI components, use Portproof's [proxy URL format guide](https://portproof.org/guides/proxy-url-format). The [proxy traffic catalogue](https://portproof.org/proxies) describes the actual product; this fixture neither buys traffic nor establishes gateway capacity. References were checked on 30 October 2026.

