# Technical documentation — neoafrica_qa

Evidence date: 2026-10-09.

Source: `git@bitbucket.org:overdrive/neoafrica_qa.git`, `main` at `3f8d683` (2026-06-19).

Credential values are omitted. Config keys are listed so a reader knows what must be set.

## Architecture

Legitimate Licences is a .NET Framework 4.6.2 system in `LegitimateLicences_QA/`. The staff site is ASP.NET MVC 5. Module projects are class libraries referenced by `Neo.LegitimateLicences.Web`. Workflow, documents, and security come from vendored `Neo.Afx.*` projects. SQL Server holds the registers and the workflow state. A Topshelf Windows service runs database-defined jobs. A desktop executable prints and scans through a Web API.

See the diagram in `project-plan.md`.

## Repository layout

```text
neoafrica_qa/
  bitbucket-pipelines.yml          SonarQube on main
  sonar-project.properties         project key only
  checklist.csv                    setup checklist (contains secrets; not copied here)
  .gitignore
  LegitimateLicences_QA/
    README.md                      points at docs/EhailingProviders.md
    Environment_Readiness_Checklist.md
    docs/EhailingProviders.md
    QA/TestPlan_EhailingProviders/
    azure-pipelines.yml
    NeoAfricaDevelopmentWeb.yaml
    NeoAfricaDevelopmentApi.yaml
    NeoAfricaDevelopmentCdn.yaml
    RefreshAssembliesFromNeoAfxMaster.bat
    *.sln                          four solutions (see below)
    Neo.LegitimateLicences.*       product modules
    Neo.Afx.*                      platform libraries
    Assemblies/                    binary drop area
    Deploy/NeoAfxSharedAssemblies/
    WIndowServiceInstall/          spelling as in the tree; install assets
```

Not in git: `repos/`, `sonarqube_scan.bat`. The task-creation scan listed both.

## Solutions and frameworks

| Solution | Role |
|---|---|
| `Neo.LegitimateLicences.Web.sln` | Staff web app and module projects |
| `Neo.LegitimateLicences.Api.sln` | Web API |
| `Neo.LegitimateLicences.WinService.sln` | Windows service |
| `Neo.LegitimateLicences.ActiveX.sln` | ActiveX print stack |
| `Neo.LegitimateLicences.Desktop/Neo.LegitimateLicences.Desktop.sln` | Desktop client (nested) |

`Neo.LegitimateLicences.Web.csproj` targets `v4.6.2`.

NuGet (web `packages.config`, versions as pinned):

| Package | Version |
|---|---|
| Microsoft.AspNet.Mvc | 5.2.7 |
| EntityFramework | 6.2.0 |
| dbup / dbup-sqlserver | 5.0.8 |
| Microsoft.AspNet.SignalR | 2.2.1 |
| Sentry / Sentry.AspNet / Sentry.EntityFramework | 4.6.0 |
| Microsoft.Owin | 2.1.0 |
| Microsoft.Azure.Services.AppAuthentication | 1.6.2 |
| Microsoft.IdentityModel.Clients.ActiveDirectory | 5.2.9 |

`Web.config` also loads DevExpress Web 19.1 handlers (`DXXRD.axd`, `DXXRDV.axd`, `DXQB.axd`) and `DPUruNet` is imported by the API and the sign-in controller for fingerprints.

UI scripts in the CDN project include jQuery 3.3.1, jQuery UI 1.12, SweetAlert, Summernote, and Magnific Popup. Workflow screens use Knockout-style `data-bind` attributes. That is **Implemented** in the views. A single front-end framework version for Knockout was not pinned in this note.

## Web application

- Entry: `Global.asax.cs` registers routes, initialises Sentry, then calls `DatabaseUpdator.UpdateDB()` inside a try/catch that only `Debug.WriteLine`s failures.
- Sentry: `AddAspNet`, `AddEntityFramework`, DSN from `SentryDsn`, `Debug = true`, `TracesSampleRate = 1.0`.
- Controllers under `Controllers/` are thin (home, profile, reports, errors, auth, shared AFX actions). Feature controllers live in the module projects.
- Views are copied or linked under `Neo.LegitimateLicences.Web/Views/` by module name (Applications, GazetteConsole, ProvidersRegistry, and the rest listed in `features.md`).
- `Startup.cs` exists beside `Global.asax`. OWIN packages are referenced. This pass did not trace which middleware `Startup` registers.
- SignalR hub: `Hubs/MessageHub.cs`.

