Explorar el Código

Docs and test Implementation

master
Daniel Covington hace 1 mes
padre
commit
f229f088eb
Se han modificado 45 ficheros con 2808 adiciones y 0 borrados
  1. +4
    -0
      .gitignore
  2. +10
    -0
      README.md
  3. +121
    -0
      TESTING.md
  4. +63
    -0
      docs/api-contracts-mvc-starter.md
  5. +34
    -0
      docs/architecture-patterns.md
  6. +81
    -0
      docs/architecture.md
  7. +63
    -0
      docs/component-inventory.md
  8. +46
    -0
      docs/comprehensive-analysis-mvc-starter.md
  9. +11
    -0
      docs/critical-folders-summary.md
  10. +42
    -0
      docs/data-models-mvc-starter.md
  11. +35
    -0
      docs/deployment-configuration.md
  12. +37
    -0
      docs/deployment-guide.md
  13. +55
    -0
      docs/development-guide.md
  14. +59
    -0
      docs/development-instructions.md
  15. +46
    -0
      docs/existing-documentation-inventory.md
  16. +71
    -0
      docs/index.md
  17. +70
    -0
      docs/project-overview.md
  18. +26
    -0
      docs/project-parts.json
  19. +1
    -0
      docs/project-scan-report.json
  20. +38
    -0
      docs/project-structure.md
  21. +100
    -0
      docs/source-tree-analysis.md
  22. +26
    -0
      docs/state-management-patterns-mvc-starter.md
  23. +28
    -0
      docs/technology-stack.md
  24. +50
    -0
      docs/ui-component-inventory-mvc-starter.md
  25. +13
    -0
      docs/user-provided-context.md
  26. +190
    -0
      tests/PlainRunnerTheme.asp
  27. +21
    -0
      tests/aspunit/LICENSE-MIT
  28. +12
    -0
      tests/aspunit/Lib/ASPUnit.asp
  29. +123
    -0
      tests/aspunit/Lib/classes/ASPUnitJSONResponder.asp
  30. +104
    -0
      tests/aspunit/Lib/classes/ASPUnitLibrary.asp
  31. +174
    -0
      tests/aspunit/Lib/classes/ASPUnitRunner.asp
  32. +258
    -0
      tests/aspunit/Lib/classes/ASPUnitTester.asp
  33. +395
    -0
      tests/aspunit/Lib/classes/ASPUnitUIModern.asp
  34. +49
    -0
      tests/bootstrap.asp
  35. +43
    -0
      tests/component/TestHomeController.asp
  36. +18
    -0
      tests/component/web.config
  37. +80
    -0
      tests/integration/TestMvcDispatch.asp
  38. +18
    -0
      tests/integration/web.config
  39. +9
    -0
      tests/run-all.asp
  40. +25
    -0
      tests/sync-webconfigs.vbs
  41. +10
    -0
      tests/test-manifest.asp
  42. +48
    -0
      tests/unit/TestControllerRegistry.asp
  43. +65
    -0
      tests/unit/TestHelpers.asp
  44. +18
    -0
      tests/unit/web.config
  45. +18
    -0
      tests/web.config

+ 4
- 0
.gitignore Ver fichero

@@ -0,0 +1,4 @@
/_bmad
/_bmad*
/.agents
/.github

+ 10
- 0
README.md Ver fichero

@@ -76,3 +76,13 @@ Move generated file to `app/controllers/`.
- Classic ASP enabled
- IIS URL Rewrite module
- Microsoft Access Database Engine (for .accdb support)

## Testing

This repo now includes a dev-only `aspunit` harness under `tests/`. It is intentionally separate from the production app rooted at `public/`.

- Configure a separate IIS application rooted at `tests/`
- Ensure Classic ASP parent paths are enabled for that IIS app
- If you change `tests/web.config`, refresh the nested test-folder copies with `cscript //nologo tests\sync-webconfigs.vbs`
- Open `run-all.asp` inside that IIS app to execute the test suite
- See `TESTING.md` for setup, manifest registration, and extension guidance

+ 121
- 0
TESTING.md Ver fichero

@@ -0,0 +1,121 @@
# Testing MVC-Starter

This project now supports a dev-only Classic ASP test harness built with `aspunit`.

## Scope

- Production app: `public/`
- Dev-only test app: `tests/`
- Test framework: `tests/aspunit/`

The `tests/` IIS application assumes the repository layout keeps `tests/`, `public/`, `core/`, and `app/` as sibling directories.

## File Inventory

| Type | Path | Purpose |
| ---- | ---- | ------- |
| Create | `tests/web.config` | IIS/default-document config for the isolated test app |
| Create | `tests/unit/web.config` | Mirrored config for nested unit pages that load config-aware code |
| Create | `tests/component/web.config` | Mirrored config for nested component pages that load config-aware code |
| Create | `tests/integration/web.config` | Mirrored config for nested integration pages that load config-aware code |
| Create | `tests/sync-webconfigs.vbs` | Utility script to mirror `tests/web.config` into nested test folders |
| Create | `tests/bootstrap.asp` | Shared test bootstrap and runtime reset helpers |
| Create | `tests/PlainRunnerTheme.asp` | Local runner theme that removes CDN dependence from the test UI |
| Create | `tests/test-manifest.asp` | Single source of truth for registered test pages |
| Create | `tests/run-all.asp` | Browser runner that aggregates all test pages |
| Vendor | `tests/aspunit/Lib/*` | Upstream aspunit framework files |
| Vendor | `tests/aspunit/LICENSE-MIT` | Upstream aspunit license for vendored third-party code |
| Create | `tests/unit/TestHelpers.asp` | Deterministic helper-function unit tests |
| Create | `tests/unit/TestControllerRegistry.asp` | Controller whitelist/format unit tests |
| Create | `tests/component/TestHomeController.asp` | Controlled component-level controller test |
| Create | `tests/integration/TestMvcDispatch.asp` | Narrow router/dispatch smoke test |
| Reference | `public/web.config` | Source of mirrored config keys for the test app |
| Reference | `core/helpers.asp` | Helper functions and config-loading behavior under test |
| Reference | `core/mvc.asp` | Dispatcher behavior used by the smoke test |
| Reference | `core/lib.ControllerRegistry.asp` | Whitelist behavior under test |
| Verify | `public/` site | Confirm production app exposes no test routes/pages |

## IIS Setup

1. Keep the existing production IIS app rooted at `public/`.
2. Create a separate development-only IIS application rooted at `tests/`.
3. Enable Classic ASP for that IIS app.
4. Ensure parent paths are allowed for the `tests/` app. This repo ships `tests/web.config` with `enableParentPaths="true"` because the bootstrap and integration pages include sibling files from `../core/` and `../app/`.
5. Browse to the `tests/` app root or directly to `run-all.asp`.
6. If you change `tests/web.config`, run `cscript //nologo tests\sync-webconfigs.vbs` to refresh the nested copies used by the unit, component, and integration pages.

Example layout:

- Production: `http://localhost/`
- Tests: `http://localhost/tests-dev/`

## How It Works

- `tests/run-all.asp` includes the aspunit library and the manifest.
- `tests/run-all.asp` also applies `PlainRunnerTheme.asp`, a local runner theme that avoids the upstream CDN dependency in aspunit’s default UI.
- `tests/test-manifest.asp` explicitly registers each test page.
- `tests/bootstrap.asp` provides the shared runtime setup:
- helper/config access
- controller registry access
- router reset

The integration test page includes `core/mvc.asp` directly because that is the only first-wave test that needs dispatcher behavior.

Because `GetAppSetting()` uses `Server.MapPath("web.config")`, nested test folders also need a mirrored `web.config` alongside the executing test pages that rely on config-aware runtime files. Use `tests/sync-webconfigs.vbs` after changing `tests/web.config`.

The manifest is manual by design. There is no filesystem auto-discovery.

## Running Tests

Open the browser runner:

Browse to `run-all.asp` within the `tests/` IIS application, for example:

```text
http://localhost/tests-dev/run-all.asp
```

aspunit renders a UI in runner mode and loads each registered page with `?task=test` behind the scenes.

## Adding a New Test Page

1. Choose the right folder:
- `tests/unit/` for deterministic helper or registry tests
- `tests/component/` for direct controller/object tests with controlled setup
- `tests/integration/` for narrow runtime smoke coverage
2. Create a new `.asp` file that:
- includes `../aspunit/Lib/ASPUnit.asp`
- includes `../bootstrap.asp`
- registers one or more modules with `ASPUnit.AddModule(...)`
- calls `ASPUnit.Run()`
3. Add the page path to `tests/test-manifest.asp`.
4. Reload `run-all.asp`.

## First-Pass Isolation Policy

- Unit tests should avoid mutating globals.
- Registry tests should inspect current behavior, not expand the production whitelist.
- Component tests should reset touched singleton/controller state in setup/teardown.
- Dispatch smoke tests should initialize fresh route state before each run.

## What Not To Unit Test

- Full rendered page markup for shared-layout pages unless you add reliable output-capture support.
- Production IIS rewrite behavior from `public/`.
- Broad end-to-end request flows through the production site root.
- Config-dependent behavior that requires machine-specific production values unless you first mirror safe test values into `tests/web.config`.
- Internet access as a requirement for the runner UI. The local theme removes the upstream CDN dependency for first-wave execution.

## Validation Checklist

- `run-all.asp` loads in the separate `tests/` IIS app.
- All manifest pages appear in the runner.
- Helper, registry, component, and integration suites all execute.
- Re-running the suite produces stable results.
- The production site under `public/` still exposes no test runner pages or test routes.

## Limitations

- This harness runs only inside IIS/Classic ASP; it is not intended for Linux execution.
- The repo’s production runtime is still configured via `public/web.config`; the test app uses a minimal mirrored config in `tests/web.config`.
- If the narrow bootstrap is not sufficient for future smoke tests, broaden only the test bootstrap first before considering production runtime changes.

+ 63
- 0
docs/api-contracts-mvc-starter.md Ver fichero

@@ -0,0 +1,63 @@
# API Contracts - MVC-Starter

**Date:** 2026-03-11T11:59:39Z

## Overview

This project does not expose a standalone JSON API. Its current externally reachable contracts are server-rendered HTTP routes handled by the Classic ASP MVC dispatcher.

## Route Catalog

### `GET /`

- **Controller:** `homeController`
- **Action:** `Index`
- **Behavior:** Renders the home page using the shared layout and `app/views/Home/index.asp`.
- **Layout:** enabled (`useLayout = True`)
- **Response Type:** HTML
- **Status:** `200 OK`

### `GET /home`

- **Controller:** `homeController`
- **Action:** `Index`
- **Behavior:** Alias route to the home page; renders the same content as `/`.
- **Layout:** enabled
- **Response Type:** HTML
- **Status:** `200 OK`

### `GET ""` (empty path fallback)

- **Controller:** `homeController`
- **Action:** `Index`
- **Behavior:** Additional root-path fallback route declared in `public/Default.asp`.
- **Layout:** enabled
- **Response Type:** HTML
- **Status:** `200 OK`

### `GET /404`

- **Controller:** `ErrorController`
- **Action:** `NotFound`
- **Behavior:** Renders the not-found page and sets the response status to `404 Not Found`.
- **Layout:** enabled
- **Response Type:** HTML
- **Status:** `404 Not Found`

## Dispatch Contract

- All non-static requests are rewritten by IIS to `public/Default.asp`.
- `public/Default.asp` registers routes and calls `MVC.DispatchRequest`.
- `core/mvc.asp` validates controller and action names, checks the controller whitelist, resolves the controller dynamically, and invokes the action.
- If `useLayout` is enabled on the resolved controller, shared header and footer views wrap the response.

## Security and Validation Notes

- Controller names must pass format validation and whitelist checks in `core/lib.ControllerRegistry.asp`.
- Action names must pass identifier validation before dispatch.
- Current route handling is page-oriented; there are no documented JSON payload schemas or standalone API auth flows in the application itself.

## Brownfield Notes

- Future endpoint additions require updates in at least three places: route registration, controller include/implementation, and controller whitelist registration.
- This document reflects route contracts, not REST-style service endpoints.

