feat(plans): menu watermark on Free (removed by paid feature)

Guest QR menu shows a "ساخته‌شده با میزی" watermark under the menu unless the café's
plan has the `watermark_removed` feature (Starter+).

- PublicMenuDto gains ShowWatermark; PublicService computes it from
  IsFeatureEnabledForCafeAsync("watermark_removed") for both slug and branch menus.
- Guest menu renders the watermark footer when showWatermark.
- NoOpPlatformCatalogService test double (all features on) for the PublicService
  ctor; QrMenuTests updated.

86 tests pass; dashboard tsc clean.
This commit is contained in:
soroush.asadi
2026-06-03 02:10:24 +03:30
parent 2487f9e30f
commit 7d06f149d3
6 changed files with 73 additions and 5 deletions
@@ -0,0 +1,44 @@
using Meezi.Core.Enums;
using Meezi.Core.Platform;
using Meezi.Infrastructure.Services.Platform;
namespace Meezi.API.Tests;
/// <summary>Test double: every feature enabled, unlimited limits. Keeps plan gating
/// out of the way for service-level tests.</summary>
internal sealed class NoOpPlatformCatalogService : IPlatformCatalogService
{
public Task<IReadOnlyList<PlanDefinitionDto>> GetPlansAsync(CancellationToken ct = default) =>
Task.FromResult<IReadOnlyList<PlanDefinitionDto>>([]);
public Task<PlanDefinitionDto?> GetPlanAsync(PlanTier tier, CancellationToken ct = default) =>
Task.FromResult<PlanDefinitionDto?>(null);
public Task<PlanLimitsData> GetLimitsAsync(PlanTier tier, CancellationToken ct = default) =>
Task.FromResult(new PlanLimitsData());
public Task<decimal> GetMonthlyPriceTomanAsync(PlanTier tier, CancellationToken ct = default) =>
Task.FromResult(0m);
public Task<bool> IsBillableOnlineAsync(PlanTier tier, CancellationToken ct = default) =>
Task.FromResult(false);
public Task<IReadOnlyList<PlatformSettingDto>> GetSettingsAsync(CancellationToken ct = default) =>
Task.FromResult<IReadOnlyList<PlatformSettingDto>>([]);
public Task<string?> GetSettingAsync(string key, CancellationToken ct = default) =>
Task.FromResult<string?>(null);
public Task<IReadOnlyList<PlatformFeatureDto>> GetFeaturesAsync(CancellationToken ct = default) =>
Task.FromResult<IReadOnlyList<PlatformFeatureDto>>([]);
public Task<IReadOnlyDictionary<string, bool>> GetEffectiveFeaturesForCafeAsync(
string cafeId, PlanTier planTier, CancellationToken ct = default) =>
Task.FromResult<IReadOnlyDictionary<string, bool>>(new Dictionary<string, bool>());
public Task<bool> IsFeatureEnabledForCafeAsync(
string cafeId, PlanTier planTier, string featureKey, CancellationToken ct = default) =>
Task.FromResult(true);
public void InvalidateCache() { }
}