Skip to content
← Tous les articles
· Delta1 Labs Licensing.NETGuide

Tester le code qui vit derrière une vérification de licence

Vos fonctionnalités payantes sont derrière une barrière de licence — mais comment les tester sans service d'émission en ligne, sans vraie clé, sans dépendre de l'horloge ? Voici une stratégie à deux niveaux pour Keyright : une fine couture que vous simulez pour des tests unitaires rapides, et une poignée de fixtures committées, signées avec une clé de test, qui exercent le vrai chemin de vérification du SDK, entièrement hors ligne et de façon déterministe en CI.

Quelque part dans votre app se trouve une branche qui dit, en substance : « si ce client est licencié, fais la chose qui a de la valeur ». Cette branche est toute la raison d’être d’un produit licencié — et c’est aussi celle que vos tests sautent le plus souvent, parce que l’exercer semble exiger un serveur de licences, une vraie clé et, pour les cas d’expiration, une machine à remonter le temps. Alors le bouton d’export, l’invite de limite de sièges, le panneau réservé à Enterprise restent sans test, et la première fois que quelqu’un exécute le chemin licencié de bout en bout, c’est devant un client payant.

Ce n’est pas une fatalité. Cet article expose une stratégie à deux niveaux pour tester du code protégé par Keyright : une fine couture que vous simulez pour la grande majorité de vos tests, et quelques fixtures committées, signées avec une clé de test, qui pilotent le vrai chemin de vérification du SDK — entièrement hors ligne, sans service dans la boucle et sans dépendance à l’horloge.

Pourquoi les approches naïves font mal

Deux réflexes finissent mal tous les deux. Le premier consiste à laisser les tests parler à votre vrai service d’émission : votre suite unitaire a désormais besoin du réseau, d’une clé valide et d’un siège qu’elle consomme à chaque exécution, et elle passe au rouge dès que le service est indisponible. Le second consiste à ne pas tester du tout le code protégé et à « le vérifier à la main » — c’est-à-dire jamais.

La solution, c’est de remarquer qu’il y a en réalité deux choses différentes que vous voulez tester, et qu’elles demandent des outils différents :

  • Ma fonctionnalité se comporte-t-elle correctement étant donné un résultat de licence ? (Le bouton d’export est-il masqué en Free ? L’invite de sièges se déclenche-t-elle à la limite ?) C’est le gros de vos tests, et il doit être pur, rapide et n’avoir rien à voir avec la cryptographie.
  • Le SDK résout-il une licence donnée vers le résultat que j’attends ? (Cette clé Pro débloque-t-elle vraiment ? Une clé altérée échoue-t-elle de façon fermée ?) C’est une poignée de tests, et ils doivent exercer le vrai code de vérification — simplement sans serveur.
Votre code métierdépend de ILicenseGateILicenseGateNiveau 1 — la plupart des testsFakeLicenseGate (sans crypto)injecte n'importe quelle édition / droitrapide · hors ligne · déterministeNiveau 2 — quelques testsvrai KeyrightClientfixtures committées à clé de testvraie vérification · sans serveur

Niveau 1 : une couture que vous pouvez simuler

Le SDK .NET de Keyright est volontairement petit — Validate(), IsEnabled, GetLimit — et il ne lève jamais d’exception, ce qui le rend déjà agréable à appeler. Mais vos view-models et vos gestionnaires de commandes ne devraient pas l’appeler directement, sinon chaque test de ceux-ci entraîne le vrai client avec lui. Placez devant lui une interface à une méthode par question :

public interface ILicenseGate
{
    bool IsPaid { get; }
    bool IsEnabled(string feature);
    long GetLimit(string feature, long fallback);
}

L’implémentation de production est une fine enveloppe autour de l’unique KeyrightClient que vous construisez au démarrage :

public sealed class KeyrightLicenseGate : ILicenseGate
{
    private readonly KeyrightClient _client;
    public KeyrightLicenseGate(KeyrightClient client) => _client = client;