+ 34
- 0
docs/architecture-patterns.md Ver fichero

@@ -0,0 +1,34 @@
# Architecture Patterns

**Date:** 2026-03-11T11:59:39Z

## Part: MVC-Starter

### Primary Architecture Pattern

Server-rendered MVC monolith with a starter/framework hybrid structure: IIS and `public/` handle request entry, `core/` provides shared runtime and dispatch behavior, and `app/` provides project-specific extensions.

## Pattern Breakdown

- **End-to-end request path:** IIS URL Rewrite sends requests to `public/Default.asp`, routes are registered there, the router resolves controller/action/params, `core/mvc.asp` validates and dispatches, and shared layout files wrap output when `useLayout` is enabled.
- **Routing pattern:** Routes are declared centrally in `public/Default.asp` and resolved through the RouteKit router object.
- **Dispatch pattern:** `core/mvc.asp` uses validation, controller whitelisting, and dynamic execution to resolve the current controller and action.
- **Application layering:** `core/` contains reusable framework/runtime behavior, while `app/` contains project-specific controllers, views, models, and repositories.
- **Rendering pattern:** Controllers orchestrate and include `.asp` view files; shared header/footer layout files provide common page chrome.
- **Data pattern:** Data access is config-driven through `public/web.config`, with DAL libraries and generator-assisted repositories/POBOs supporting database interaction.
- **Operational tooling pattern:** Schema and scaffolding workflows are handled by standalone VBScript tools under `scripts/`.

## Architectural Implications

- This codebase is optimized around a single IIS-hosted deployable unit rather than separable services.
- Framework bootstrapping and request dispatch are centralized, so edits to `public/` entry files or `core/` runtime files have broad impact.
- Dynamic dispatch is part of the framework design, which makes naming, registration, and validation rules part of the architecture contract.
- Feature development typically extends existing seams in `app/`, `db/`, and `scripts/` rather than introducing new application subsystems.
- The architecture assumes server-side HTML rendering and config-driven runtime behavior rather than client-heavy SPA patterns.

## Brownfield Notes

- The most important architectural boundary is between the framework/runtime layer (`core/`) and the app extension layer (`app/`).
- Controller activation is not automatic; routing, include registration, and whitelist registration are all part of the runtime contract.
- Windows/IIS hosting is part of the architecture, not just deployment detail, because request flow and configuration depend on it.
- Generator scripts are part of the implementation model, not just convenience tooling.

+ 81
- 0
docs/architecture.md Ver fichero

@@ -0,0 +1,81 @@
# MVC-Starter - Architecture

**Date:** 2026-03-11T11:59:39Z
**Project Type:** web
**Architecture:** Server-rendered MVC monolith

## Executive Summary

MVC-Starter is a Classic ASP starter application built around a single IIS-hosted deployable unit. Requests enter through `public/Default.asp`, flow through a shared runtime in `core/`, and resolve into application-specific controllers and views under `app/`.

## Technology Stack

- Classic ASP / VBScript
- Windows IIS with URL Rewrite
- RouteKit-style router and MVC dispatcher
- Microsoft Access via ACE OLE DB
- Bootstrap 5.3.3 and Bootstrap Icons 1.11.3 from CDN
- VBScript tooling for scaffolding and migrations

## Architecture Pattern

Starter/framework hybrid MVC monolith:

- `public/` handles web entry and IIS-facing config
- `core/` handles routing, dispatch, validation, helpers, and DAL concerns
- `app/` contains project-specific behavior and server-rendered views
- `db/` and `scripts/` support data and operational workflows

## Request Flow

1. IIS receives the request.
2. `public/web.config` rewrites non-static requests to `public/Default.asp`.
3. `Default.asp` registers routes and calls `MVC.DispatchRequest`.
4. `core/mvc.asp` validates controller/action names, checks whitelist registration, resolves the current controller dynamically, and executes the target action.
5. Shared layout files wrap the response if `useLayout` is enabled.

## Data Architecture

- Database configuration is read from `public/web.config`.
- `db/webdata.accdb` is the included Access database.
- DAL and migration helpers live in `core/`.
- Repositories and POBOs are expected to be generated via `scripts/GenerateRepo.vbs`.

## Component Overview

- Controllers: `app/controllers/`
- Views: `app/views/`
- Shared layout: `app/views/shared/`
- Runtime/framework: `core/`
- Scaffolding/tooling: `scripts/`

## Source Tree

See [source-tree-analysis.md](./source-tree-analysis.md) for the annotated directory breakdown.

## Development Workflow

- Generate migration
- Generate POBO/repository
- Generate controller
- Register controller include, whitelist, and routes
- Add views and validate via IIS-hosted execution

## Deployment Architecture

- Single IIS site
- `public/` as web root
- XML config in `public/web.config`
- No separate service/process topology detected

## Testing Strategy

- No automated test framework detected
- Manual request-flow verification is required
- Script tooling can be validated via `cscript`

## Architectural Constraints

- Dynamic dispatch means naming and registration consistency are part of runtime correctness.
- `core/`, `public/Default.asp`, and `public/web.config` are high-impact files.
- The architecture assumes Windows/IIS hosting rather than cross-platform portability.

+ 63
- 0
docs/component-inventory.md Ver fichero

@@ -0,0 +1,63 @@
# MVC-Starter - Component Inventory

**Date:** 2026-03-11T11:59:39Z

## Runtime and MVC Components

### Front Controller

- `public/Default.asp`
- Registers routes
- Boots runtime
- Dispatches requests

### Dispatcher

- `core/mvc.asp`
- Validates controller/action names
- Enforces controller whitelist
- Wraps responses in shared layout when enabled

### Controller Registry

- `core/lib.ControllerRegistry.asp`
- Tracks valid controllers
- Prevents arbitrary controller execution

### Controllers

- `app/controllers/HomeController.asp`
- Home page controller
- `app/controllers/ErrorController.asp`
- 404/not-found handling

## UI/View Components

### Shared Layout

- `app/views/shared/header.asp`
- `app/views/shared/footer.asp`

### Feature Views

- `app/views/Home/index.asp`
- `app/views/Error/NotFound.asp`

## Data/Operational Components

### Database Assets

- `db/webdata.accdb`

### Script Tooling

- `scripts/generateController.vbs`
- `scripts/GenerateRepo.vbs`
- `scripts/generateMigration.vbs`
- `scripts/runMigrations.vbs`

## Reuse Notes

- The main reusable UI surface is the shared layout.
- The main reusable runtime surface is the `core/` library set.
- The data layer is scaffold-oriented and currently has no committed application-specific model/repository classes.

+ 46
- 0
docs/comprehensive-analysis-mvc-starter.md Ver fichero

@@ -0,0 +1,46 @@
# Comprehensive Analysis - MVC-Starter

**Date:** 2026-03-11T11:59:39Z

## Configuration Management

- Primary runtime configuration lives in `public/web.config`.
- Important settings include database connection, environment mode, flash timing, 404 redirect timing, cache controls, and error logging paths.
- Configuration is read in application code through helper accessors such as `GetAppSetting`.

## Authentication and Security

- No full authentication subsystem is present in the starter.
- The main observable security boundary is controller/action validation and controller whitelisting in `core/lib.ControllerRegistry.asp`.
- Dispatch also HTML-encodes some user-visible error output to reduce injection risk.

## Entry Points and Bootstrap

- IIS web root: `public/`
- Default document: `public/Default.asp`
- Runtime bootstrap include: `core/autoload_core.asp`
- Request dispatcher: `core/mvc.asp`

## Shared Code

- `core/` contains reusable framework/runtime libraries for routing, data access, flash messaging, forms, uploads, HTML helpers, validation, encryption helpers, and JSON support.
- `scripts/` contains operational generators and migration tooling.

## Event/Async Patterns

- No queue, worker, or event-bus architecture was detected.
- UI behavior is mostly server-rendered with small inline browser-side scripts where needed.

## CI/CD and Deployment

- No CI/CD pipeline configuration was detected.
- Deployment is IIS-based and documented in `README.md` and `public/web.config`.

## Localization

- No localization or i18n framework was detected.

## Testing Surface

- No automated test framework or test files were detected in the application code.
- Validation currently depends on manual runtime checks and script execution in a Windows/IIS environment.

+ 11
- 0
docs/critical-folders-summary.md Ver fichero

@@ -0,0 +1,11 @@
# Critical Folders Summary

**Date:** 2026-03-11T11:59:39Z

- `public/`: externally facing web root, rewrite config, and request bootstrap
- `core/`: framework/runtime internals, dispatcher, router, helpers, DAL, and security checks
- `app/controllers/`: project controller implementations and include-based activation surface
- `app/views/`: server-rendered feature views and shared layout
- `db/`: Access database and migration workspace
- `scripts/`: generator and migration tooling used during feature development
- `docs/`: generated brownfield documentation for future planning and AI-assisted work

+ 42
- 0
docs/data-models-mvc-starter.md Ver fichero

@@ -0,0 +1,42 @@
# Data Models - MVC-Starter

**Date:** 2026-03-11T11:59:39Z

## Overview

The repository includes a database file and data-access infrastructure, but it does not yet contain checked-in application model or repository source files under `app/models/` or `app/repositories/`.

## Current Data Assets

### Database File

- **Path:** `db/webdata.accdb`
- **Type:** Microsoft Access database
- **Configured Provider:** `Microsoft.ACE.OLEDB.12.0`
- **Configured From:** `public/web.config`

### Data Access Runtime

- **DAL libraries:** `core/lib.DAL.asp`, `core/lib.Data.asp`
- **Migration support:** `core/lib.Migrations.asp`
- **Repository/model generation:** `scripts/GenerateRepo.vbs`

## Schema and Model Strategy

- The intended model pattern is generator-assisted.
- `GenerateRepo.vbs` inspects a target table and produces:
- `POBO_<Table>.asp`
- `<Table>Repository.asp`
- Generated outputs are expected to be moved into `app/models/` and `app/repositories/`.

## Current Repository State

- `app/models/` is currently empty.
- `app/repositories/` is currently empty.
- `db/migrations/` currently has no checked-in migration files.

## Brownfield Implications

- The project is database-capable, but the sample starter does not include domain entities yet.
- Schema evolution and repository creation are expected to happen through the provided VBScript tooling rather than a separate ORM or package-managed migration framework.
- Future data documentation should be regenerated once concrete tables, migration files, or generated POBO/repository files are added.

+ 35
- 0
docs/deployment-configuration.md Ver fichero

@@ -0,0 +1,35 @@
# Deployment Configuration

**Date:** 2026-03-11T11:59:39Z

## Hosting Model

- Windows IIS
- `public/` configured as the site root
- `public/Default.asp` as the default document
- URL Rewrite sends non-static requests through the ASP front controller

## Runtime Configuration

Primary deployment configuration is stored in `public/web.config`.

### Important Settings

- `ConnectionString`
- `Environment`
- `FlashMessageTimeout`
- `Error404RedirectSeconds`
- `CacheExpirationYear`
- `EnableErrorLogging`
- `ErrorLogPath`

## Deployment Notes

- The Access DB path must be updated for the target machine.
- `ErrorLogPath` should be writable by the IIS application identity if enabled.
- Static assets are expected under `public/` paths excluded from rewrite rules.

## Observed Gaps

- No container, CI/CD, or infrastructure-as-code deployment config was detected.
- Deployment is currently documented as a manual IIS-based process.

+ 37
- 0
docs/deployment-guide.md Ver fichero

@@ -0,0 +1,37 @@
# MVC-Starter - Deployment Guide

**Date:** 2026-03-11T11:59:39Z

## Deployment Model

Single-site Windows IIS deployment with `public/` as the web root.

## Deployment Steps

1. Copy the repository to the target Windows host.
2. Configure the IIS site to point to `public/`.
3. Ensure Classic ASP is enabled.
4. Ensure URL Rewrite is installed.
5. Update `public/web.config` for the target environment.
6. Ensure the Access DB file path is valid and accessible.

## Key Runtime Config

- `ConnectionString`
- `Environment`
- `EnableErrorLogging`
- `ErrorLogPath`
- cache and UI timing settings

