Tester l’encodage des paramètres URL iOS sur un Mac cloud

Tester l’encodage des paramètres URL iOS sur un Mac cloud

Tester l’encodage des paramètres URL iOS sur un Mac cloud

Vous saisissez C++ 入门 dans le champ de recherche, mais le serveur reçoit C 入门 ; vous envoyez deux filtres avec la même clé, mais un seul arrive à destination. Ces problèmes viennent généralement d’un désaccord entre l’encodage côté client et le décodage côté serveur, et non d’une perte de paquets réseau. Sur un Mac cloud, isoler le constructeur d’URL dans une fonction pure et verrouiller les cas limites avec XCTest permet de trouver la cause plus facilement qu’en inspectant sans cesse les requêtes de production, sans même lancer de simulateur.

Définir d’abord le contrat des paramètres

Une chaîne de requête n’est pas un dictionnaire. Le contrat de l’API doit préciser si une clé peut apparaître plusieurs fois, si l’ordre compte, si un paramètre absent, un paramètre sans valeur et une chaîne vide sont équivalents, et si le serveur applique les règles de décodage des formulaires. Le fait que le client et le serveur utilisent chacun leur « bibliothèque standard » ne garantit pas qu’ils répondent de la même manière à ces questions.

Ainsi, flag, flag= et l’absence complète de flag constituent trois entrées distinctes. Le client peut les représenter avec un URLQueryItem dont la valeur est nil, une chaîne vide, ou en n’ajoutant aucun élément. Pour savoir si le framework serveur préserve cette distinction, il faut le vérifier avec le parseur réellement utilisé.

Dans une requête URL ordinaire, un signe plus littéral n’équivaut pas par défaut à une espace. L’interprétation du signe plus comme une espace relève de l’encodage des formulaires ; elle ne doit pas être étendue à toutes les interfaces acceptant des paramètres de requête.

Faites apparaître ces conventions dans les noms des tests. Ne vous contentez pas d’un seul cas de « recherche normale réussie » : le texte chinois peut passer alors que les signes plus et les filtres à clés répétées restent mal traités.

Construire l’URL avec une fonction pure

Créez une cible de tests unitaires macOS et ajoutez le fichier Swift ci-dessous. L’adresse de l’exemple sert uniquement à une analyse locale ; aucune requête n’est envoyée. Lors de l’intégration, remplacez l’adresse fixe par la configuration d’API du projet. Les valeurs des paramètres doivent être fournies telles quelles, sans encodage en pourcentage manuel préalable.

import Foundation
import XCTest

func makeQueryURL(
    items: [URLQueryItem],
    formDecoder: Bool = false
) throws -> URL {
    var parts = URLComponents()
    parts.scheme = "https"
    parts.host = "xcodenode.com"
    parts.path = "/query-fixture"
    parts.queryItems = items

    if formDecoder, let encoded = parts.percentEncodedQuery {
        parts.percentEncodedQuery = encoded.replacingOccurrences(
            of: "+",
            with: "%2B"
        )
    }

    return try XCTUnwrap(parts.url)
}

final class QueryEncodingTests: XCTestCase {
    func testReservedCharactersRoundTrip() throws {
        let value = "C++ 入门 & x=1 # 50% / ?"
        let url = try makeQueryURL(items: [
            URLQueryItem(name: "q", value: value)
        ])
        let parts = try XCTUnwrap(
            URLComponents(url: url, resolvingAgainstBaseURL: false)
        )
        let items = try XCTUnwrap(parts.queryItems)
        XCTAssertEqual(items.count, 1)
        XCTAssertEqual(items.first?.value, value)
        XCTAssertNil(parts.fragment)
    }

    func testDuplicatesAndEmptyValues() throws {
        let url = try makeQueryURL(items: [
            URLQueryItem(name: "tag", value: "swift"),
            URLQueryItem(name: "tag", value: "ios"),
            URLQueryItem(name: "flag", value: nil),
            URLQueryItem(name: "empty", value: "")
        ])
        let parts = try XCTUnwrap(
            URLComponents(url: url, resolvingAgainstBaseURL: false)
        )
        let items = try XCTUnwrap(parts.queryItems)
        XCTAssertEqual(items.map(\.name), [
            "tag", "tag", "flag", "empty"
        ])
        XCTAssertEqual(items.map(\.value), [
            "swift", "ios", nil, ""
        ])
    }

    func testFormDecoderReceivesLiteralPlus() throws {
        let value = "C++ 50%"
        let url = try makeQueryURL(
            items: [URLQueryItem(name: "q", value: value)],
            formDecoder: true
        )
        let parts = try XCTUnwrap(
            URLComponents(url: url, resolvingAgainstBaseURL: false)
        )
        let query = try XCTUnwrap(parts.percentEncodedQuery)
        XCTAssertFalse(query.contains("+"))
        let pair = try XCTUnwrap(
            query.split(separator: "=", maxSplits: 1).last
        )
        let decoded = String(pair)
            .replacingOccurrences(of: "+", with: " ")
            .removingPercentEncoding
        XCTAssertEqual(decoded, value)
    }
}

Le premier test vérifie que les caractères réservés n’ont créé ni paramètres supplémentaires ni fragment. Le deuxième confirme que les clés répétées et leur ordre dans le tableau ont été conservés. Le troisième simule une passe de décodage de formulaire. Ce n’est pas un parseur serveur complet : il vérifie uniquement le traitement du signe plus dans une valeur et ne remplace pas les tests d’intégration de l’API.