    public bool IsPaid => _client.Validate().IsPaid;
    public bool IsEnabled(string feature) => _client.IsEnabled(feature);
    public long GetLimit(string feature, long fallback) => _client.GetLimit(feature, fallback);
}

Votre code métier prend un ILicenseGate, jamais un KeyrightClient :

public sealed class ExportViewModel
{
    private readonly ILicenseGate _license;
    public ExportViewModel(ILicenseGate license) => _license = license;

    public bool CanExport => _license.IsEnabled("export");
    public int  MaxRows   => (int)_license.GetLimit("max-rows", fallback: 100);
}

Le faux devient alors trivial — et, surtout, il peut exprimer n’importe quel état de licence instantanément, y compris ceux qui sont pénibles à produire pour de vrai :

public sealed class FakeLicenseGate : ILicenseGate
{
    private readonly Dictionary<string, string> _ent = new();
    public bool IsPaid { get; init; }

    public FakeLicenseGate With(string feature, string value) { _ent[feature] = value; return this; }

    public bool IsEnabled(string feature) => _ent.TryGetValue(feature, out var v) && v == "true";
    public long GetLimit(string feature, long fallback)
        => _ent.TryGetValue(feature, out var v) && long.TryParse(v, out var n) ? n : fallback;
}

Les tests se lisent comme des spécifications, s’exécutent en microsecondes et ne touchent aucun matériel de clé :

[Fact]
public void Export_is_hidden_on_the_free_edition()
{
    var vm = new ExportViewModel(new FakeLicenseGate { IsPaid = false });
    Assert.False(vm.CanExport);
}

[Fact]
public void Export_unlocks_when_the_entitlement_is_present()
{
    var gate = new FakeLicenseGate { IsPaid = true }.With("export", "true").With("max-rows", "50000");
    var vm = new ExportViewModel(gate);
    Assert.True(vm.CanExport);
    Assert.Equal(50000, vm.MaxRows);
}

C’est là que doit vivre l’écrasante majorité de vos tests liés à la licence. Notez que FakeLicenseGate reflète la convention de transport de Keyright lui-même — les valeurs de droits sont des chaînes, "true"/"false" pour les drapeaux et un entier (ou "unlimited") pour les limites — donc un faux qui renvoie "true" teste exactement ce que renvoie la vraie barrière.

Niveau 2 : une vraie vérification contre des fixtures committées

Le niveau 1 fait confiance à KeyrightLicenseGate pour mapper correctement le SDK. Un second niveau, bien plus petit, prouve que le SDK lui-même transforme une vraie licence en l’édition et les droits que vous attendez — et il peut le faire sans serveur, parce que Keyright est hors ligne d’abord : Validate() vérifie une signature contre une clé publique embarquée et n’a besoin d’aucun réseau.

L’astuce consiste à traiter les clés de licence comme des fixtures de test. Créez un produit de test jetable dédié (appelez-le acme-test, idéalement dans un espace de travail hors production), émettez quelques clés une seule fois — une clé Pro avec des droits connus, une clé Enterprise et une clé pour un produit différent — et committez les chaînes de clés avec la clé publique de ce produit de test. Rien de tout cela n’est sensible : la clé publique est déjà distribuée dans chaque binaire et ne peut que vérifier, et les fixtures sont des affirmations signées sans rapport avec votre clé de production ni avec un vrai client.

Passez une fixture directement à un vrai client via la source LicenseString — la source de plus haute priorité, elle l’emporte donc sans fichier ni cache en jeu :

public static class Fixtures
{
    // Public key of the throwaway "acme-test" product. NOT a secret — safe to commit.
    public const string TestPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...";

    public static LicenseInfo Resolve(string licenseString) =>
        KeyrightClient.Initialize(new KeyrightOptions
        {
            Product         = "acme-test",
            PublicKeyBase64 = TestPublicKey,
            LicenseString   = licenseString,   // feed the fixture directly: no file, no network, no cache
        }).Validate();
}