## Deployment Risks

- Incorrect `ConnectionString` path for `.accdb`
- Missing IIS URL Rewrite module
- Missing Classic ASP support
- File permission issues for logs or database access

## What Was Not Found

- No Docker, Kubernetes, or container deployment setup
- No CI/CD pipeline config
- No infrastructure-as-code deployment definition

+ 55
- 0
docs/development-guide.md Ver fichero

@@ -0,0 +1,55 @@
# MVC-Starter - Development Guide

**Date:** 2026-03-11T11:59:39Z

## Prerequisites

- Windows environment
- IIS with Classic ASP enabled
- IIS URL Rewrite module
- Microsoft Access Database Engine
- Windows Script Host (`cscript`)

## Local Setup

1. Place the project on a Windows machine with IIS.
2. Point the site root to `public/`.
3. Update `public/web.config` for the correct database and logging paths.
4. Browse to `/` and confirm the starter home page renders.

## Common Workflows

### Add a database-backed feature

1. Generate a migration
2. Generate POBO/repository
3. Move generated files into `app/models/` and `app/repositories/`
4. Generate a controller
5. Include the controller from `app/controllers/autoload_controllers.asp`
6. Register the controller in `core/lib.ControllerRegistry.asp`
7. Add routes in `public/Default.asp`
8. Add corresponding views in `app/views/`

### Useful Commands

```bat
cscript //nologo scripts\generateMigration.vbs create_my_table
cscript //nologo scripts\GenerateRepo.vbs /table:my_table /pk:id
cscript //nologo scripts\generateController.vbs MyController "Index;Show(id)"
cscript //nologo scripts\runMigrations.vbs status
```

## Testing Approach

- Manual runtime validation through IIS
- Manual route/layout verification after controller or view changes
- Script validation through `cscript`
- No built-in automated test suite detected

## High-Risk Change Areas

- `public/Default.asp`
- `public/web.config`
- `core/mvc.asp`
- `core/lib.ControllerRegistry.asp`
- shared layout files under `app/views/shared/`

+ 59
- 0
docs/development-instructions.md Ver fichero

@@ -0,0 +1,59 @@
# Development Instructions

**Date:** 2026-03-11T11:59:39Z

## Prerequisites

- Windows Server or Windows development environment
- IIS with Classic ASP enabled
- IIS URL Rewrite module
- Microsoft Access Database Engine for `.accdb` support
- Windows Script Host / `cscript`

## Setup

1. Copy the project to the target IIS host.
2. Point the IIS site root to `public/`.
3. Update `public/web.config`:
- `ConnectionString`
- `ErrorLogPath` if logging is desired
4. Ensure the Access database file path is valid for the environment.

## Common Development Tasks

### Generate a migration

```bat
cscript //nologo scripts\generateMigration.vbs create_my_table
```

### Generate POBO and repository

```bat
cscript //nologo scripts\GenerateRepo.vbs /table:my_table /pk:id
```

Move generated files into `app/models/` and `app/repositories/`.

### Generate a controller

```bat
cscript //nologo scripts\generateController.vbs MyController "Index;Show(id);Create;Store"
```

Move the generated controller into `app/controllers/`.

### Activate a controller

After generating a controller:

1. Include it from `app/controllers/autoload_controllers.asp`
2. Register it in `core/lib.ControllerRegistry.asp`
3. Add routes in `public/Default.asp`
4. Create corresponding views in `app/views/`

## Validation Approach

- Manual browser validation is required for runtime changes.
- Route/layout changes should be checked through IIS-hosted execution.
- Migration and generator changes should be validated with `cscript`.

+ 46
- 0
docs/existing-documentation-inventory.md Ver fichero

@@ -0,0 +1,46 @@
# Existing Documentation Inventory

**Date:** 2026-03-11T11:59:39Z

## Summary

The project has a small set of directly relevant documentation files plus BMAD framework support files. For brownfield understanding, the highest-priority sources are the project README and a small number of repo-specific instruction files.

## Priority Documentation for Project Understanding

### `README.md`

- **Type:** readme
- **Path:** `/workspace/MVC-Starter/README.md`
- **Scope:** whole project
- **Notes:** Documents IIS setup, project structure, generator workflow, and baseline runtime requirements.

### `.github/copilot-instructions.md`

- **Type:** project instructions
- **Path:** `/workspace/MVC-Starter/.github/copilot-instructions.md`
- **Scope:** whole project
- **Notes:** Documents BMAD runtime conventions, config locations, workflow engine usage, and agent/runtime structure.

### `_bmad-output/project-context.md`

- **Type:** generated ai-project-context
- **Path:** `/workspace/MVC-Starter/_bmad-output/project-context.md`
- **Scope:** whole project
- **Notes:** Derivative documentation generated from workflow analysis. Useful for AI implementation rules, but not an original source of project design intent.

## BMAD Framework Documentation Assets

- `.github/agents/*.md` and `.github/prompts/*.md` provide BMAD agent and prompt definitions.
- `_bmad/` contains the installed BMAD framework, workflows, templates, and configuration.
- These files are important for maintaining the BMAD layer, but they are secondary to the application docs when documenting the MVC starter itself.

## Generated Documentation in This Workflow Run

- Files created under `docs/` by this workflow should be treated as generated brownfield documentation outputs, not original project-authored documents.

## Observations

- No dedicated architecture, deployment, API, or data-model markdown docs existed for the application before this workflow run.
- The README is the main human-authored source of project usage and structure.
- Most other markdown assets in the repo belong to the BMAD toolchain rather than the Classic ASP application itself.

+ 71
- 0
docs/index.md Ver fichero

@@ -0,0 +1,71 @@
# MVC-Starter Documentation Index

**Type:** monolith
**Primary Language:** VBScript / Classic ASP
**Architecture:** Server-rendered MVC monolith
**Last Updated:** 2026-03-11T11:59:39Z

## Project Overview

MVC-Starter is a RouteKit Classic ASP starter application for Windows IIS. It uses a single front controller, shared runtime/framework libraries, server-rendered views, an Access database, and VBScript-based scaffolding tools.

## Quick Reference

- **Tech Stack:** Classic ASP, VBScript, IIS, Access, Bootstrap
- **Entry Point:** `public/Default.asp`
- **Architecture Pattern:** Starter/framework hybrid MVC monolith
- **Database:** `db/webdata.accdb` via ACE OLE DB
- **Deployment:** Manual IIS deployment

## Generated Documentation

### Core Documentation

- [Project Overview](./project-overview.md) - Executive summary and high-level architecture
- [Project Structure](./project-structure.md) - Classification and structural boundaries
- [Source Tree Analysis](./source-tree-analysis.md) - Annotated directory structure
- [Architecture](./architecture.md) - Detailed technical architecture
- [Architecture Patterns](./architecture-patterns.md) - Runtime and design patterns
- [Technology Stack](./technology-stack.md) - Stack inventory and justification
- [Component Inventory](./component-inventory.md) - Runtime, UI, and tooling components
- [Development Guide](./development-guide.md) - Setup and common workflows
- [Deployment Guide](./deployment-guide.md) - IIS deployment and config guidance
- [API Contracts](./api-contracts-mvc-starter.md) - HTTP route contracts
- [Data Models](./data-models-mvc-starter.md) - Current data/model state and strategy

### Supporting Analysis

- [Existing Documentation Inventory](./existing-documentation-inventory.md)
- [User-Provided Context](./user-provided-context.md)
- [State Management Patterns](./state-management-patterns-mvc-starter.md)
- [UI Component Inventory](./ui-component-inventory-mvc-starter.md)
- [Comprehensive Analysis](./comprehensive-analysis-mvc-starter.md)
- [Critical Folders Summary](./critical-folders-summary.md)
- [Development Instructions](./development-instructions.md)
- [Deployment Configuration](./deployment-configuration.md)
- [Project Parts Metadata](./project-parts.json)
- [Project Scan State](./project-scan-report.json)

## Existing Documentation

- [README.md](../README.md) - Setup, structure, and feature scaffolding workflow
- [.github/copilot-instructions.md](../.github/copilot-instructions.md) - BMAD/Copilot repo instructions
- [_bmad-output/project-context.md](../_bmad-output/project-context.md) - Generated AI implementation context

## Getting Started

### For Humans

1. Read [project-overview.md](./project-overview.md)
2. Review [architecture.md](./architecture.md)
3. Use [development-guide.md](./development-guide.md) for feature work

### For AI-Assisted Development

- Start from this index
- Use [architecture.md](./architecture.md) and [project-context.md](../_bmad-output/project-context.md) together
- Reference [api-contracts-mvc-starter.md](./api-contracts-mvc-starter.md) and [data-models-mvc-starter.md](./data-models-mvc-starter.md) for route/data planning

---

Documentation generated by BMAD `document-project`.

+ 70
- 0
docs/project-overview.md Ver fichero

@@ -0,0 +1,70 @@
# MVC-Starter - Project Overview

**Date:** 2026-03-11T11:59:39Z
**Type:** web
**Architecture:** Server-rendered MVC monolith

## Executive Summary

MVC-Starter is a RouteKit Classic ASP starter for building server-rendered MVC applications on Windows IIS. It combines a shared framework/runtime layer in `core/` with application-specific controllers and views in `app/`, and supports database-backed features through Access and VBScript-based scaffolding.

## Project Classification

- **Repository Type:** monolith
- **Project Type(s):** web
- **Primary Language(s):** VBScript, Classic ASP
- **Architecture Pattern:** Starter/framework hybrid MVC monolith

## Technology Stack Summary

- Runtime: Classic ASP on IIS
- Routing/dispatch: RouteKit-style router and MVC dispatcher
- Data: ADO + Microsoft Access
- UI: Bootstrap CDN + server-rendered ASP views
- Tooling: Windows Script Host VBScript scripts

## Key Features

- Front-controller request flow through `public/Default.asp`
- Controller whitelist and dynamic dispatch in `core/mvc.asp`
- Shared header/footer layout for page rendering
- Generator-assisted migrations, repositories, and controllers
- Included starter pages for home and 404 handling

## Architecture Highlights

- Single deployable web app
- Clear split between runtime/framework code and app-specific code
- Config-driven behavior through `public/web.config`
- Manual but structured development workflow through scripts and route/controller wiring

## Development Overview

### Prerequisites

- Windows + IIS + Classic ASP
- IIS URL Rewrite
- Microsoft Access Database Engine
- `cscript` for tooling

### Getting Started

Point IIS at `public/`, update `public/web.config`, then validate the home page loads. Use the provided VBScript generators to create migrations, repositories, and controllers for new features.

### Key Commands

- **Generate migration:** `cscript //nologo scripts\generateMigration.vbs create_my_table`
- **Generate repo:** `cscript //nologo scripts\GenerateRepo.vbs /table:my_table /pk:id`
- **Generate controller:** `cscript //nologo scripts\generateController.vbs MyController "Index;Show(id)"`
- **Run migrations:** `cscript //nologo scripts\runMigrations.vbs status`

## Repository Structure

`public/` is the web entry surface, `core/` is the framework/runtime layer, `app/` is the application layer, `db/` contains database assets, and `scripts/` contains operational tooling.

## Documentation Map

- [index.md](./index.md)
- [architecture.md](./architecture.md)
- [source-tree-analysis.md](./source-tree-analysis.md)
- [development-guide.md](./development-guide.md)

+ 26
- 0
docs/project-parts.json Ver fichero

@@ -0,0 +1,26 @@
{
"repository_type": "monolith",
"parts": [
{
"part_id": "mvc-starter",
"part_name": "MVC-Starter",
"root_path": "/workspace/MVC-Starter",
"project_type_id": "web",
"runtime_shape": "server-rendered-mvc-monolith",
"hosting_assumption": "Windows IIS with URL Rewrite",
"entry_point": "public/Default.asp",
"framework_runtime_path": "core/",
"application_surface_path": "app/",
"operational_support_paths": [
"db/",
"scripts/"
],
"primary_technologies": [
"Classic ASP",
"VBScript",
"IIS",
"Microsoft Access"
]
}
]
}