### App setting keys (web)

`webpages:Version`, `PreserveLoginUrl`, `ClientValidationEnabled`, `UnobtrusiveJavaScriptEnabled`, `aspnet:MaxJsonDeserializerMembers`, `WebsupergooPdfKey_DisabledOnTest`, `SystemUsername`, `vs:EnableBrowserLink`, `BaseUrl`, `SentryDsn`, `eNatisLoginURL`, `eNatisQueryURL`, `eNatisCertificatePath`, `eNatisCertificatePassword`, `eNatisUsername`, `eNatisPassword`, `eNatisOpModInd`, `eNatisESTxanID`, `eNatisESUserN`, `eNatisESUID`, `eNatisEStermID`, `eNatisTxanType`, `AfxServicesPath`, `AfxCdnPath`, `FingerprintAuthenticationEnabled`, `ExternalRouteIgnores`.

`WebsupergooPdfKey_DisabledOnTest` implies a PDF component that can be disabled in test. The key name is the evidence. The product behaviour of that switch was not traced.

### Connection string names (web)

`ConnectionString` (also the DbUp target), `AuditContext`, `WorkflowContext`, `LegitimateEntities`, `Integration`, `ApplicationContext`, `LicenceIssueContext`, `DocumentContext`, `ReportContext`, `Report_Connection`, `AccreditationIssueContext`, `AfxAdminSecurityContext`, `CommentContext`, `CorrespondenceContext`, `MDApprovalRequestContext`, `ConfigContext`, `FormContext`, `IntegrationContext`, `QueryContext`, `SecurityContext`, `NotificationContext`, `NotificationsContext`, `PerformanceContext`, `QueriesContext`, `NewRouteContext`, `NewMemberContext`, `NewAssociationContext`, `NewVehicleContext`, `LtpsEntities`, `LicenceExtensionContext`, plus `AfxAdmin*` contexts for CRM, documents, delegation, forms, integration, reports, security, services, workflow, audit, and system.

Several names appear twice in `Web.config` (`AuditContext`, `AfxAdminSecurityContext`). That is how the file reads. Whether both entries are active **Requires confirmation**.

## Authentication

```xml
authentication mode="Forms"
forms loginUrl="~/Auth/Login" defaultUrl="/" slidingExpiration="true" timeout="30"
authorization: deny users="?"
```

Membership provider: `Neo.Afx.Security.DomainMembershipProvider` in `Neo.Afx.Core`. `passwordFormat="Clear"`. `requiresQuestionAndAnswer="true"`. `requiresUniqueEmail="true"`. `enablePasswordRetrieval="false"`. `enablePasswordReset="true"`.

Role cookie name: `.NeoLegitimate_ASPROLES`.

API: `LegitimateLicencesApiController` reads `ClaimsIdentity` claim type `UserID` and email. `[EnableCors(origins: "*", headers: "*", methods: "*")]`.

## API surface

| Method | File | Purpose |
|---|---|---|
| `Ping` | `LegitimateLicencesApiController.cs` | Health-style method returning `JsonBaseResult` |
| `GetPrintoutData(verificationToken)` | `LegitimateLicencesApiController.Printing.cs` | Print payload, including `ProviderCertificateData` |
| `GetScanningData(scanToken)` | `LegitimateLicencesApiController.Scanning.cs` | Scan session data |
| `UploadScannedDocument` | same | Body is a `JObject` |
| `GetVerifiedLicenceToUseXml(olNumber)` | `LegitimateLicencesApiController.VerifiedData.cs` | Verified operating licence XML |
| `GetImportedLicenceToUseXml(olpermitNumber, sourceApplicationID)` | same | Imported licence XML |
| `LTPSApplicationXmlSearch(permitNumber)` | same | LTPS search |
| `GetApplications(...)` | same | Search by operating licence number, id number, registration, chassis, division type ids |

`BlobDownload.ashx` is a separate handler in the API project.

Desktop `Common.cs` sets an HTTP client timeout of 5 minutes.

## Windows service