Enrichir les cas d’échec plutôt que multiplier les appels réseau

Ajoutez au même jeu de tests du texte chinois, des séquences de caractères combinés, des emoji, des valeurs vides, des espaces en début et en fin de chaîne, ainsi que la chaîne littérale %2B. Ce dernier cas est particulièrement important : le signe pourcentage saisi par l’utilisateur doit être encodé, et non interprété comme un signe plus déjà encodé.

Si vous rencontrez %252B, vérifiez d’abord si l’entrée était un signe plus brut ou la chaîne littérale %2B. Dans le premier cas, il s’agit généralement d’un double encodage ; dans le second, le résultat peut être parfaitement correct. Déclarer une erreur à la seule vue de %25 risque d’introduire un nouveau bug lors de la correction.

N’encodez pas l’URL entière après assemblage à l’aide d’un jeu de caractères trop général. Les noms de paramètres, leurs valeurs et le chemin relèvent de contextes différents ; une concaténation directe de &, = ou # peut modifier la structure. Confiez cette structure au constructeur, puis vérifiez séparément le nombre, les noms et les valeurs des éléments obtenus après analyse.

Prévoyez une limite de longueur distincte pour les recherches très longues. Le nombre d’octets après encodage ne correspond pas au nombre de caractères. Testez la politique de limitation selon le contrat de l’API, sans supposer qu’un caractère chinois occupe un seul octet.

Vérifier aussi les octets des requêtes signées

Deux URL sémantiquement équivalentes n’ont pas nécessairement la même représentation en octets. L’ordre des paramètres, la représentation des espaces et la casse des séquences d’échappement en pourcentage peuvent tous influer sur la signature. Pour une requête ordinaire, les tests peuvent vérifier que les valeurs sont identiques après un aller-retour d’encodage et de décodage. Les tests de signature doivent aussi vérifier la chaîne canonique définie par le protocole.

Si le protocole impose un tri, précisez d’abord comment trier les clés répétées, puis signez la chaîne finale. S’il exige de conserver l’ordre, gardez le tableau d’éléments au lieu de le reconstruire à partir d’un dictionnaire. Une fois la signature calculée, ne changez plus le mode de traitement des signes plus : le contenu transmis ne correspondrait plus à l’entrée utilisée pour la signature.

Les cas de référence doivent inclure des clés répétées, des valeurs vides et du texte non ASCII, avec une assertion sur la chaîne encodée finale. Ici, « de référence » désigne le format convenu dans le projet, et non une sortie système ponctuelle érigée sans réserve en norme permanente. Le serveur doit lui aussi conserver les mêmes cas pour vérifier que les deux côtés utilisent exactement la même entrée de signature.

Exécuter les tests sur le nœud cloud et conserver les résultats

Foundation suffit pour la cible de tests ; il n’est pas nécessaire de configurer la signature d’une application. Lancez les tests via le Scheme partagé de cette cible. Remplacez les noms ci-dessous par ceux du projet ; le répertoire de résultats ne doit pas encore exister.

set -euo pipefail
mkdir -p artifacts
result="artifacts/query-$(uuidgen).xcresult"
xcodebuild test \
  -project QueryContract.xcodeproj \
  -scheme QueryContract \
  -destination 'platform=macOS' \
  -resultBundlePath "$result"

Avant l’exécution, consignez la version de Xcode, l’identifiant du commit et le mode du constructeur. Utilisez la même chaîne d’outils pour une même série de tests. Après une mise à niveau, revérifiez les octets utilisés pour la signature, pas seulement le nombre de tests au vert.

Pour valider le résultat, vérifiez quatre points : les valeurs brutes n’ont pas été préencodées ; les clés répétées n’ont pas été éliminées par un dictionnaire ; le choix du décodage de formulaire découle du contrat de l’API ; le parseur serveur réel réussit le même jeu de tests. Dans les rapports d’échec, il suffit de conserver la catégorie d’entrée et le résultat encodé. Si les données contiennent de vrais termes de recherche ou des jetons, remplacez-les par des exemples synthétiques.

Au final, les tests de la fonction pure protègent la structure côté client, tandis que les tests du contrat serveur protègent les limites du décodage. Il faut que les deux couches passent pour conclure que les paramètres sont correctement transmis, et non que deux analyses côté client ont simplement produit le même résultat par hasard.

Questions fréquentes

URLComponents encode-t-il toujours le signe plus en %2B ?

Non. Un plus peut rester littéral dans une requête URL, alors qu’un décodeur de formulaire le traite comme un espace. Si ce décodage fait partie du contrat, remplacez les plus littéraux de percentEncodedQuery par %2B après l’encodage, puis testez le décodeur serveur.

Peut-on convertir les paramètres en dictionnaire avant de signer ?

Seulement si le protocole interdit les clés répétées, ignore leur ordre et définit une normalisation précise. Sinon, conservez un tableau de URLQueryItem et testez séparément les octets finaux utilisés par la signature.

Louez un Mac dédié dans le cloud pour vos projets de développement

Des nœuds physiques Apple Silicon, pas des machines virtuelles. Louez à la journée, à la semaine, au mois ou au trimestre. Modèles et tarifs affichés en toute transparence ; disponibilité réelle consultable en temps réel dans la console.

Louer un Mac dans le cloud Voir les modèles et les durées de location