+ 1
- 0
docs/project-scan-report.json Ver fichero

@@ -0,0 +1 @@
{"workflow_version":"1.2.0","timestamps":{"started":"2026-03-11T11:59:39Z","last_updated":"2026-03-11T12:13:30Z","completed":"2026-03-11T12:13:30Z"},"mode":"initial_scan","scan_level":"exhaustive","project_root":"/workspace/MVC-Starter","project_knowledge":"/workspace/MVC-Starter/docs","completed_steps":[{"step":"step_1","status":"completed","timestamp":"2026-03-11T12:03:40Z","summary":"Classified as monolith with 1 part; detected Classic ASP MVC web application hosted via IIS entrypoint in public/Default.asp"},{"step":"step_2","status":"completed","timestamp":"2026-03-11T12:06:06Z","summary":"Inventoried 3 relevant existing documentation files and recorded no additional user focus areas"},{"step":"step_3","status":"completed","timestamp":"2026-03-11T12:09:06Z","summary":"Analyzed technology stack and architecture pattern as Classic ASP/VBScript MVC monolith on IIS with Access and VBScript tooling"},{"step":"step_4","status":"completed","timestamp":"2026-03-11T12:12:40Z","summary":"Completed exhaustive conditional analysis and generated API, data, state, UI, and comprehensive analysis documents"},{"step":"step_5","status":"completed","timestamp":"2026-03-11T12:12:55Z","summary":"Generated source tree analysis and critical folders summary"},{"step":"step_6","status":"completed","timestamp":"2026-03-11T12:13:05Z","summary":"Generated development instructions and deployment configuration documentation"},{"step":"step_8","status":"completed","timestamp":"2026-03-11T12:13:10Z","summary":"Generated architecture.md for the single application part"},{"step":"step_9","status":"completed","timestamp":"2026-03-11T12:13:15Z","summary":"Generated supporting overview, component, development, deployment, and index-adjacent docs"},{"step":"step_10","status":"completed","timestamp":"2026-03-11T12:13:20Z","summary":"Generated master index with complete links and no incomplete markers"},{"step":"step_11","status":"completed","timestamp":"2026-03-11T12:13:25Z","summary":"Validated generated docs, found no incomplete documentation markers"},{"step":"step_12","status":"completed","timestamp":"2026-03-11T12:13:30Z","summary":"Workflow complete"}],"current_step":"completed","findings":{"project_classification":"monolith, 1 part, Classic ASP/VBScript web application","existing_documentation":"3 relevant docs found; README, Copilot instructions, and generated project context","technology_stack":"Classic ASP/VBScript on IIS with Access, RouteKit-style MVC runtime, Bootstrap CDN, and VBScript tooling","batch_summaries":["Application routes, controllers, views, and shared layout documented from app/ and public/","Runtime and framework behavior documented from core/","Database and operational tooling documented from db/ and scripts/"]},"project_types":[{"part_id":"mvc-starter","project_type_id":"web","display_name":"web"}],"outputs_generated":["project-scan-report.json","project-structure.md","project-parts.json","existing-documentation-inventory.md","user-provided-context.md","technology-stack.md","architecture-patterns.md","api-contracts-mvc-starter.md","data-models-mvc-starter.md","state-management-patterns-mvc-starter.md","ui-component-inventory-mvc-starter.md","comprehensive-analysis-mvc-starter.md","source-tree-analysis.md","critical-folders-summary.md","development-instructions.md","deployment-configuration.md","architecture.md","project-overview.md","component-inventory.md","development-guide.md","deployment-guide.md","index.md"],"resume_instructions":"Workflow complete. Start a fresh scan only if the project changes materially.","verification_summary":"Exhaustive file-system scan of app/core/public/scripts/db; extracted runtime config, routes, controllers, views, and tooling docs; checked docs for incomplete markers; no IIS runtime tests executed.","open_risks":"Documentation is based on static code/config analysis in this Linux workspace; Windows IIS runtime behavior, Access connectivity, and script execution were not validated live.","next_checks":"Review docs/index.md, validate the app on a Windows IIS host, confirm web.config paths, and regenerate docs after adding real models/repositories/migrations."}

+ 38
- 0
docs/project-structure.md Ver fichero

@@ -0,0 +1,38 @@
# MVC-Starter Project Structure

**Date:** 2026-03-11T11:59:39Z
**Repository Type:** monolith
**Detected Project Type:** web
**Specific Runtime Shape:** Classic ASP server-rendered MVC starter on Windows IIS
**Primary Technologies:** Classic ASP, VBScript, IIS, Microsoft Access

## Classification Summary

This repository is a single deployable web application, not a multi-part client/server system. It is a server-rendered Classic ASP MVC starter with:

- `public/` as the IIS web root and deploy entrypoint
- `core/` as shared framework/runtime code
- `app/` as the application extension surface for controllers, views, models, and repositories
- `db/` as database and migration support
- `scripts/` as scaffolding and operational tooling

## Detected Part

### MVC-Starter

- **Root Path:** `/workspace/MVC-Starter`
- **Project Type:** web
- **Runtime Shape:** Server-rendered MVC monolith
- **Hosting Assumption:** Windows IIS with URL Rewrite
- **Deploy Entry Point:** `public/Default.asp`
- **Framework Runtime:** `core/`
- **Application Extension Surface:** `app/`
- **Operational Support Directories:** `db/`, `scripts/`

## Why This Classification Fits

- The repository exposes a single web entrypoint through `public/Default.asp` and IIS rewrite config in `public/web.config`.
- Request handling flows through `public/` into framework code in `core/`, while application behavior is implemented in `app/`.
- Request handling, view rendering, routing, data access, and framework runtime are all contained in one application tree.
- There are no separate client/server packages, independent deployables, or service boundaries that would justify multi-part classification.
- The repo structure matches a classic server-rendered MVC application rather than a frontend/backend split web stack.

+ 100
- 0
docs/source-tree-analysis.md Ver fichero

@@ -0,0 +1,100 @@
# MVC-Starter - Source Tree Analysis

**Date:** 2026-03-11T11:59:39Z

## Overview

The repository is organized as a Classic ASP MVC starter with a clear split between deploy entry, shared runtime, application code, database assets, and operational tooling.

## Complete Directory Structure

```text
MVC-Starter/
├── app/ # Application-specific MVC layer
│ ├── controllers/ # Controllers and controller includes
│ ├── models/ # Intended POBO/model location (currently empty)
│ ├── repositories/ # Intended repository location (currently empty)
│ └── views/ # Server-rendered ASP views
│ ├── Error/
│ ├── Home/
│ └── shared/
├── core/ # Framework/runtime libraries and dispatcher
├── db/ # Database file and future migrations
│ ├── migrations/
│ └── webdata.accdb
├── docs/ # Generated brownfield documentation
├── public/ # IIS web root and request entrypoint
│ ├── Default.asp
│ └── web.config
├── scripts/ # Scaffolding and migration tooling
├── _bmad/ # Installed BMAD framework assets
├── _bmad-output/ # Generated BMAD workflow artifacts
└── .github/ # Copilot/BMAD prompt and agent support files
```

## Critical Directories

### `public/`

Purpose: deploy-facing web root and request bootstrap.

- Contains the front controller `Default.asp`
- Contains `web.config` with rewrite and runtime config
- Entry point for all non-static requests

### `core/`

Purpose: shared framework/runtime layer.

- Contains dispatcher, router, controller registry, helpers, DAL, migrations, flash helpers, and utility libraries
- High-blast-radius area for architectural changes

### `app/`

Purpose: project-specific extension surface.

- `controllers/` contains active controllers and include registration
- `views/` contains page templates and shared layout files
- `models/` and `repositories/` are intended generator targets

### `db/`

Purpose: database storage and schema evolution support.

- Includes `webdata.accdb`
- Includes `migrations/` directory for migration files

### `scripts/`

Purpose: development and operations support tooling.

- Controller generator
- Repository/POBO generator
- Migration generator
- Migration runner

## Entry Points

- Main request entry: `public/Default.asp`
- Runtime bootstrap: `core/autoload_core.asp`
- MVC dispatcher: `core/mvc.asp`
- Migration tooling entry: `scripts/runMigrations.vbs`

## Configuration Files

- `public/web.config` - IIS routing and runtime application settings
- `README.md` - setup and workflow guidance
- `.github/copilot-instructions.md` - BMAD/Copilot workflow guidance for repo maintenance

## File Organization Patterns

- Request handling starts in `public/`, not `app/`
- Shared reusable runtime code lives in `core/`
- Feature-specific behavior belongs in `app/`
- Operational scripts live outside the runtime in `scripts/`
- Generated documentation is isolated under `docs/`

## Development Notes

- This structure assumes IIS deployment with `public/` as the site root.
- Future application growth is expected to add controllers, views, models, repositories, and migrations without changing the overall directory shape.

+ 26
- 0
docs/state-management-patterns-mvc-starter.md Ver fichero

@@ -0,0 +1,26 @@
# State Management Patterns - MVC-Starter

**Date:** 2026-03-11T11:59:39Z

## Overview

This project does not implement client-side application state management in the SPA sense. State is primarily handled on the server/request side through controller properties, request data, and helper libraries.

## Observed Patterns

- **Request-scoped flow:** Incoming requests are routed and dispatched per request through `public/Default.asp` and `core/mvc.asp`.
- **Controller-held page state:** Controllers expose simple properties such as `useLayout` and `Title` to influence rendering behavior.
- **Flash messaging:** Shared layout calls `Flash().ShowErrorsIfPresent` and `Flash().ShowSuccessIfPresent`, indicating transient server-side UI messaging.
- **Configuration-driven behavior:** Runtime flags and UI behavior are sourced from `public/web.config` via `GetAppSetting`.
- **Form/cache helpers available:** `core/lib.FormCache.asp` and related helpers suggest server-side request/form support patterns.

## What Is Not Present

- No Redux, Context API, Vuex, MobX, Zustand, or similar client-state library
- No SPA store layer
- No separate API/client state synchronization layer

## Brownfield Notes

- Treat this app as server-rendered and request-driven.
- If richer client-side interactivity is added later, it will be a new architectural layer rather than an extension of an existing state-management system.

+ 28
- 0
docs/technology-stack.md Ver fichero

@@ -0,0 +1,28 @@
# Technology Stack

**Date:** 2026-03-11T11:59:39Z

## Part: MVC-Starter

| Category | Technology | Version / Variant | Justification |
|---|---|---|---|
| Runtime | Classic ASP | legacy IIS-hosted runtime | Request entrypoint is `public/Default.asp`, with `.asp` controllers, views, and framework files across `app/` and `core/`. |
| Language | VBScript | Classic ASP server-side VBScript | Controllers, framework code, helpers, and operational scripts are written in VBScript. |
| Web Server | IIS | Windows IIS with URL Rewrite | `public/web.config` configures default document behavior and rewrite rules to route requests through `Default.asp`. |
| MVC Framework | RouteKit Classic ASP | project-local framework/starter | README identifies RouteKit Classic ASP; routing and dispatch are implemented by `router.wsc`, `core/mvc.asp`, and controller registry logic. |
| Routing | RouteKit router + IIS rewrite | custom runtime routing | `router.AddRoute` is used in `public/Default.asp`, and incoming requests are rewritten by IIS before dispatch. |
| Data Access | ADO / OLE DB | Classic ASP ADODB stack | DAL and repository generation rely on ADO connections and Classic ASP data access libraries. |
| Database | Microsoft Access | `Microsoft.ACE.OLEDB.12.0` provider | `public/web.config` configures an `.accdb` connection string; `db/webdata.accdb` is included in the repo. |
| Database Migration | Custom VBScript migration runner | project-local | `scripts/runMigrations.vbs` manages migration application, rollback, and status outside IIS. |
| UI Framework | Bootstrap | 5.3.3 | Shared header loads Bootstrap CSS and JS from CDN for layout and components. |
| Icon Library | Bootstrap Icons | 1.11.3 | Shared header loads Bootstrap Icons from CDN. |
| Frontend Rendering | Server-rendered HTML views | Classic ASP include-based rendering | Controllers include `.asp` view files, with shared header/footer layout wrapping page content. |
| Tooling | Windows Script Host VBScript scripts | project-local generators | `scripts/generateController.vbs`, `GenerateRepo.vbs`, `generateMigration.vbs`, and `runMigrations.vbs` drive scaffolding and DB workflow. |
| Configuration | IIS `web.config` + appSettings | XML-based runtime config | Connection strings, environment flags, cache settings, flash timing, and other behavior are driven from `public/web.config`. |