`Program.Main` calls `ConfigureService.Configure()`. Topshelf hosts `DatabaseServiceScheduler`:

- Service name and display name: `DatabaseServiceScheduler`.
- Description string in code: `My .Net windows service with Topshelf`.
- Account: `RunAsLocalSystem()`.
- `Start` loads services from `ServiceDatabase.GetServices()`, builds a `ServiceTask` per row, and starts `ServiceTaskScheduler`. A single service that fails to construct is audited and skipped.
- `SmsVodazone` implements `ISmsSender` using `Interop.Clickatell`. It authenticates in the constructor.

The checklist's install bat lives under `WIndowServiceInstall/` (capital I in WIndow). Two different install directories are written in the two checklists. See business rules.

## Database

| Item | Evidence |
|---|---|
| Engine | SQL Server. Scripts use `USE [LegitimateNational]` |
| Second database | Checklist: `LegitimateKZN_LTPS`. Config name: `LtpsEntities` |
| ORM | Entity Framework 6.2.0. EDMX files exist (for example `SystemTriggerModel.edmx`) |
| Migrations | DbUp embedded SQL, not EF migrations |
| CLR | Checklist requires `clr enabled`, TRUSTWORTHY, and assemblies `Neo.Afx.SqlServer`, `System.Core`, `System.Xml.Linq` |
| Owner and user | Checklist says owner `sa` and user `Legitimate`. Password omitted |

### DbUp behaviour

`DatabaseUpdator.UpdateDB`:

1. Reads `ConnectionString`.
2. `EnsureDatabase.For.SqlDatabase`.
3. Deploys embedded scripts from the executing assembly whose name contains `Legitimate`.
4. Calls `PerformUpgrade` and does not branch on `result.Successful`.

Scripts are listed as `<EmbeddedResource Include="DBUp\Scripts\...">` in the web csproj (the 2024 block starts at those includes). A file on disk that is not an embedded resource will not run. This pass did not diff the full SQL tree against the csproj include list.

Script areas:

| Folder | Contents (representative) |
|---|---|
| `DBUp/Scripts/2024` | Referrals, pre-check, views, receipt, office location, report definitions |
| `DBUp/Scripts/2025` | E-hailing provider chain `0923`–`1150`, vehicle particulars, conversion module, accredited operators, verification questions |
| `DBUp/Scripts/2026` | Scholar gazetting, inspection SMS, gazette period, payment correction return, step-check fixes, vehicle cleanse, eNatis description columns |
| `DBUp/Scripts/Migration` | OLAS steps 1–7 (seed, master data, routes, licences, associations, DOT users, accreditations) |

`EnsureDatabase` will create an empty database when the connection's database is missing. Combined with startup execution, a wrong connection string can create a database. That is **Implemented** library behaviour of the call that is in `DatabaseUpdator`.

## External systems

### eNatis

Settings listed above. Checklist documents production URLs on `ifprod.enatis.co.za` port 4443, paths `/eNaTIS_PROD/secure/X0000` (login) and `X1001` (query), a PFX file path, and operation mode `LIVE`. Secrets in that checklist are not copied.

### Clickatell

`SmsVodazone` takes API id, username, and password, calls authenticate, and expects a reply prefix `OK:`.

### Sentry

DSN is configuration. Sample rate in code is 100% of traces, with debug logging on.

### Azure AD libraries

`Microsoft.Azure.Services.AppAuthentication` and `ActiveDirectory` client packages are referenced. No application setting in the extracted key list is an Azure AD tenant. **Unknown** whether those packages are used on a live path or are leftover references.

## Realtime

`MessageHub` (SignalR 2.2.1) pushes verification-queue changes to `Clients.All`. There is no group or office filter in that class.

## Build, CI, and deployment

### Bitbucket Pipelines

`bitbucket-pipelines.yml` defines one step, SonarQube pipe `sonarsource/sonarqube-scan:3.0.2`, on branch `main` only. Clone depth is `full`. Variables: `SONAR_HOST_URL`, `SONAR_TOKEN`. Cache: `~/.sonar`.

`sonar-project.properties` sets only:

```text
sonar.projectKey=overdrive_neoafrica_qa_21f5270d-7822-4e4b-85bd-cfbc07367f3b
```

