Option Explicit Dim fso, logDir, viewsDir, testViewsDir, pass Dim dbConnStringPath, dbConnString, dbSectionAvailable pass = True Set fso = CreateObject("Scripting.FileSystemObject") logDir = fso.GetParentFolderName(WScript.ScriptFullName) & "\test-logs" If fso.FolderExists(logDir) Then fso.DeleteFolder logDir, True End If ' Real Views/ used for the /hello integration path (proves the end-to-end ' rendering pipeline, not just the ViewRenderer component in isolation). viewsDir = fso.GetParentFolderName(WScript.ScriptFullName) & "\..\Views" ' The connection string (with live credentials) lives only in the untracked ' db.connectionstring file (see .gitignore, docs/DECISIONS.md) - never ' hardcoded here. Read up front so every RunApplication call (including the ' non-DB ones above the Database contract section below) sees a consistent ' value. If the file isn't present on this machine, dbSectionAvailable gates ' the Database contract tests as NOT RUN rather than FAIL further down. dbConnStringPath = fso.GetParentFolderName(WScript.ScriptFullName) & "\..\db.connectionstring" dbSectionAvailable = fso.FileExists(dbConnStringPath) dbConnString = "" If dbSectionAvailable Then Dim dbConnStringStreamEarly Set dbConnStringStreamEarly = fso.OpenTextFile(dbConnStringPath, 1) dbConnString = Trim(dbConnStringStreamEarly.ReadAll) dbConnStringStreamEarly.Close Set dbConnStringStreamEarly = Nothing End If ' Disposable fixture directory for direct ViewRenderer contract tests below. testViewsDir = fso.GetParentFolderName(WScript.ScriptFullName) & "\test-views" If fso.FolderExists(testViewsDir) Then fso.DeleteFolder testViewsDir, True End If fso.CreateFolder testViewsDir Function NewContext(path, httpMethod) Dim ctx Set ctx = CreateObject("WscMvc.RequestContext") ctx.Initialize path, httpMethod, logDir Set NewContext = ctx End Function Sub RunApplication(app, path, httpMethod, applicationName, statusLine, contentType, body, allowHeader) Dim ctx Set ctx = NewContext(path, httpMethod) statusLine = "" : contentType = "" : body = "" : allowHeader = "" app.Run ctx, applicationName, viewsDir, dbConnString, statusLine, contentType, body, allowHeader Set ctx = Nothing End Sub Sub WriteFixture(fileName, content) Dim stream Set stream = fso.CreateTextFile(testViewsDir & "\" & fileName, True) stream.Write content stream.Close Set stream = Nothing End Sub Sub CheckEqual(actual, expected, label) If actual = expected Then WScript.Echo "PASS: " & label Else WScript.Echo "FAIL: " & label & " -> expected [" & expected & "] got [" & actual & "]" pass = False End If End Sub ' --- RequestContext contract --- Dim ctx1 On Error Resume Next Set ctx1 = NewContext("/hello", "GET") If Err.Number <> 0 Then WScript.Echo "FAIL: could not create/initialize WscMvc.RequestContext - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then CheckEqual ctx1.Path, "/hello", "RequestContext.Path" CheckEqual ctx1.HttpMethod, "GET", "RequestContext.HttpMethod" If Len(ctx1.CorrelationId) > 0 Then WScript.Echo "PASS: RequestContext.CorrelationId non-empty (" & ctx1.CorrelationId & ")" Else WScript.Echo "FAIL: RequestContext.CorrelationId empty" pass = False End If If ctx1.ElapsedMs() >= 0 Then WScript.Echo "PASS: RequestContext.ElapsedMs non-negative (" & ctx1.ElapsedMs() & ")" Else WScript.Echo "FAIL: RequestContext.ElapsedMs negative" pass = False End If End If ' --- Two independent contexts must not share correlation IDs or state --- Dim ctx2 If pass Then Set ctx2 = NewContext("/does-not-exist", "GET") If ctx1.CorrelationId <> ctx2.CorrelationId Then WScript.Echo "PASS: two RequestContext instances have distinct CorrelationIds" Else WScript.Echo "FAIL: two RequestContext instances produced the same CorrelationId - " & ctx1.CorrelationId pass = False End If End If ' --- Application.Run happy path via ctx --- Dim app, statusLine, contentType, body, allowHeader If pass Then On Error Resume Next Set app = CreateObject("WscMvc.Application") If Err.Number <> 0 Then WScript.Echo "FAIL: could not create WscMvc.Application - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass Then On Error Resume Next RunApplication app, "/hello", "GET", "production", statusLine, contentType, body, allowHeader If Err.Number <> 0 Then WScript.Echo "FAIL: Application.Run raised error on /hello - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass Then CheckEqual statusLine, "200 OK", "Application.Run(/hello) statusLine" CheckEqual contentType, "text/html; charset=utf-8", "Application.Run(/hello) contentType" CheckEqual body, "Hello from WSC-MVC!", "Application.Run(/hello) body" CheckEqual allowHeader, "", "Application.Run(/hello) Allow header" End If ' --- Application.Run unknown route via ctx (expected 404, not an error) --- If pass Then On Error Resume Next RunApplication app, "/does-not-exist", "GET", "production", statusLine, contentType, body, allowHeader If Err.Number <> 0 Then WScript.Echo "FAIL: Application.Run raised error on unknown route - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass Then CheckEqual statusLine, "404 Not Found", "Application.Run(unknown route) statusLine" CheckEqual allowHeader, "", "Application.Run(unknown route) Allow header" End If ' --- Router method contract: existing route with unsupported method is 405 + Allow --- If pass Then On Error Resume Next RunApplication app, "/hello", "POST", "production", statusLine, contentType, body, allowHeader If Err.Number <> 0 Then WScript.Echo "FAIL: Application.Run raised error on method mismatch - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass Then CheckEqual statusLine, "405 Method Not Allowed", "Application.Run(POST /hello) statusLine" CheckEqual contentType, "text/plain; charset=utf-8", "Application.Run(POST /hello) contentType" CheckEqual body, "Method Not Allowed", "Application.Run(POST /hello) body" CheckEqual allowHeader, "GET", "Application.Run(POST /hello) Allow header" End If ' --- Router app isolation and POST support for test app route --- If pass Then On Error Resume Next RunApplication app, "/self-test", "POST", "tests", statusLine, contentType, body, allowHeader If Err.Number <> 0 Then WScript.Echo "FAIL: Application.Run raised error on POST /self-test - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass Then CheckEqual statusLine, "200 OK", "Application.Run(POST /self-test tests) statusLine" CheckEqual contentType, "application/json; charset=utf-8", "Application.Run(POST /self-test tests) contentType" CheckEqual allowHeader, "", "Application.Run(POST /self-test tests) Allow header" End If ' --- Router malformed path contract --- If pass Then On Error Resume Next RunApplication app, "/hello/../secret", "GET", "production", statusLine, contentType, body, allowHeader If Err.Number <> 0 Then WScript.Echo "FAIL: Application.Run raised error on malformed path - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass Then CheckEqual statusLine, "400 Bad Request", "Application.Run(malformed path) statusLine" CheckEqual contentType, "text/plain; charset=utf-8", "Application.Run(malformed path) contentType" CheckEqual body, "Bad Request", "Application.Run(malformed path) body" CheckEqual allowHeader, "", "Application.Run(malformed path) Allow header" End If ' --- ViewRenderer contract: substitution + HTML-encoding --- Dim renderer On Error Resume Next Set renderer = CreateObject("WscMvc.ViewRenderer") If Err.Number <> 0 Then WScript.Echo "FAIL: could not create WscMvc.ViewRenderer - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then WriteFixture "Sample.html", "
{{Name}}
" Dim dataSample, outSample Set dataSample = CreateObject("Scripting.Dictionary") dataSample.Add "Name", "" outSample = "" On Error Resume Next renderer.Render testViewsDir, "Sample", dataSample, outSample If Err.Number <> 0 Then WScript.Echo "FAIL: ViewRenderer.Render raised error on Sample - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then CheckEqual outSample, "<script>alert(1)</script>
", "ViewRenderer HTML-encodes placeholder value" End If Set dataSample = Nothing End If ' --- ViewRenderer contract: template with no placeholders passes through unchanged, data = Nothing --- If pass Then WriteFixture "Plain.html", "Plain text, no placeholders." Dim outPlain outPlain = "" On Error Resume Next renderer.Render testViewsDir, "Plain", Nothing, outPlain If Err.Number <> 0 Then WScript.Echo "FAIL: ViewRenderer.Render raised error on Plain (data=Nothing) - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then CheckEqual outPlain, "Plain text, no placeholders.", "ViewRenderer passes through template with no placeholders (data=Nothing)" End If End If ' --- ViewRenderer contract: missing template file is a safe, path-free error --- If pass Then Dim outMissing, missingOk, missingDetail missingOk = False outMissing = "" On Error Resume Next renderer.Render testViewsDir, "DoesNotExist", Nothing, outMissing If Err.Number <> 0 Then missingDetail = Err.Description If InStr(1, missingDetail, testViewsDir, 1) = 0 Then missingOk = True End If Err.Clear End If On Error Goto 0 If missingOk Then WScript.Echo "PASS: ViewRenderer.Render on missing template raises a path-free error" Else WScript.Echo "FAIL: ViewRenderer.Render on missing template -> Err.Number after call, detail=[" & missingDetail & "]" pass = False End If End If ' --- ViewRenderer contract: unresolved placeholder (no matching dictionary key) is a safe error --- If pass Then WriteFixture "Missing.html", "{{Unknown}}" Dim dataEmpty, outMissingKey, unresolvedOk Set dataEmpty = CreateObject("Scripting.Dictionary") unresolvedOk = False outMissingKey = "" On Error Resume Next renderer.Render testViewsDir, "Missing", dataEmpty, outMissingKey If Err.Number <> 0 Then unresolvedOk = True Err.Clear End If On Error Goto 0 If unresolvedOk Then WScript.Echo "PASS: ViewRenderer.Render raises on unresolved placeholder" Else WScript.Echo "FAIL: ViewRenderer.Render did not raise on unresolved placeholder -> got [" & outMissingKey & "]" pass = False End If Set dataEmpty = Nothing End If ' --- ViewRenderer contract: invalid view name (path traversal attempt) is rejected --- If pass Then Dim outTraversal, traversalOk traversalOk = False outTraversal = "" On Error Resume Next renderer.Render testViewsDir, "../secret", Nothing, outTraversal If Err.Number <> 0 Then traversalOk = True Err.Clear End If On Error Goto 0 If traversalOk Then WScript.Echo "PASS: ViewRenderer.Render rejects an invalid view name" Else WScript.Echo "FAIL: ViewRenderer.Render did not reject invalid view name -> got [" & outTraversal & "]" pass = False End If End If Set renderer = Nothing ' --- Database contract (M5) --- ' Targets the dedicated integration test database created by ' tools/Setup-TestDatabase.ps1 (never auto-created by application code - ' SPEC SS10: no broad filesystem/DB write privileges from a web request). ' dbConnString/dbSectionAvailable were already read at the top of this file ' (before the first RunApplication call). If db.connectionstring isn't ' present on this machine, the DB section is skipped (NOT RUN) rather than ' failed, since the real issue is an unprovisioned environment, not a ' framework defect. Dim db If Not dbSectionAvailable Then WScript.Echo "NOT RUN: Database contract tests skipped - db.connectionstring not found at " & dbConnStringPath End If If pass And dbSectionAvailable Then On Error Resume Next Set db = CreateObject("WscMvc.Database") If Err.Number <> 0 Then WScript.Echo "FAIL: could not create WscMvc.Database - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If If pass And dbSectionAvailable Then On Error Resume Next db.Open dbConnString If Err.Number <> 0 Then WScript.Echo "FAIL: Database.Open raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If ' Clean slate so this test is deterministic across repeated runs. If pass And dbSectionAvailable Then Dim rowsCleared On Error Resume Next db.ExecuteNonQuery "DELETE FROM dbo.Widgets", "", Nothing, rowsCleared If Err.Number <> 0 Then WScript.Echo "FAIL: Database.ExecuteNonQuery(DELETE) raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 End If ' --- Parameterized INSERT with a value containing a single quote: proves no ' string-concatenation SQL injection (a naive concatenation would break the ' SQL syntax or need manual quote-escaping; the parameterized call needs ' neither). --- If pass And dbSectionAvailable Then Dim insertParams, rowsInserted Set insertParams = CreateObject("Scripting.Dictionary") insertParams.Add "Name", "O'Brien" On Error Resume Next db.ExecuteNonQuery "INSERT INTO dbo.Widgets (Name) VALUES (?)", "Name", insertParams, rowsInserted If Err.Number <> 0 Then WScript.Echo "FAIL: Database.ExecuteNonQuery(INSERT) raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then CheckEqual rowsInserted, 1, "Database.ExecuteNonQuery(INSERT) rowsAffected" End If Set insertParams = Nothing End If ' --- Parameterized SELECT round-trips the quote-containing value intact --- If pass And dbSectionAvailable Then Dim selectParams, foundName Set selectParams = CreateObject("Scripting.Dictionary") selectParams.Add "Name", "O'Brien" On Error Resume Next db.ExecuteScalar "SELECT Name FROM dbo.Widgets WHERE Name = ?", "Name", selectParams, foundName If Err.Number <> 0 Then WScript.Echo "FAIL: Database.ExecuteScalar(SELECT) raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then CheckEqual foundName, "O'Brien", "Database.ExecuteScalar round-trips quote-containing value" End If Set selectParams = Nothing End If ' --- Null parameter contract: a Null value must pass through as a real SQL ' NULL, not the string "Null" or an empty string. --- If pass And dbSectionAvailable Then Dim nullParams, nullResult Set nullParams = CreateObject("Scripting.Dictionary") nullParams.Add "v", Null On Error Resume Next db.ExecuteScalar "SELECT ? AS v", "v", nullParams, nullResult If Err.Number <> 0 Then WScript.Echo "FAIL: Database.ExecuteScalar(Null param) raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then If IsNull(nullResult) Then WScript.Echo "PASS: Database.ExecuteScalar Null parameter round-trips as SQL NULL" Else WScript.Echo "FAIL: Database.ExecuteScalar Null parameter -> expected Null, got [" & nullResult & "]" pass = False End If End If Set nullParams = Nothing End If ' --- Transaction contract: rollback leaves no trace --- If pass And dbSectionAvailable Then Dim rollbackParams, rowsRolledBack, countAfterRollback On Error Resume Next db.BeginTransaction Set rollbackParams = CreateObject("Scripting.Dictionary") rollbackParams.Add "Name", "RolledBack" db.ExecuteNonQuery "INSERT INTO dbo.Widgets (Name) VALUES (?)", "Name", rollbackParams, rowsRolledBack db.RollbackTransaction If Err.Number <> 0 Then WScript.Echo "FAIL: Database rollback sequence raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then Dim countParams Set countParams = CreateObject("Scripting.Dictionary") countParams.Add "Name", "RolledBack" On Error Resume Next db.ExecuteScalar "SELECT COUNT(*) FROM dbo.Widgets WHERE Name = ?", "Name", countParams, countAfterRollback On Error Goto 0 CheckEqual countAfterRollback, 0, "Database.RollbackTransaction leaves no row" Set countParams = Nothing End If Set rollbackParams = Nothing End If ' --- Transaction contract: commit persists the change --- If pass And dbSectionAvailable Then Dim commitParams, rowsCommitted, countAfterCommit On Error Resume Next db.BeginTransaction Set commitParams = CreateObject("Scripting.Dictionary") commitParams.Add "Name", "Committed" db.ExecuteNonQuery "INSERT INTO dbo.Widgets (Name) VALUES (?)", "Name", commitParams, rowsCommitted db.CommitTransaction If Err.Number <> 0 Then WScript.Echo "FAIL: Database commit sequence raised error - " & Err.Description pass = False Err.Clear End If On Error Goto 0 If pass Then On Error Resume Next db.ExecuteScalar "SELECT COUNT(*) FROM dbo.Widgets WHERE Name = ?", "Name", commitParams, countAfterCommit On Error Goto 0 CheckEqual countAfterCommit, 1, "Database.CommitTransaction persists the row" End If Set commitParams = Nothing End If ' --- Failure path: operating on a closed/never-opened connection is a safe error --- If pass And dbSectionAvailable Then Dim db2, closedOk, closedDetail, dummyResult closedOk = False Set db2 = CreateObject("WscMvc.Database") On Error Resume Next db2.ExecuteScalar "SELECT 1", "", Nothing, dummyResult If Err.Number <> 0 Then closedOk = True Err.Clear End If On Error Goto 0 If closedOk Then WScript.Echo "PASS: Database.ExecuteScalar on an unopened connection raises a safe error" Else WScript.Echo "FAIL: Database.ExecuteScalar on an unopened connection did not raise -> got [" & dummyResult & "]" pass = False End If Set db2 = Nothing End If ' --- Close is idempotent --- If pass And dbSectionAvailable Then Dim closeOk closeOk = True On Error Resume Next db.Close db.Close If Err.Number <> 0 Then closeOk = False Err.Clear End If On Error Goto 0 If closeOk Then WScript.Echo "PASS: Database.Close is idempotent" Else WScript.Echo "FAIL: calling Database.Close twice raised an error" pass = False End If End If Set db = Nothing ' --- Logging: best-effort log file was written with both outcomes --- If pass Then Dim logPath, logContent logPath = logDir & "\app.log" If fso.FileExists(logPath) Then Dim logStream Set logStream = fso.OpenTextFile(logPath, 1) logContent = logStream.ReadAll logStream.Close If InStr(logContent, "200 OK") > 0 And InStr(logContent, "404 Not Found") > 0 Then WScript.Echo "PASS: app.log contains both 200 OK and 404 Not Found outcomes" Else WScript.Echo "FAIL: app.log missing expected outcomes -> " & logContent pass = False End If Else WScript.Echo "FAIL: app.log was not created at " & logPath pass = False End If End If Set app = Nothing Set ctx1 = Nothing Set ctx2 = Nothing If fso.FolderExists(logDir) Then fso.DeleteFolder logDir, True End If If fso.FolderExists(testViewsDir) Then fso.DeleteFolder testViewsDir, True End If Set fso = Nothing If pass Then WScript.Echo "RESULT: ALL PASS" WScript.Quit 0 Else WScript.Echo "RESULT: FAILURE" WScript.Quit 1 End If