## Stack Notes

- This is a server-rendered web app, not a separate frontend/backend split stack.
- The repo has no `package.json`, `requirements.txt`, or other modern package manifest for the application itself.
- Frontend assets are primarily CDN-hosted Bootstrap resources plus local static assets served from `public/`.
- Development and operational tooling assume a Windows environment with IIS and Windows Script Host.

+ 50
- 0
docs/ui-component-inventory-mvc-starter.md Ver fichero

@@ -0,0 +1,50 @@
# UI Component Inventory - MVC-Starter

**Date:** 2026-03-11T11:59:39Z

## Overview

The current UI surface is small and server-rendered. Reuse is primarily achieved through shared layout files and Bootstrap utility/component classes rather than a formal component library.

## Shared UI Surfaces

### Shared Layout

- `app/views/shared/header.asp`
- Sets page charset/codepage
- Resolves the page title from `CurrentController.Title`
- Loads Bootstrap 5.3.3 and Bootstrap Icons 1.11.3 from CDN
- Renders the top navigation shell
- Displays flash error/success messages

- `app/views/shared/footer.asp`
- Closes the main layout wrapper
- Loads the Bootstrap JS bundle from CDN

## Feature Views

### Home View

- `app/views/Home/index.asp`
- Welcome/placeholder landing page
- Uses Bootstrap cards, grid, typography, and code snippets
- Documents where new controllers, repositories, and views belong

### Error View

- `app/views/Error/NotFound.asp`
- 404 page with countdown redirect behavior
- Uses Bootstrap card layout and iconography
- Reads redirect timing from config via `GetAppSetting("Error404RedirectSeconds")`

## Reusable UI Patterns

- Shared header/footer layout wrapping
- Bootstrap-based cards and grid layout
- Config-driven flash messages in shared layout
- Simple server-rendered page title convention via controller property

## Brownfield Notes

- Reuse is currently layout-oriented, not component-library oriented.
- New UI work will likely create additional `.asp` view files under `app/views/` rather than reusable frontend components in a JS framework.

+ 13
- 0
docs/user-provided-context.md Ver fichero

@@ -0,0 +1,13 @@
# User-Provided Context

**Date:** 2026-03-11T11:59:39Z

No additional documents, paths, or focus areas were provided by the user for this documentation run.

## Implication for This Scan

The scan should prioritize:

- the main application structure under `public/`, `core/`, `app/`, `db/`, and `scripts/`
- existing first-party guidance in `README.md`
- BMAD-related project instructions only where they affect project understanding or AI-assisted maintenance

+ 190
- 0
tests/PlainRunnerTheme.asp Ver fichero

@@ -0,0 +1,190 @@
<%
Class PlainRunnerTheme
Public Sub Render(ByRef objRunner)
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>MVC-Starter Test Runner</title>
<style type="text/css">
body {
font-family: Arial, sans-serif;
margin: 24px;
color: #1f2933;
background: #f7fafc;
}

h1 {
margin-bottom: 8px;
}

.summary {
margin-bottom: 16px;
padding: 12px 16px;
background: #ffffff;
border: 1px solid #d9e2ec;
}

.page {
margin-bottom: 16px;
padding: 12px 16px;
border: 1px solid #bcccdc;
background: #ffffff;
}

.page.pass {
border-left: 6px solid #2f855a;
}

.page.fail {
border-left: 6px solid #c53030;
}

.module {
margin-top: 12px;
padding-top: 8px;
border-top: 1px solid #e2e8f0;
}

.test {
margin: 6px 0;
}

.pass-text {
color: #2f855a;
}

.fail-text {
color: #c53030;
}

code {
background: #edf2f7;
padding: 2px 4px;
}
</style>
</head>
<body>
<h1>MVC-Starter Test Runner</h1>
<p>Dev-only aspunit runner for the separate <code>tests/</code> IIS application.</p>

<div id="summary" class="summary">Running tests...</div>
<div id="results"></div>

<script type="text/javascript">
(function() {
var pages = [<%= GetPagesAsJSArray(objRunner.Pages) %>];
var summaryEl = document.getElementById("summary");
var resultsEl = document.getElementById("results");
var totals = { pages: 0, passedPages: 0, tests: 0, passedTests: 0 };

function escapeHtml(value) {
return String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}

function renderModule(module) {
var testsHtml = module.tests.map(function(test) {
return '<div class="test ' + (test.passed ? 'pass-text' : 'fail-text') + '">' +
'<strong>' + escapeHtml(test.name) + ':</strong> ' +
escapeHtml(test.description || '') +
'</div>';
}).join("");

return '<div class="module">' +
'<div><strong>' + escapeHtml(module.name) + '</strong> (' + module.passCount + '/' + module.testCount + ')</div>' +
testsHtml +
'</div>';
}

function renderPage(page, data, error) {
var wrapper = document.createElement("div");
wrapper.className = "page " + (error || !data.passed ? "fail" : "pass");

if (error) {
wrapper.innerHTML =
'<div><strong>' + escapeHtml(page) + '</strong></div>' +
'<div class="fail-text">' + escapeHtml(error) + '</div>';
resultsEl.appendChild(wrapper);
return;
}

wrapper.innerHTML =
'<div><strong>' + escapeHtml(page) + '</strong> - ' +
(data.passed ? '<span class="pass-text">PASS</span>' : '<span class="fail-text">FAIL</span>') +
' (' + data.passCount + '/' + data.testCount + ')</div>' +
data.modules.map(renderModule).join("");

resultsEl.appendChild(wrapper);
}

function updateSummary(done) {
summaryEl.innerHTML =
'<strong>Pages:</strong> ' + totals.passedPages + '/' + totals.pages +
' &nbsp; <strong>Tests:</strong> ' + totals.passedTests + '/' + totals.tests +
(done ? '' : ' &nbsp; <em>Running...</em>');
}

function next(index) {
if (index >= pages.length) {
updateSummary(true);
return;
}

var page = pages[index];
fetch(page + '?task=test', { credentials: 'same-origin' })
.then(function(response) {
if (!response.ok) {
throw new Error('HTTP ' + response.status + ' while loading ' + page);
}
return response.json();
})
.then(function(data) {
totals.pages += 1;
totals.tests += data.testCount;
totals.passedTests += data.passCount;
if (data.passed) {
totals.passedPages += 1;
}

renderPage(page, data, null);
updateSummary(false);
next(index + 1);
})
.catch(function(error) {
totals.pages += 1;
renderPage(page, null, error.message);
updateSummary(false);
next(index + 1);
});
}

updateSummary(false);
next(0);
})();
</script>
</body>
</html>
<%
End Sub

Private Function GetPagesAsJSArray(ByRef pages)
Dim strReturn, i

strReturn = ""
For i = 0 To (pages.Count - 1)
strReturn = strReturn & """" & Replace(pages.Item(i), """", "\""") & """"
If i < (pages.Count - 1) Then
strReturn = strReturn & ", "
End If
Next

GetPagesAsJSArray = strReturn
End Function
End Class
%>

+ 21
- 0
tests/aspunit/LICENSE-MIT Ver fichero

@@ -0,0 +1,21 @@
The MIT License

Copyright (c) 2013 R. Peter Clark, Inc. http://www.rpeterclark.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 12
- 0
tests/aspunit/Lib/ASPUnit.asp Ver fichero

@@ -0,0 +1,12 @@
<!-- #include file="classes/ASPUnitLibrary.asp" -->
<!-- #include file="classes/ASPUnitTester.asp" -->
<!-- #include file="classes/ASPUnitRunner.asp" -->
<!-- #include file="classes/ASPUnitUIModern.asp" -->
<!-- #include file="classes/ASPUnitJSONResponder.asp" -->

<%
Dim ASPUnit
Set ASPUnit = New ASPUnitLibrary

ASPUnit.Task = Request.QueryString("task")
%>

+ 123
- 0
tests/aspunit/Lib/classes/ASPUnitJSONResponder.asp Ver fichero

@@ -0,0 +1,123 @@
<%
Class ASPUnitJSONResponder
Private _
m_Serializer

Private Sub Class_Initialize()
Set m_Serializer = New ASPUnitScenarioSerializer
End Sub

Private Sub Class_Terminate()
Set m_Serializer = Nothing
End Sub

Public Property Set Serializer(objValue)
Set m_Serializer = objValue
End Property

Public Sub Respond(objModules)
Response.ContentType = "application/json"
Response.Write m_Serializer.ToJSON(objModules)
End Sub
End Class

' Private Classes

Class ASPUnitScenarioSerializer
Function ToJSON(objScenario)
Dim objStream, _
objModule, _
objTest, _
i, j, _
strReturn

Set objStream = Server.CreateObject("ADODB.Stream")

objStream.Type = 2 ' adTypeText
objStream.Mode = 3 ' adModeReadWrite
objStream.Open

Call objStream.WriteText("{")
Call objStream.WriteText(JSONNumberPair("testCount", objScenario.TestCount) & ",")
Call objStream.WriteText(JSONNumberPair("passCount", objScenario.PassCount) & ",")
Call objStream.WriteText(JSONBooleanPair("passed", objScenario.Passed) & ",")
Call objStream.WriteText(JSONString("modules") & ":[")
For i = 0 To (objScenario.Modules.Count - 1)
Set objModule = objScenario.Modules.Item(i)

Call objStream.WriteText("{")
Call objStream.WriteText(JSONStringPair("name", objModule.Name) & ",")
Call objStream.WriteText(JSONNumberPair("testCount", objModule.TestCount) & ",")
Call objStream.WriteText(JSONNumberPair("passCount", objModule.PassCount) & ",")
Call objStream.WriteText(JSONNumberPair("failCount", (objModule.FailCount)) & ",")
Call objStream.WriteText(JSONBooleanPair("passed", objModule.Passed) & ",")
Call objStream.WriteText(JSONNumberPair("duration", objModule.Duration) & ",")
Call objStream.WriteText(JSONString("tests") & ":[")
For j = 0 To (objModule.Tests.Count - 1)
Set objTest = objModule.Tests.Item(j)
Call objStream.WriteText("{")
Call objStream.WriteText(JSONStringPair("name", objTest.Name) & ",")
Call objStream.WriteText(JSONBooleanPair("passed", objTest.Passed) & ",")
Call objStream.WriteText(JSONStringPair("description", objTest.Description))
Call objStream.WriteText("}")

If j < (objModule.Tests.Count - 1) Then
Call objStream.WriteText(",")
End If

Set objTest = Nothing
Next
Call objStream.WriteText("]")
Call objStream.WriteText("}")

If i < (objScenario.Modules.Count - 1) Then
Call objStream.WriteText(",")
End If

Set objModule = Nothing
Next
Call objStream.WriteText("]")
Call objStream.WriteText("}")

objStream.Position = 0

strReturn = objStream.ReadText()

objStream.Close
Set objStream = Nothing

ToJSON = strReturn
End Function

Private Function JSONString(strValue)
JSONString = """" & JSONStringEscape(strValue) & """"
End Function

Private Function JSONStringPair(strName, strValue)
JSONStringPair = JSONString(strName) & ":" & JSONString(strValue)
End Function

Private Function JSONNumberPair(strName, varValue)
JSONNumberPair = JSONString(strName) & ":" & varValue
End Function

Private Function JSONBooleanPair(strName, blnValue)
JSONBooleanPair = JSONString(strName) & ":" & LCase(blnValue)
End Function

Private Function JSONStringEscape(strValue)
Dim strReturn

strReturn = strValue