Le vrai chemin de vérification est maintenant sous test, et chaque cas est déterministe :

[Fact]
public void Pro_fixture_resolves_to_a_paid_edition_with_its_entitlements()
{
    LicenseInfo info = Fixtures.Resolve(File.ReadAllText("fixtures/pro.key"));

    Assert.True(info.IsPaid);
    Assert.True(info.Entitlements.IsEnabled("export"));
    Assert.Equal(10, info.Entitlements.GetLimit("max-projects", fallback: 1));
}

[Fact]
public void A_tampered_key_fails_closed_to_free_rather_than_throwing()
{
    // Flip one character to break the signature.
    string tampered = File.ReadAllText("fixtures/pro.key").Replace('A', 'B');

    LicenseInfo info = Fixtures.Resolve(tampered);

    Assert.False(info.IsPaid);                              // dropped to Free, not an exception
    Assert.Equal("SignatureInvalid", info.Status.ToString());
}

[Fact]
public void A_key_for_a_different_product_does_not_unlock_this_one()
{
    LicenseInfo info = Fixtures.Resolve(File.ReadAllText("fixtures/other-product.key"));
    Assert.False(info.IsPaid);
}

Le test de la clé altérée est celui qui justifie tout le niveau : il prouve que votre build échoue de façon fermée. Si une future montée de version du SDK ou une option mal réglée faisait un jour résoudre une mauvaise signature vers une édition payante, ce test passerait au rouge en CI bien avant la livraison. Vous pouvez tester le câblage de la priorité des sources de la même manière — déposez une fixture sur disque et définissez LicenseFilePath, ou pointez la variable d’environnement KEYRIGHT_LICENSE vers elle, pour prouver que votre amorçage lit la licence là où vous le pensez.

Ce qui reste volontairement hors de la suite rapide

Trois comportements sont réels, importants, et il serait faux de les forcer dans un test unitaire : l’expiration, la révocation et la détection de manipulation d’horloge. Ils dépendent du temps et du réseau — un lease réellement expiré, une révocation qui se propage au prochain rafraîchissement, une horloge reculée au-delà de la tolérance — et les simuler de façon convaincante au niveau du SDK revient à reproduire un état serveur que vous ne devriez pas simuler.

Testez-les aux deux niveaux qui conviennent. Au niveau 1, vérifiez la réaction de votre app quand la barrière rapporte ces états (injectez un faux dont IsPaid est false et dont le statut est Expired ou Revoked, et vérifiez que vous affichez la bonne invite « renouveler »). Et vérifiez les vraies transitions une fois, à la main ou dans un job scripté, contre votre tenant de test — la séquence exacte est la checklist de bout en bout du guide du SDK .NET : activer, couper le réseau et confirmer que le lease en cache valide toujours, puis révoquer et confirmer que le prochain rafraîchissement retombe en Free. Cela a sa place dans un job nocturne ou de pré-release, pas dans la suite qui garde chaque commit.

Le bénéfice

Cette séparation garde chaque test honnête sur ce qu’il vérifie vraiment. Le niveau 1 rend vos chemins de code licenciés aussi testables que n’importe quelle autre branche — rapides, déterministes et sans matériel de clé — de sorte que « masqué en Free, débloqué en Pro » cesse d’être quelque chose que vous vérifiez à la main. Le niveau 2 verrouille la seule chose que le niveau 1 suppose, à savoir que le SDK mappe une vraie licence signée vers le bon résultat et échoue de façon fermée sur une mauvaise, et il le fait avec rien d’autre que des fixtures committées et le vérificateur hors ligne. Câblez la barrière au démarrage comme dans le guide d’intégration du SDK .NET, lisez comment se comportent les leases et la validation hors ligne sur la page sécurité et leases, et le plan gratuit suffit largement pour frapper la poignée de fixtures sur lesquelles repose cette stratégie.

Essayez Nebula.NET

Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.