| @@ -0,0 +1,4 @@ | |||
| /_bmad | |||
| /_bmad* | |||
| /.agents | |||
| /.github | |||
| @@ -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 | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -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/` | |||
| @@ -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`. | |||
| @@ -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. | |||
| @@ -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`. | |||
| @@ -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) | |||
| @@ -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" | |||
| ] | |||
| } | |||
| ] | |||
| } | |||
| @@ -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."} | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -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, "&") | |||
| .replace(/</g, "<") | |||
| .replace(/>/g, ">") | |||
| .replace(/"/g, """) | |||
| .replace(/'/g, "'"); | |||
| } | |||
| 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 + | |||
| ' <strong>Tests:</strong> ' + totals.passedTests + '/' + totals.tests + | |||
| (done ? '' : ' <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 | |||
| %> | |||
| @@ -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. | |||
| @@ -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") | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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() | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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> | |||
| @@ -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 | |||
| %> | |||
| @@ -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> | |||
| @@ -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() | |||
| %> | |||
| @@ -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 | |||
| @@ -0,0 +1,10 @@ | |||
| <% | |||
| Sub RegisterTestPages() | |||
| Call ASPUnit.AddPages(Array( _ | |||
| "unit/TestHelpers.asp", _ | |||
| "unit/TestControllerRegistry.asp", _ | |||
| "component/TestHomeController.asp", _ | |||
| "integration/TestMvcDispatch.asp" _ | |||
| )) | |||
| End Sub | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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 | |||
| %> | |||
| @@ -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> | |||
| @@ -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> | |||
Powered by TurnKey Linux.