strReturn = Replace(strReturn, "\", "\\")
strReturn = Replace(strReturn, """", "\""")
strReturn = Replace(strReturn, vbLf, "\n")
strReturn = Replace(strReturn, vbCr, "\n")
strReturn = Replace(strReturn, vbCrLf, "\n")
strReturn = Replace(strReturn, vbTab, "\t")

JSONStringEscape = strReturn
End Function
End Class
%>

+ 104
- 0
tests/aspunit/Lib/classes/ASPUnitLibrary.asp Ver fichero

@@ -0,0 +1,104 @@
<%
Class ASPUnitLibrary
Private _
m_Runner, _
m_Tester, _
m_Task

Public _
Version

Private Sub Class_Initialize()
Version = "0.1.0"

Set m_Runner = New ASPUnitRunner
Set m_Tester = New ASPUnitTester
End Sub

Private Sub Class_Terminate()
Set m_Runner = Nothing
Set m_Tester = Nothing
End Sub

Public Property Set Runner(ByRef objValue)
Set m_Runner = objValue
End Property

Public Property Set Tester(ByRef objValue)
Set m_Tester = objValue
End Property

Public Property Let Task(strTask)
m_Task = strTask
End Property

Public Sub Run()
Select Case UCase(m_Task)
Case "TEST"
Call m_Tester.Run()
Case Else
Call m_Runner.Run()
End Select
End Sub

' Test Service Facade

Public Property Set Responder(ByRef objValue)
Set m_Tester.Responder = objValue
End Property

Public Function CreateModule(strName, arrTests, objLifecycle)
Set CreateModule = m_Tester.CreateModule(strName, arrTests, objLifecycle)
End Function

Public Function CreateTest(strName)
Set CreateTest = m_Tester.CreateTest(strName)
End Function

Public Function CreateLifecycle(strSetup, strTeardown)
Set CreateLifecycle = m_Tester.CreateLifecycle(strSetup, strTeardown)
End Function

Public Sub AddModule(objModule)
Call m_Tester.AddModule(objModule)
End Sub

Public Sub AddModules(arrModules)
Call m_Tester.AddModules(arrModules)
End Sub

Public Function Ok(blnResult, strDescription)
Call m_Tester.Ok(blnResult, strDescription)
End Function

Public Function Equal(varActual, varExpected, strDescription)
Call m_Tester.Equal(varActual, varExpected, strDescription)
End Function

Public Function NotEqual(varActual, varExpected, strDescription)
Call m_Tester.NotEqual(varActual, varExpected, strDescription)
End Function

Public Function Same(varActual, varExpected, strDescription)
Call m_Tester.Same(varActual, varExpected, strDescription)
End Function

Public Function NotSame(varActual, varExpected, strDescription)
Call m_Tester.NotSame(varActual, varExpected, strDescription)
End Function

' UI Service Facade

Public Property Set Theme(ByRef objValue)
Set m_Runner.Theme = objValue
End Property

Public Sub AddPage(strPage)
Call m_Runner.AddPage(strPage)
End Sub

Public Sub AddPages(arrPages)
Call m_Runner.AddPages(arrPages)
End Sub
End Class
%>

+ 174
- 0
tests/aspunit/Lib/classes/ASPUnitRunner.asp Ver fichero

@@ -0,0 +1,174 @@
<%
Class ASPUnitRunner
Private _
m_Theme, _
m_Pages

Private Sub Class_Initialize()
Set m_Theme = New ASPUnitUIModern
Set m_Pages = Server.CreateObject("System.Collections.ArrayList")
End Sub

Private Sub Class_Terminate()
Set m_Pages = Nothing
Set m_Theme = Nothing
End Sub

Public Property Set Theme(ByRef objValue)
Set m_Theme = objValue
End Property

Public Property Get Pages
Set Pages = m_Pages
End Property

' Public methods to specify test pages

Public Sub AddPage(strPage)
Call m_Pages.Add(strPage)
End Sub

Public Sub AddPages(arrPages)
Dim i

For i = 0 To UBound(arrPages)
Call AddPage(arrPages(i))
Next
End Sub

' Method to run UI

Public Sub Run()
If m_Pages.Count = 0 Then
Call AddCurrentPage()
End If

Call m_Theme.Render(Me)
End Sub

Private Sub AddCurrentPage()
Call AddPage(Request.ServerVariables("URL"))
End Sub

Public Sub RenderJSLib() %>
<script>
var ASPUnit = function() {
'use strict';

var config = {
pages: [],
callbacks: {
onStart: [],
onStop: [],
onPageStart: [],
onPageSuccess: [],
onPageFail: [],
onPageFinish: [],
onFinish: [],
}
}

var getPageTimeout = null,
getPageXHR = null;

var status = {
pageIndex: 0,
pageCount: 0,
testCount: 0,
passCount: 0
};

function registerCallback(key, callback) {
config.callbacks[key].push(callback);
}

function callback(key, args) {
var callbacks = config.callbacks[key];
for (var i = 0, len = callbacks.length; i < len; i++) {
callbacks[i].call({}, args);
}
}

function getPage(page) {
callback('onPageStart', {page: page});

getPageXHR = $.getJSON(page + '?task=test')
.done(function(data) {
status.pageDoneCount++;
status.testCount += data.testCount;
status.passCount += data.passCount;

callback('onPageSuccess', $.extend({page: page}, data));
})
.fail(function(jqXHR, textStatus, errorThrown) {
callback('onPageFail', {
page: page,
error: errorThrown,
description: (jqXHR.status != 404) ? jqXHR.responseText : ''
});
})
.always(function() {
callback('onPageFinish', {
page: page,
status: status
});

if (status.pageIndex < (config.pages.length - 1)) {
getPageTimeout = setTimeout(function() {
getPage(config.pages[++status.pageIndex]);
}, 100);
} else {
callback('onFinish');
}
});
}

return {
load: function(pages) {
config.pages = pages;
status.pageCount = config.pages.length;
this.start();
},

start: function() {
callback('onStart');
getPage(config.pages[status.pageIndex]);
},

stop: function() {
getPageXHR.abort();
clearTimeout(getPageTimeout);
callback('onStop');
},

onStart: function(callback) { registerCallback('onStart', callback); },
onPageStart: function(callback) { registerCallback('onPageStart', callback); },
onPageSuccess: function(callback) { registerCallback('onPageSuccess', callback); },
onPageFail: function(callback) { registerCallback('onPageFail', callback); },
onPageFinish: function(callback) { registerCallback('onPageFinish', callback); },
onFinish: function(callback) { registerCallback('onFinish', callback); }
};
}();
</script> <%
End Sub

Public Sub RenderJSInit() %>
<script>$(function(){ASPUnit.load([<%= GetPagesAsJSString() %>])});</script> <%
End Sub

Private Function GetPagesAsJSString()
Dim strReturn, _
i

strReturn = ""
For i = 0 To (m_Pages.Count - 1)
strReturn = strReturn & "'" & m_Pages.Item(i) & "'"
If i < (m_Pages.Count - 1) Then
strReturn = strReturn & ", "
End If
Next

GetPagesAsJSString = strReturn
End Function
End Class
%>

+ 258
- 0
tests/aspunit/Lib/classes/ASPUnitTester.asp Ver fichero

@@ -0,0 +1,258 @@
<%
Class ASPUnitTester
Private _
m_Responder, _
m_Scenario

Private _
m_CurrentModule, _
m_CurrentTest

Private Sub Class_Initialize()
Set m_Responder = New ASPUnitJSONResponder
Set m_Scenario = New ASPUnitScenario
End Sub

Private Sub Class_Terminate()
Set m_Scenario = Nothing
Set m_Responder = Nothing
End Sub

Public Property Set Responder(ByRef objValue)
Set m_Responder = objValue
End Property

Public Property Get Modules()
Set Modules = m_Scenario.Modules
End Property

' Factory methods for private classes

Public Function CreateModule(strName, arrTests, objLifecycle)
Dim objReturn, _
i

Set objReturn = New ASPUnitModule
objReturn.Name = strName
For i = 0 To UBound(arrTests)
objReturn.Tests.Add(arrTests(i))
Next
Set objReturn.Lifecycle = objLifecycle

Set CreateModule = objReturn
End Function

Public Function CreateTest(strName)
Dim objReturn

Set objReturn = New ASPUnitTest
objReturn.Name = strName

Set CreateTest = objReturn
End Function

Public Function CreateLifecycle(strSetup, strTeardown)
Dim objReturn

Set objReturn = New ASPUnitTestLifecycle
objReturn.Setup = strSetup
objReturn.Teardown = strTeardown

Set CreateLifecycle = objReturn
End Function

' Public methods to add modules

Public Sub AddModule(objModule)
Call m_Scenario.Modules.Add(objModule)
End Sub

Public Sub AddModules(arrModules)
Dim i

For i = 0 To UBound(arrModules)
Call AddModule(arrModules(i))
Next
End Sub

' Assertion Methods

Private Function Assert(blnResult, strDescription)
If IsObject(m_CurrentTest) Then
m_CurrentTest.Passed = blnResult
m_CurrentTest.Description = strDescription
End If

Assert = blnResult
End Function

Public Function Ok(blnResult, strDescription)
Ok = Assert(blnResult, strDescription)
End Function

Public Function Equal(varActual, varExpected, strDescription)
Equal = Assert((varActual = varExpected), strDescription)
End Function

Public Function NotEqual(varActual, varExpected, strDescription)
NotEqual = Assert(Not (varActual = varExpected), strDescription)
End Function

Public Function Same(varActual, varExpected, strDescription)
Same = Assert((varActual Is varExpected), strDescription)
End Function

Public Function NotSame(varActual, varExpected, strDescription)
NotSame = Assert(Not (varActual Is varExpected), strDescription)
End Function

' Methods to run module tests

Public Sub Run()
Dim objModule, _
i

For i = 0 To (m_Scenario.Modules.Count - 1)
Set objModule = m_Scenario.Modules.Item(i)
Call RunModule(objModule)

m_Scenario.TestCount = m_Scenario.TestCount + objModule.TestCount
m_Scenario.PassCount = m_Scenario.PassCount + objModule.PassCount
m_Scenario.FailCount = m_Scenario.FailCount + objModule.FailCount

Set objModule = Nothing
Next

m_Responder.Respond(m_Scenario)
End Sub

Private Sub RunModule(ByRef objModule)
Dim intTimeStart, _
intTimeEnd, _
objTest, _
i

Set m_CurrentModule = objModule

intTimeStart = Timer
For i = 0 To (objModule.Tests.Count - 1)
Set objTest = objModule.Tests.Item(i)

Call RunTestModuleSetup(objModule)
Call RunModuleTest(objTest)
Call RunTestModuleTeardown(objModule)

If objTest.Passed Then
objModule.PassCount = objModule.PassCount + 1
End If

Set objTest = Nothing
Next
intTimeEnd = Timer

objModule.Duration = Round((intTimeEnd - intTimestart), 3)

Set m_CurrentModule = Nothing
End Sub

Private Sub RunModuleTest(ByRef objTest)
Set m_CurrentTest = objTest

On Error Resume Next
Call GetRef(objTest.Name)()

If Err.Number <> 0 Then
Call Assert(False, Err.Description)
End If

Err.Clear()
On Error Goto 0

Set m_CurrentTest = Nothing
End Sub

Private Sub RunTestModuleSetup(ByRef objModule)
If Not objModule.Lifecycle Is Nothing Then
If Not objModule.Lifecycle.Setup = Empty Then
Call GetRef(objModule.Lifecycle.Setup)()
End If
End If
End Sub

Private Sub RunTestModuleTeardown(ByRef objModule)
If Not objModule.Lifecycle Is Nothing Then
If Not objModule.Lifecycle.Teardown = Empty Then
Call GetRef(objModule.Lifecycle.Teardown)()
End If
End If
End Sub
End Class

' Private Classses

Class ASPUnitScenario
Public _
Modules, _
TestCount, _
PassCount, _
FailCount

Private Sub Class_Initialize()
Set Modules = Server.CreateObject("System.Collections.ArrayList")
PassCount = 0
TestCount = 0
FailCount = 0
End Sub

Private Sub Class_Terminate()
Set Modules = Nothing
End Sub

Public Property Get Passed
Passed = (PassCount = TestCount)
End Property
End Class

Class ASPUnitModule
Public _
Name, _
Tests, _
Lifecycle, _
Duration, _
PassCount

Private Sub Class_Initialize()
Set Tests = Server.CreateObject("System.Collections.ArrayList")
PassCount = 0
End Sub

Private Sub Class_Terminate()
Set Tests = Nothing
End Sub

Public Property Get TestCount
TestCount = Tests.Count()
End Property

Public Property Get FailCount
FailCount = (TestCount - PassCount)
End Property

Public Property Get Passed
Passed = (PassCount = TestCount)
End Property
End Class

Class ASPUnitTest
Public _
Name, _
Passed, _
Description
End Class

Class ASPUnitTestLifecycle
Public _
Setup, _
Teardown
End Class
%>

+ 395
- 0
tests/aspunit/Lib/classes/ASPUnitUIModern.asp Ver fichero

@@ -0,0 +1,395 @@
<%
Class ASPUnitUIModern
Public Sub Render(ByRef objRunner) %>
<html>
<head>
<title>ASPUnit</title>

<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.min.css" rel="stylesheet">
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600,700' rel='stylesheet' type='text/css'>

<style type="text/css">
body {
background-color: #ECF0F1;
padding-top: 280px;
font-family: 'Open Sans', sans-serif;
-webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
font-family: 'Open Sans', sans-serif;
}

a {
color: #95a5a6;
transition: all 0.25s;
}

a:hover {
color: #fff;
text-decoration: none;
}

.project-name {
color: #fff !important;
}

#footer .project-name {
margin-top: 0;
margin-bottom: 0;
}

#container {
position: relative;
height: auto;
min-height: 100%;
}

#header {
color: #ecf0f1;
background-color: #13202c;
transition: all 0.5s;
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 1000;
text-shadow: 0px 2px 1px #000;
}

#header a {
color: #ecf0f1;
}

#header a:active {
position: relative;
top: 1px;
text-shadow: 0px 1px 1px #000;
}

#header.affix {
padding-top: 0;
padding-bottom: 0.2em;
}

#main {
padding-bottom: 12em;
}

#footer {
position: absolute;
bottom: 0;
width: 100%;
padding-top: 2em;
padding-bottom: 2em;
margin-top: 4em;
color: #95a5a6;
background-color: #13202c;
}

.progress-loading {
box-shadow: 0 2px 6px RGBA(0,0,0,0.25);
border-bottom: 2px #444 solid;
border-radius: 0 0 2px 2px;
}

.pages-overview {
position: relative;
}

.pages-status {
display: inline-block;
}

.pages-options {
display: inline-block;
position: absolute;
right: 0;
}

.page-report .page-overview {
color: #7F8C8D;
}

.page-report .page-overview small {
color: #95A5A6;
}

.page-module {
margin-bottom: 1em;
box-shadow: 0 2px 6px RGBA(0,0,0,0.25);
}

.page-module .page-module-header {
color: #fff;
padding: 1em 2em;
}

.page-module-pass .page-module-header {
background-color: #2ECC71;
border: 1px #27AE60 solid;
}

.page-module-fail .page-module-header {
background-color: #E74C3C;
border: 1px #C0392B solid;
}

.page-module-name {
font-weight: bold;
margin: 0;
}

.page-module-tests {
border-left: 1px #95A5A6 solid;
border-right: 1px #95A5A6 solid;
border-bottom: 3px #95A5A6 solid;
border-radius: 0 0 3px 3px;
}

.page-module-test {
padding: 0.8em 2em;
background-color: #fff;
margin-bottom: 1px;
position: relative;
}

.page-module-test-icon {
position: absolute;
top: 0.5em;
left: -11px;
color: #fff;
width: 22px;
height: 22px;
padding: 3px 4px;
border-radius: 11px;
}

.page-module-test-icon-pass {
box-shadow: 0 3px 0 #27AE60, 0 6px 3px RGBA(0,0,0,0.25);
background-color: #2ECC71;
}

.page-module-test-icon-fail {
box-shadow: 0 3px 0 #C0392B, 0 6px 3px RGBA(0,0,0,0.25);
background-color: #E74C3C;
}

.page-module-test-name {
font-weight: bold;
}

.page-module-test-fail .page-module-test-name, .page-module-test-fail .page-module-test-description {
color: #C0392B;
}

@media (max-width: 768px) {
body {
padding-top: 260px;
}

#main {
padding-bottom: 15em;
}

.pages-options {
display: inline-block;
position: relative;
}
}
</style>
</head>
<body>
<div id="container">
<div id="header" class="jumbotron" data-spy="affix" data-offset-top="10">
<div class="container">
<h1 class="project-name"><strong>ASP</strong>Unit</h1>

<div class="pages-overview">
<div class="pages-status"></div>

<div class="pages-options">
<div class="pages-option-passed-tests">
<a href="#" class="action-hide-passed"><i class="glyphicon glyphicon-remove-sign"></i> Hide Passed Tests</a>
</div>
</div>

<div class="progress progress-striped progress-loading">
<div class="progress-bar progress-bar-success" role="progressbar"></div>
<div class="progress-bar progress-bar-danger" role="progressbar"></div>
</div>

<div class="alert alert-danger progress-error" style="display: none;"></div>
</div>
</div>
</div>

<div id="main">
<div class="page-reports"></div>
</div>

<div id="footer">
<div class="container">
<div class="row">
<div class="col-md-12">
<h3 class="project-name"><strong>ASP</strong>Unit</h3>
</div>
</div>
<div class="row">
<div class="col-md-4">
Classic ASP Unit Testing Library<br />
</div>
<div class="col-md-4">
<ul class="list-unstyled">
<li><a href="https://github.com/rpeterclark/aspunit/"><i class="icon-github"></i> GitHub Project</a></li>
<li><a href="https://github.com/rpeterclark/aspunit/wiki/"><i class="icon-book"></i> Documentation</a></li>
<li><a href="https://github.com/rpeterclark/aspunit/issues/"><i class="icon-bug"></i> Issues</a></li>
</ul>
</div>
<div class="col-md-4">
MIT Licensed
</div>
</div>
</div>
</div>
</div>

<script id="page-status-template" type="text/x-handlebars-template">
{{pageNumber}} of {{pageCount}} pages tested, {{passCount}} of {{testCount}} tests passed
</script>

<script id="page-report-template" type="text/x-handlebars-template">
<div class="page-report container">
<div class="page-overview">
<h3>{{page}} <small>{{passCount}} of {{testCount}} tests passed</small></h3>
</div>
{{#each modules}}
<div class="page-module page-module-{{#if passed}}pass{{else}}fail{{/if}}">
<div class="page-module-header">
<h4 class="page-module-name">{{name}}</h4>
{{passCount}} of {{testCount}} tests passed, {{failCount}} failed, completed in {{duration}} milliseconds
</div>

<div class="page-module-tests">
{{#each tests}}
<div class="page-module-test page-module-test-{{#if passed}}pass{{else}}fail{{/if}}">
{{#if passed}}
<i class="page-module-test-icon page-module-test-icon-pass glyphicon glyphicon-ok"></i>
{{else}}
<i class="page-module-test-icon page-module-test-icon-fail glyphicon glyphicon-remove"></i>
{{/if}}
<span class="page-module-test-name">{{name}}:</span>
<span class="page-module-test-description">{{description}}</span>
</div>
{{/each}}
</div>
</div>
{{/each}}
</div>
</script>

<script id="page-error-template" type="text/x-handlebars-template">
<div class="page-report container">
<div class="page-module page-module-fail">
<div class="page-module-header">
<h4 class="page-module-name">{{page}}</h4>
</div>

<div class="page-module-tests">
<div class="page-module-test page-module-test-fail">
<i class="page-module-test-icon page-module-test-icon-fail glyphicon glyphicon-remove"></i>
<span class="page-module-test-name">{{error}}{{#if description}}:{{/if}}</span>
<span class="page-module-test-description">{{description}}</span>
</div>
</div>
</div>
</div>
</div>
</script>

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/js/bootstrap.min.js"></script>

<%= objRunner.RenderJSLib() %>

<script>
$(function() {
var successCount = 0;
var pageReportsContainer = $('.page-reports');
var pageReportTemplate = Handlebars.compile($('#page-report-template').html());
var pageErrorTemplate = Handlebars.compile($('#page-error-template').html());
var pageStatusContainer = $('.pages-status');
var pageStatusTemplate = Handlebars.compile($('#page-status-template').html());
var pageReportsProgress = $('.progress-loading');
var progressError = $('.progress-error');

ASPUnit.onStart(function() {
pageReportsProgress.addClass('active');
});

ASPUnit.onPageSuccess(function(details) {
successCount++;
$(pageReportTemplate(details)).appendTo(pageReportsContainer).hide().fadeIn();
});

ASPUnit.onPageFail(function(details) {
$(pageErrorTemplate(details)).appendTo(pageReportsContainer);
});

ASPUnit.onPageFinish(function(details) {
var status = $.extend(details.status, {pageNumber: (details.status.pageIndex + 1)});

pageStatusContainer.html(pageStatusTemplate(status));

var progressPct = (status.pageNumber / status.pageCount);
var passPct = (status.passCount / status.testCount) * (successCount / status.pageNumber);
var failPct = (1 - passPct);

pageReportsProgress.find('.progress-bar-success').css({"width": ((passPct * 100) * progressPct) + "%"})
pageReportsProgress.find('.progress-bar-danger').css({"width": ((failPct * 100) * progressPct) + "%"})
});

ASPUnit.onFinish(function() {
pageReportsProgress.removeClass('active');
});

$(document).on('click', '.action-hide-passed', function(e) {
e.preventDefault();

var $el = $(e.target);

if ($el.hasClass('active')) {
$el.removeClass('active').html('<i class="glyphicon glyphicon-remove-sign"></i> Hide Passed Tests</a>');
showPassedTests();
} else {
$el.addClass('active').html('<i class="glyphicon glyphicon-ok-sign"></i> Show Passed Tests</a>');
hidePassedTests();
}
});

function hidePassedTests() {
$('.page-report').each(function() {
if ($(this).find('.page-module-test-fail').length > 0) {
$(this).find('.page-module-pass').addClass('hidden');
$(this).find('.page-module-test-pass').addClass('hidden');
} else {
$(this).addClass('hidden');
}
});
}

function showPassedTests() {
$('.hidden').removeClass('hidden');
}
});
</script>

<%= objRunner.RenderJSInit() %>
</body>
</html> <%
End Sub
End Class
%>

+ 49
- 0
tests/bootstrap.asp Ver fichero

@@ -0,0 +1,49 @@
<!-- #include file="../core/helpers.asp" -->
<!-- #include file="../core/lib.ControllerRegistry.asp" -->

<%
Dim router

Function ResolveProjectPath(relativePath)
Dim fso, currentFolder, testsRoot, projectRoot

Set fso = Server.CreateObject("Scripting.FileSystemObject")
currentFolder = Server.MapPath(".")

If LCase(fso.GetFileName(currentFolder)) = "tests" Then
testsRoot = currentFolder
Else
testsRoot = fso.GetParentFolderName(currentFolder)
End If

projectRoot = fso.GetParentFolderName(testsRoot)
ResolveProjectPath = fso.BuildPath(projectRoot, relativePath)

Set fso = Nothing
End Function

Sub ResetTestRuntime()
On Error Resume Next
ControllerRegistry_Class__Singleton = Empty
Set router = Nothing
On Error GoTo 0
End Sub

Sub EnsureTestRouter()
If (Not IsObject(router)) Then
Set router = GetObject("script:" & ResolveProjectPath("core\\router.wsc"))
ElseIf router Is Nothing Then
Set router = GetObject("script:" & ResolveProjectPath("core\\router.wsc"))
End If
End Sub

Sub RegisterDefaultRoutes()
Call EnsureTestRouter()
Call router.AddRoute("GET", "/home", "homeController", "Index")
Call router.AddRoute("GET", "/", "homeController", "Index")
Call router.AddRoute("GET", "", "homeController", "Index")
Call router.AddRoute("GET", "/404", "ErrorController", "NotFound")
End Sub

Call ResetTestRuntime()
%>

+ 43
- 0
tests/component/TestHomeController.asp Ver fichero

@@ -0,0 +1,43 @@
<!-- #include file="../aspunit/Lib/ASPUnit.asp" -->
<!-- #include file="../bootstrap.asp" -->
<!-- #include file="../../app/controllers/HomeController.asp" -->

<%
Call ASPUnit.AddModule( _
ASPUnit.CreateModule( _
"Home Controller Component Tests", _
Array( _
ASPUnit.CreateTest("HomeControllerDefaultsToUsingLayout"), _
ASPUnit.CreateTest("HomeControllerDefaultTitleIsHome") _
), _
ASPUnit.CreateLifeCycle("SetupHomeController", "TeardownHomeController") _
) _
)

Call ASPUnit.Run()

Sub SetupHomeController()
Call ResetTestRuntime()
On Error Resume Next
HomeController_Class__Singleton = Empty
On Error GoTo 0
Call ExecuteGlobal("Dim objHomeController")
Set objHomeController = HomeController()
End Sub

Sub TeardownHomeController()
Set objHomeController = Nothing
On Error Resume Next
HomeController_Class__Singleton = Empty
On Error GoTo 0
Call ResetTestRuntime()
End Sub

Function HomeControllerDefaultsToUsingLayout()
Call ASPUnit.Ok(objHomeController.useLayout, "HomeController should default to layout-enabled rendering")
End Function

Function HomeControllerDefaultTitleIsHome()
Call ASPUnit.Equal(objHomeController.Title, "Home", "HomeController should expose the expected default title")
End Function
%>

+ 18
- 0
tests/component/web.config Ver fichero

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="Environment" value="Development" />
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
</appSettings>

<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="run-all.asp" />
</files>
</defaultDocument>
</system.webServer>
</configuration>

+ 80
- 0
tests/integration/TestMvcDispatch.asp Ver fichero

@@ -0,0 +1,80 @@
<!-- #include file="../aspunit/Lib/ASPUnit.asp" -->
<!-- #include file="../bootstrap.asp" -->
<!-- #include file="../../core/mvc.asp" -->

<%
Class TestDispatchController_Class
Private m_useLayout

Private Sub Class_Initialize()
m_useLayout = False
End Sub

Public Property Get useLayout
useLayout = m_useLayout
End Property

Public Property Let useLayout(value)
m_useLayout = value
End Property

Public Sub Smoke()
dispatchActionRan = True
End Sub
End Class

Dim TestDispatchController_Class__Singleton
Function TestDispatchController()
If IsEmpty(TestDispatchController_Class__Singleton) Then
Set TestDispatchController_Class__Singleton = New TestDispatchController_Class
End If
Set TestDispatchController = TestDispatchController_Class__Singleton
End Function

Call ASPUnit.AddModule( _
ASPUnit.CreateModule( _
"MVC Dispatch Smoke Tests", _
Array( _
ASPUnit.CreateTest("RootRouteResolvesToHomeController"), _
ASPUnit.CreateTest("KnownRouteDispatchCompletesWithoutLookupFailure") _
), _
ASPUnit.CreateLifeCycle("SetupMvcDispatch", "TeardownMvcDispatch") _
) _
)

Call ASPUnit.Run()

Sub SetupMvcDispatch()
Call ResetTestRuntime()
On Error Resume Next
MVC_Dispatcher_Class__Singleton = Empty
TestDispatchController_Class__Singleton = Empty
On Error GoTo 0
Call ExecuteGlobal("Dim dispatchActionRan")
dispatchActionRan = False
Call RegisterDefaultRoutes()
Call ControllerRegistry().RegisterController("testdispatchcontroller")
Call router.AddRoute("GET", "/dispatch-smoke", "testdispatchcontroller", "Smoke")
End Sub

Sub TeardownMvcDispatch()
On Error Resume Next
MVC_Dispatcher_Class__Singleton = Empty
TestDispatchController_Class__Singleton = Empty
Response.Status = "200 OK"
On Error GoTo 0
Call ResetTestRuntime()
End Sub

Function RootRouteResolvesToHomeController()
Dim routeArray
routeArray = router.Resolve("GET", "/")

Call ASPUnit.Ok((LCase(routeArray(0)) = "homecontroller" And LCase(routeArray(1)) = "index"), "Root route should resolve to HomeController.Index")
End Function

Function KnownRouteDispatchCompletesWithoutLookupFailure()
Call MVC().DispatchRequest("GET", "/dispatch-smoke")
Call ASPUnit.Ok(dispatchActionRan, "Dispatch should reach a registered controller action without whitelist or lookup failures")
End Function
%>

+ 18
- 0
tests/integration/web.config Ver fichero

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="Environment" value="Development" />
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
</appSettings>

<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="run-all.asp" />
</files>
</defaultDocument>
</system.webServer>
</configuration>

+ 9
- 0
tests/run-all.asp Ver fichero

@@ -0,0 +1,9 @@
<!-- #include file="aspunit/Lib/ASPUnit.asp" -->
<!-- #include file="PlainRunnerTheme.asp" -->
<!-- #include file="test-manifest.asp" -->

<%
Set ASPUnit.Theme = New PlainRunnerTheme
Call RegisterTestPages()
Call ASPUnit.Run()
%>

+ 25
- 0
tests/sync-webconfigs.vbs Ver fichero

@@ -0,0 +1,25 @@
Option Explicit

Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")

Dim testsRoot
testsRoot = fso.GetParentFolderName(WScript.ScriptFullName)

Dim sourceFile
sourceFile = fso.BuildPath(testsRoot, "web.config")

If Not fso.FileExists(sourceFile) Then
WScript.Echo "Source config not found: " & sourceFile
WScript.Quit 1
End If

Call CopyConfig(sourceFile, fso.BuildPath(fso.BuildPath(testsRoot, "unit"), "web.config"))
Call CopyConfig(sourceFile, fso.BuildPath(fso.BuildPath(testsRoot, "component"), "web.config"))
Call CopyConfig(sourceFile, fso.BuildPath(fso.BuildPath(testsRoot, "integration"), "web.config"))

WScript.Echo "Mirrored test web.config files updated."

Sub CopyConfig(sourcePath, targetPath)
Call fso.CopyFile(sourcePath, targetPath, True)
End Sub

+ 10
- 0
tests/test-manifest.asp Ver fichero

@@ -0,0 +1,10 @@
<%
Sub RegisterTestPages()
Call ASPUnit.AddPages(Array( _
"unit/TestHelpers.asp", _
"unit/TestControllerRegistry.asp", _
"component/TestHomeController.asp", _
"integration/TestMvcDispatch.asp" _
))
End Sub
%>

+ 48
- 0
tests/unit/TestControllerRegistry.asp Ver fichero

@@ -0,0 +1,48 @@
<!-- #include file="../aspunit/Lib/ASPUnit.asp" -->
<!-- #include file="../bootstrap.asp" -->

<%
Call ASPUnit.AddModule( _
ASPUnit.CreateModule( _
"Controller Registry Tests", _
Array( _
ASPUnit.CreateTest("RegisteredHomeControllerIsValid"), _
ASPUnit.CreateTest("RegisteredErrorControllerIsValid"), _
ASPUnit.CreateTest("InvalidControllerFormatIsRejected"), _
ASPUnit.CreateTest("InvalidActionFormatIsRejected"), _
ASPUnit.CreateTest("UnknownControllerIsRejected") _
), _
ASPUnit.CreateLifeCycle("SetupControllerRegistry", "TeardownControllerRegistry") _
) _
)

Call ASPUnit.Run()

Sub SetupControllerRegistry()
Call ResetTestRuntime()
End Sub

Sub TeardownControllerRegistry()
Call ResetTestRuntime()
End Sub

Function RegisteredHomeControllerIsValid()
Call ASPUnit.Ok(ControllerRegistry().IsValidController("homecontroller"), "HomeController should be present in the whitelist")
End Function

Function RegisteredErrorControllerIsValid()
Call ASPUnit.Ok(ControllerRegistry().IsValidController("errorcontroller"), "ErrorController should be present in the whitelist")
End Function

Function InvalidControllerFormatIsRejected()
Call ASPUnit.Ok((Not ControllerRegistry().IsValidControllerFormat("home-controller")), "Controller names with dashes should be rejected")
End Function

Function InvalidActionFormatIsRejected()
Call ASPUnit.Ok((Not ControllerRegistry().IsValidActionFormat("show-item")), "Action names with dashes should be rejected")
End Function

Function UnknownControllerIsRejected()
Call ASPUnit.Ok((Not ControllerRegistry().IsValidController("missingcontroller")), "Unknown controllers should not pass whitelist checks")
End Function
%>

+ 65
- 0
tests/unit/TestHelpers.asp Ver fichero

@@ -0,0 +1,65 @@
<!-- #include file="../aspunit/Lib/ASPUnit.asp" -->
<!-- #include file="../bootstrap.asp" -->

<%
Call ASPUnit.AddModule( _
ASPUnit.CreateModule( _
"Helper Function Tests", _
Array( _
ASPUnit.CreateTest("TrimQueryParamsStripsQuestionString"), _
ASPUnit.CreateTest("TrimQueryParamsStripsAmpersandSuffix"), _
ASPUnit.CreateTest("TrimQueryParamsLeavesPathWithoutDelimiters"), _
ASPUnit.CreateTest("SurroundStringInArrayWrapsStringValues"), _
ASPUnit.CreateTest("SurroundStringInArrayLeavesNumericValuesUntouched"), _
ASPUnit.CreateTest("SurroundStringInArrayLeavesArraysWithoutStringsUntouched") _
), _
ASPUnit.CreateLifeCycle("SetupHelpers", "TeardownHelpers") _
) _
)

Call ASPUnit.Run()

Sub SetupHelpers()
Call ResetTestRuntime()
End Sub

Sub TeardownHelpers()
Call ResetTestRuntime()
End Sub

Function TrimQueryParamsStripsQuestionString()
Call ASPUnit.Equal(TrimQueryParams("/home?id=7"), "/home", "TrimQueryParams should remove query string values after ?")
End Function

Function TrimQueryParamsStripsAmpersandSuffix()
Call ASPUnit.Equal(TrimQueryParams("/home&debug=true"), "/home", "TrimQueryParams should remove suffix values after &")
End Function

Function TrimQueryParamsLeavesPathWithoutDelimiters()
Call ASPUnit.Equal(TrimQueryParams("/home"), "/home", "TrimQueryParams should leave clean paths unchanged")
End Function

Function SurroundStringInArrayWrapsStringValues()
Dim arr
arr = Array("alpha", 2)
arr = SurroundStringInArray(arr)

Call ASPUnit.Equal(arr(0), """alpha""", "SurroundStringInArray should wrap string items in double quotes")
End Function

Function SurroundStringInArrayLeavesNumericValuesUntouched()
Dim arr
arr = Array("alpha", 2)
arr = SurroundStringInArray(arr)

Call ASPUnit.Equal(arr(1), 2, "SurroundStringInArray should leave non-string items unchanged")
End Function

Function SurroundStringInArrayLeavesArraysWithoutStringsUntouched()
Dim arr
arr = Array(1, 2)
arr = SurroundStringInArray(arr)

Call ASPUnit.Ok((arr(0) = 1 And arr(1) = 2), "SurroundStringInArray should leave arrays without string members unchanged")
End Function
%>

+ 18
- 0
tests/unit/web.config Ver fichero

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="Environment" value="Development" />
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
</appSettings>

<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="run-all.asp" />
</files>
</defaultDocument>
</system.webServer>
</configuration>

+ 18
- 0
tests/web.config Ver fichero

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="Environment" value="Development" />
<add key="CacheExpirationYear" value="2030" />
<add key="EnableCacheBusting" value="false" />
<add key="CacheBustParamName" value="v" />
</appSettings>

<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="run-all.asp" />
</files>
</defaultDocument>
</system.webServer>
</configuration>

Cargando…
Cancelar
Guardar

Powered by TurnKey Linux.