02 - Mock Mode
Mock mode is the backend's primary dev-safety feature. One flag in appsettings controls whether all external provider calls are real or mocked. Critical for local development without hitting production providers.
Global: MockMode.GlobalMockEnabled in appsettings.Development.json. Affects ~13 services simultaneously.
Per-provider: provider_configurations.mock_mode column in DB. Toggles a single provider live or mocked without touching appsettings.
Global Mock Mode Setup
backend/src/MO.Ekyc.Api/appsettings.Development.jsonJSON
{
"Logging": {
"LogLevel": { "Default": "Debug" }
},
"ConnectionStrings": {
"EkycDb": "Host=127.0.0.1;Port=5432;Database=ekyc3;Username=postgres;Password=root123;Search Path=ekyc"
},
"MockMode": {
"GlobalMockEnabled": true
},
"Payment": {
"Razorpay": { "ClientApiKey": "rzp_test_placeholder" }
},
"Sms": { "Mode": "FIXED_OTP", "FixedOtp": "1234" },
"Email": { "Mode": "FIXED_OTP", "FixedOtp": "1234" }
}
How Services Read the Flag
Every service that interacts with an external provider reads MockMode:GlobalMockEnabled in its constructor and branches accordingly.
backend/src/MO.Ekyc.Infrastructure/Services/Common/BankAutoVerifyService.csC#
public class BankAutoVerifyService
{
private readonly bool _globalMockEnabled;
public BankAutoVerifyService(..., IConfiguration config, ...)
{
_globalMockEnabled = config.GetValue("MockMode:GlobalMockEnabled", false);
}
public async Task TriggerAutoVerifyAsync(Guid leadId, string mobileNumber, ...)
{
if (_globalMockEnabled)
{
_logger.LogInformation("Lead {LeadId}: Bank auto-verify MOCKED", leadId);
// Persist mock record with fake HDFC active savings data
return;
}
// ... real Decentro call ...
}
}
Services Reading GlobalMockEnabled
| Stage | Service | Mocked behavior |
|---|---|---|
| 2 (background) | BankAutoVerifyService | Decentro returns mock HDFC active savings account |
| 2 (background) | BackgroundCheckService | Zintlr and CSafe skipped |
| 0 | FraudCheckService | IP velocity, device fingerprint always pass |
| 4 | PanVerificationService | Returns mock success with fake name |
| 5 | AadhaarVerificationService | Returns mock redirect URL containing example.com |
| 6 | BankVerificationService | RPD returns mock redirect URL |
| 7 | LivenessService | Skips camera/SDK, returns success with mock score |
| 8 | SignatureService | Accepts any non-empty signature |
| 12 | DocumentGenerationService | Returns mock PDF URL instead of iText7 generation |
| 13 | EsignService | Returns mock eSign redirect URL |
| 14 | AccountCreationService | Skips CBOS call, returns mock clientId |
| 15 | FundTransferService | Returns test Razorpay key + mock redirect URL |
Per-Provider Mock Mode
Beyond the global flag, each external provider has its own mock_mode column in provider_configurations. The ProviderChainExecutor<T> reads this per request - no restart needed.
SQLView and toggle per-provider mock
-- View all providers and their mock state
SELECT provider_name, category, priority, enabled, mock_mode
FROM ekyc.provider_configurations
ORDER BY category, priority;
-- Force HyperVerge liveness live while everything else stays mock
UPDATE ekyc.provider_configurations
SET mock_mode = false
WHERE provider_name = 'HypervergeLiveness';
backend/src/MO.Ekyc.Infrastructure/ExternalProviders/Common/ProviderChainExecutor.csC#mock check
foreach (var config in enabledProviders)
{
if (config.MockMode)
{
_logger.LogInformation("Provider {Name} is in MOCK mode, skipping real call", config.ProviderName);
await _auditLogger.LogAsync(..., isMock: true);
return default!;
}
try
{
return await operation(provider);
}
catch (Exception ex) { ... }
}
SMS & Email Fixed OTP
OTP providers are separately controlled by Sms.Mode and Email.Mode. In FIXED_OTP mode, the backend stores and accepts 1234 for all OTP verifications.
backend/src/MO.Ekyc.Infrastructure/ExternalProviders/NetcoreSmsProvider.csC#mode check
var mode = _configuration["Sms:Mode"] ?? "REAL";
if (mode == "FIXED_OTP")
{
var fixedOtp = _configuration["Sms:FixedOtp"] ?? "1234";
_logger.LogInformation("SMS MOCKED - returning fixed OTP {FixedOtp}", fixedOtp);
return new SmsResult { Success = true, Otp = fixedOtp };
}
// ... real Netcore call ...
Troubleshooting
OTP 1234 is rejected
- Check
Sms.Modeinappsettings.Development.json- must be"FIXED_OTP" - Restart the backend after any appsettings change
- Verify you're running in Development environment:
dotnet run(uses Development by default) - Check logs for "SMS MOCKED" message - if absent, mock mode is off
Real HTTP call happens in mock mode
- A service may have been added that doesn't read
GlobalMockEnabled- search for the service file and add the check - Individual provider still has
mock_mode = falsein DB - checkprovider_configurationstable - Confirm with:
SELECT provider_name, mock_mode FROM ekyc.provider_configurations WHERE enabled = true;
Frontend shows real WebView in mock mode
The Flutter app detects mock URLs (example.com, mock-digilocker) and bypasses WebViews. If this isn't working:
- Check the backend's mock redirect URLs actually contain one of those strings
- See the frontend Mock Mode page for the client-side detection logic