No build, test, or deploy step is in that pipeline.

### Azure DevOps YAML

`azure-pipelines.yml` triggers on `master`, restores NuGet, builds `**/*.sln` in Release with a web deploy package, and runs VSTest.

`NeoAfricaDevelopmentWeb.yaml` (and the API and CDN siblings) use NuGet 5.8.0, `VSBuild`, configuration `NeoAfrica.Development.Web` on the web file, and `VSTest`. The files contain comments that `Parameters.solution`, `BuildPlatform`, and `BuildConfiguration` are undefined or queue-time variables. They are exported pipeline fragments, not a complete definition.

**Discrepancy:** default git branch is `main`. The Azure file triggers `master`. **Unknown** whether an Azure pipeline still runs.

### Local assembly refresh

`RefreshAssembliesFromNeoAfxMaster.bat` xcopies from `C:\Development\NeoAfrica\Neo.Afx\Assemblies` and `Deploy` into `C:\Development\NeoAfrica\LegitimateLicences\Assemblies`, then deletes `Neo.LegitimateLicences.Web\bin\*.*`. Those paths are a developer machine layout. They are not this git workspace.

### IIS

Checklist: separate app pools for the main web app (.NET 4.6.2), the API, and AFX services. `BaseUrl`, `AfxServicesPath`, and `AfxCdnPath` must be set per environment.

## Security observations (no secret values)

These are facts about the tree, not a pentest.

| Observation | Where |
|---|---|
| Membership `passwordFormat="Clear"` | `Web.config` |
| Checklist and `checklist.csv` contain database, eNatis certificate, and eNatis account secrets | Those files. Values omitted from Silicon Ark |
| API CORS allows every origin | `LegitimateLicencesApiController` |
| Forms timeout 30 minutes, sliding | `Web.config` |
| Windows service runs as Local System | `ConfigureService.cs` |
| Sentry debug and 100% traces | `Global.asax.cs` |
| SignalR broadcasts to all clients | `MessageHub.cs` |
| DbUp failure does not stop the site | `Global.asax.cs` |

## Testing

| Asset | What it is |
|---|---|
| `Neo.LegitimateLicences.Api.Test` | Test project. Not invoked by `bitbucket-pipelines.yml` |
| `Neo.Afx.WinService.UnitTests` | Test project. Same |
| `QA/TestPlan_EhailingProviders/` | Manual Excel-oriented pack: `TestCases.csv`, provider and certificate sample data, `SQL_Probes.sql`, `README.txt`. Assumes type ids 60–62 and certificate format `NPTR-EHP-YYYY-####` |
| Azure YAML `VSTest@2` | Would run `*test*.dll` if that pipeline is configured. It is not the Bitbucket pipeline |

No test run was executed for this documentation pass.

## Monitoring

- Sentry for ASP.NET and Entity Framework exceptions, plus performance traces at sample rate 1.0.
- Checklist asks for Sentry alerting, SQL monitoring, and Windows service auto-restart. Those server settings are not in git.
- The scheduler writes an audit log when a service fails to start (`UpdateAuditLog` in `DatabaseServiceScheduler`).

## Developer notes that are easy to miss

- Project folder `Neo.LegitimateLicences.Purifaction` is misspelled. Views use `Purification`.
- Install folder `WIndowServiceInstall` capitalises the second letter of Window.
- Seed titles use the spelling `legiti-mate` inside "LTPS to legiti-mate" and "OLAS to Legiti-mate".
- `Global.asax.cs` comment says the database-update catch exists so the site can start for eNatis API testing when SQL is down.
- E-hailing doc deployment order lists six scripts (`0923` through `0928`). Many later scripts (`1005` through `1150`, plus 2026 gazette settings) also belong to that module. Following only the doc's list is not the full chain on `main`.

## Unknown / requires confirmation

- Mapping of git branches to deployed environments.
- Full embedded-resource versus on-disk SQL gap.
- Whether Azure Pipelines still builds this repo.
- Runtime use of the Azure AD NuGet packages.
- Contents of `Assemblies/` (which binaries are committed, and from which Neo.Afx build).
- Production values for feature flags and the gazette bypass setting.
- Whether `passwordFormat="Clear"` is still true in deployed `Web.config` or only in this committed file.
