# Classic ASP Development Standards
## ShopDB Application

**Version:** 1.2
**Last Updated:** 2025-12-12
**Status:** MANDATORY for all new development and modifications

---

## Table of Contents

1. [Security Standards](#security-standards)
2. [Database Access Standards](#database-access-standards)
3. [Input Validation Standards](#input-validation-standards)
4. [Output Encoding Standards](#output-encoding-standards)
5. [Error Handling Standards](#error-handling-standards)
6. [Code Structure Standards](#code-structure-standards)
7. [Naming Conventions](#naming-conventions)
8. [Documentation Standards](#documentation-standards)
9. [Performance Standards](#performance-standards)
10. [Testing Standards](#testing-standards)

---

## Security Standards

### Authentication & Authorization

> **NOTE:** Authentication is not yet implemented. SAML integration is planned for a future release. The patterns below document the intended implementation.

**MANDATORY (when implemented):** All pages MUST implement authentication checks.

```vbscript
<!--#include file="./includes/auth_check.asp"-->
<%
' This will redirect to login if user is not authenticated
Call RequireAuthentication()

' For administrative functions:
Call RequireRole("Admin")
%>
```

**Exception:** Only the following pages may skip authentication:
- `login.asp`
- `error.asp`
- `404.asp`
- API endpoints (use API key authentication instead)

### Session Management

```vbscript
' Standard session configuration (in sql.asp)
Session.Timeout = APP_SESSION_TIMEOUT  ' From config.asp

' After successful authentication:
Session("authenticated") = True
Session("userId") = userId
Session("userName") = userName
Session("userRole") = userRole
Session("loginTime") = Now()
Session.Abandon  ' Only on explicit logout
```

### Password Requirements

- **Minimum Length:** 12 characters
- **Complexity:** Must include uppercase, lowercase, number, special character
- **Storage:** Never store plaintext passwords
- **Transmission:** HTTPS only (enforce in IIS)

### Security Headers

All pages MUST set appropriate security headers:

```vbscript
Response.AddHeader "X-Content-Type-Options", "nosniff"
Response.AddHeader "X-Frame-Options", "SAMEORIGIN"
Response.AddHeader "X-XSS-Protection", "1; mode=block"
Response.AddHeader "Content-Security-Policy", "default-src 'self'"
```

---

## Database Access Standards

### Connection String

**MANDATORY:** Use configuration file, NEVER hard-code credentials.

```vbscript
<!--#include file="./includes/config.asp"-->
<%
' In sql.asp - use config constants
objConn.ConnectionString = GetConnectionString()
objConn.Open
%>
```

**Environment Toggle:** Set `USE_DSN` in config.asp:
- `USE_DSN = False` - Development (direct ODBC driver connection)
- `USE_DSN = True` - Production (DSN-based connection)

```vbscript
' config.asp controls connection method
Const USE_DSN = False  ' Set True for production

Function GetConnectionString()
    If USE_DSN Then
        ' Production: DSN-based
        GetConnectionString = "DSN=" & DB_DSN & ";Uid=...;Pwd=...;Option=3;Pooling=True;Max Pool Size=100;"
    Else
        ' Development: Direct ODBC
        GetConnectionString = "Driver={" & DB_DRIVER & "};Server=...;..."
    End If
End Function
```

### Parameterized Queries

**MANDATORY:** ALL database queries MUST use parameterization.

** NEVER DO THIS:**
```vbscript
' WRONG - SQL Injection vulnerable
machineId = Request.QueryString("machineid")
strSQL = "SELECT * FROM machines WHERE machineid = " & machineId
Set rs = objConn.Execute(strSQL)
```

** ALWAYS DO THIS:**
```vbscript
' CORRECT - Parameterized query
machineId = GetSafeInteger("QS", "machineid", 0, 1, 999999)

Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = objConn
cmd.CommandText = "SELECT * FROM machines WHERE machineid = ?"
cmd.CommandType = 1  ' adCmdText

Set param = cmd.CreateParameter("@machineid", 3, 1, , machineId)  ' 3=adInteger, 1=adParamInput
cmd.Parameters.Append param

Set rs = cmd.Execute()
```

### Resource Cleanup

**MANDATORY:** Always clean up database resources.

```vbscript
<%
' At the end of EVERY page:
Call CleanupResources()
%>
```

**Template:**
```vbscript
<!--#include file="./includes/cleanup.asp"-->
<%
On Error Resume Next

' Database operations here

' Before any Response.Redirect:
Call CleanupResources()
Response.Redirect("page.asp")
Response.End

' At end of page:
Call CleanupResources()
On Error Goto 0
%>
```

### Connection Pooling

**MANDATORY:** Enable connection pooling in configuration.

```vbscript
' In config.asp GetConnectionString() function:
connectionString = connectionString & "Pooling=True;Max Pool Size=100;"
```

---

## Input Validation Standards

### Validation Library

**MANDATORY:** Use validation functions for ALL user input.

```vbscript
<!--#include file="./includes/validation.asp"-->
```

### Common Validation Patterns

#### Integer IDs
```vbscript
Dim machineId
machineId = GetSafeInteger("QS", "machineid", 0, 1, 999999)

If machineId = 0 Then
    Response.Redirect("error.asp?msg=INVALID_ID")
    Response.End
End If
```

#### String Fields
```vbscript
Dim serialNumber
serialNumber = GetSafeString("FORM", "serialnumber", "", 7, 50, "^[A-Z0-9]+$")

If serialNumber = "" Then
    Response.Redirect("adddevice.asp?error=INVALID_SERIAL")
    Response.End
End If
```

#### IP Addresses
```vbscript
Dim ipAddress
ipAddress = Request.Form("ipaddress")

If Not ValidateIPAddress(ipAddress) Then
    Response.Redirect("error.asp?msg=INVALID_IP")
    Response.End
End If
```

#### Email Addresses
```vbscript
Dim email
email = Request.Form("email")

If Not ValidateEmail(email) Then
    Response.Redirect("error.asp?msg=INVALID_EMAIL")
    Response.End
End If
```

### Whitelist Validation

**PREFERRED:** Use whitelist validation whenever possible.

```vbscript
' Example: Only allow specific status values
Dim status
status = Request.Form("status")

If status <> "active" And status <> "inactive" And status <> "pending" Then
    Response.Redirect("error.asp?msg=INVALID_STATUS")
    Response.End
End If
```

### Client-Side Validation

**REQUIRED:** Implement client-side validation for user experience.

**CRITICAL:** Client-side validation does NOT replace server-side validation.

```html
<form onsubmit="return validateForm();">
    <input type="text"
           name="serialnumber"
           id="serialnumber"
           required
           minlength="7"
           maxlength="50"
           pattern="[A-Za-z0-9]+"
           title="7-50 alphanumeric characters">
</form>

<script>
function validateForm() {
    var serial = document.getElementById("serialnumber").value;
    if (!/^[A-Z0-9]{7,50}$/i.test(serial)) {
        alert("Serial number must be 7-50 alphanumeric characters");
        return false;
    }
    return true;
}
</script>
```

---

## Output Encoding Standards

### HTML Output

**MANDATORY:** ALL user-controlled output MUST be HTML-encoded.

** NEVER DO THIS:**
```vbscript
<h5><%=rs("machinename")%></h5>
<p><%Response.Write(rs("description"))%></p>
```

** ALWAYS DO THIS:**
```vbscript
<h5><%=Server.HTMLEncode(rs("machinename"))%></h5>
<p><%Response.Write(Server.HTMLEncode(rs("description")))%></p>
```

### JavaScript Context

**MANDATORY:** Use JavaScript encoding for data in JavaScript.

```vbscript
<script>
var machineName = "<%=JavaScriptEncode(rs("machinename"))%>";
</script>
```

```vbscript
' Helper function in includes/encoding.asp
Function JavaScriptEncode(str)
    Dim result
    result = Replace(str, "\", "\\")
    result = Replace(result, "'", "\'")
    result = Replace(result, """", "\""")
    result = Replace(result, vbCrLf, "\n")
    result = Replace(result, vbCr, "\n")
    result = Replace(result, vbLf, "\n")
    JavaScriptEncode = result
End Function
```

### URL Parameters

**MANDATORY:** Use URLEncode for URL parameters.

```vbscript
<a href="displaymachine.asp?name=<%=Server.URLEncode(rs("machinename"))%>">Link</a>
```

### JSON Output

**MANDATORY:** Properly escape JSON output.

```vbscript
<!--#include file="./includes/json_helper.asp"-->
<%
Response.ContentType = "application/json"
Response.Write(CreateJSONFromRecordset(rs))
%>
```

---

## Error Handling Standards

### Standard Error Handler

**MANDATORY:** Include error handler in ALL pages.

```vbscript
<!--#include file="./includes/error_handler.asp"-->
<%
Call InitializeErrorHandling("pagename.asp")

' Page logic here

Call CheckForErrors()  ' After each critical operation

Call CleanupResources()
%>
```

### Error Logging

**MANDATORY:** Log all errors to server-side log file.

```vbscript
' In error_handler.asp
Call LogError(pageName, Err.Number, Err.Description, Request.ServerVariables("REMOTE_ADDR"))
```

**Log Format:**
```
2025-10-10 14:35:22 | displaymachine.asp | -2147467259 | Syntax error in SQL | 192.168.122.1
```

### User-Facing Error Messages

**MANDATORY:** NEVER expose technical details to users.

** WRONG:**
```vbscript
Response.Write("Error: " & Err.Description)
```

** CORRECT:**
```vbscript
Response.Redirect("error.asp?code=DATABASE_ERROR")
```

### Error Codes

Standard error codes for user messaging:

- `INVALID_INPUT` - User input validation failed
- `NOT_FOUND` - Record not found
- `UNAUTHORIZED` - User lacks permission
- `DATABASE_ERROR` - Database operation failed
- `GENERAL_ERROR` - Catch-all for unexpected errors

---

## Code Structure Standards

### File Header

**MANDATORY:** Every ASP file must have a header comment block.

```vbscript
<%
'=============================================================================
' FILE: displaymachine.asp
' PURPOSE: Display detailed information for a single machine
'
' PARAMETERS:
'   machineid (QueryString, Required) - Integer ID of machine to display
'
' DEPENDENCIES:
'   - includes/config.asp - Application configuration
'   - includes/sql.asp - Database connection
'   - includes/validation.asp - Input validation functions
'   - includes/auth_check.asp - Authentication verification
'
' DATABASE TABLES:
'   - machines (primary)
'   - machinetypes, models, vendors, businessunits
'   - printers (LEFT JOIN - may be NULL)
'   - communications (LEFT JOIN - may be NULL)
'
' SECURITY:
'   - Requires authentication
'   - No special role required (read-only)
'   - Uses parameterized queries
'
' AUTHOR: [Your Name]
' CREATED: 2025-10-10
' MODIFIED: 2025-10-10 - Initial version
'
'=============================================================================
%>
```

### Standard Page Template

```vbscript
<%@ Language=VBScript %>
<%
Option Explicit  ' MANDATORY - Forces variable declaration
%>
<!--#include file="./includes/config.asp"-->
<!--#include file="./includes/sql.asp"-->
<!--#include file="./includes/validation.asp"-->
<!--#include file="./includes/auth_check.asp"-->
<!--#include file="./includes/error_handler.asp"-->
<!DOCTYPE html>
<html lang="en">
<head>
  <!--#include file="./includes/header.asp"-->
  <title>Page Title</title>
</head>
<%
'-----------------------------------------------------------------------------
' AUTHENTICATION
'-----------------------------------------------------------------------------
Call RequireAuthentication()

'-----------------------------------------------------------------------------
' INITIALIZATION
'-----------------------------------------------------------------------------
Call InitializeErrorHandling("pagename.asp")

' Get and validate parameters
Dim paramId
paramId = GetSafeInteger("QS", "id", 0, 1, 999999)

If paramId = 0 Then
    Call CleanupResources()
    Response.Redirect("error.asp?msg=INVALID_ID")
    Response.End
End If

' Get theme preference
Dim theme
theme = Request.Cookies("theme")
If theme = "" Then theme = "bg-theme1"

'-----------------------------------------------------------------------------
' DATABASE OPERATIONS
'-----------------------------------------------------------------------------
Dim strSQL, objRS

strSQL = "SELECT * FROM tablename WHERE id = ?"
Set objRS = ExecuteParameterizedQuery(objConn, strSQL, Array(paramId))
Call CheckForErrors()

If objRS.EOF Then
    Call CleanupResources()
    Response.Redirect("error.asp?msg=NOT_FOUND")
    Response.End
End If
%>

<body class="bg-theme <%=theme%>">

<div id="wrapper">
  <!--#include file="./includes/leftsidebar.asp"-->
  <!--#include file="./includes/topbarheader.asp"-->

  <div class="clearfix"></div>

  <div class="content-wrapper">
    <div class="container-fluid">

      <!-- Page Content -->
      <h5><%=Server.HTMLEncode(objRS("name"))%></h5>

    </div>
  </div>

  <!--#include file="./includes/footer.asp"-->
</div>

<!-- JavaScript -->
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/app-script.js"></script>

</body>
</html>

<%
'-----------------------------------------------------------------------------
' CLEANUP
'-----------------------------------------------------------------------------
Call CleanupResources()
%>
```

### Form Processing Template

```vbscript
<%@ Language=VBScript %>
<%
Option Explicit
%>
<!--#include file="./includes/config.asp"-->
<!--#include file="./includes/sql.asp"-->
<!--#include file="./includes/validation.asp"-->
<!--#include file="./includes/auth_check.asp"-->
<!--#include file="./includes/error_handler.asp"-->
<%
'-----------------------------------------------------------------------------
' AUTHENTICATION
'-----------------------------------------------------------------------------
Call RequireAuthentication()
Call RequireRole("Editor")  ' If write operation requires special role

'-----------------------------------------------------------------------------
' INITIALIZATION
'-----------------------------------------------------------------------------
Call InitializeErrorHandling("savepage.asp")

'-----------------------------------------------------------------------------
' VALIDATE INPUT
'-----------------------------------------------------------------------------
Dim recordId, fieldValue1, fieldValue2

recordId = GetSafeInteger("FORM", "id", 0, 0, 999999)
fieldValue1 = GetSafeString("FORM", "field1", "", 1, 100, "^[A-Za-z0-9 ]+$")
fieldValue2 = GetSafeString("FORM", "field2", "", 0, 200, "")

If fieldValue1 = "" Then
    Call CleanupResources()
    Response.Redirect("editpage.asp?id=" & recordId & "&error=REQUIRED_FIELD")
    Response.End
End If

'-----------------------------------------------------------------------------
' DATABASE OPERATION
'-----------------------------------------------------------------------------
Dim strSQL

If recordId > 0 Then
    ' Update existing record
    strSQL = "UPDATE tablename SET field1 = ?, field2 = ?, lastupdated = NOW() WHERE id = ?"
    Call ExecuteParameterizedUpdate(objConn, strSQL, Array(fieldValue1, fieldValue2, recordId))
Else
    ' Insert new record
    strSQL = "INSERT INTO tablename (field1, field2, created) VALUES (?, ?, NOW())"
    Call ExecuteParameterizedInsert(objConn, strSQL, Array(fieldValue1, fieldValue2))
    recordId = CLng(objConn.Execute("SELECT LAST_INSERT_ID() AS id")(0))
End If

Call CheckForErrors()

'-----------------------------------------------------------------------------
' CLEANUP AND REDIRECT
'-----------------------------------------------------------------------------
Call CleanupResources()
Response.Redirect("displaypage.asp?id=" & recordId & "&success=1")
%>
```

---

## Naming Conventions

### Variables

**Style:** camelCase

```vbscript
' IDs - use "Id" suffix
Dim machineId, printerId, userId

' Strings - descriptive names
Dim serialNumber, ipAddress, userName, description

' Booleans - use "is" or "has" prefix
Dim isActive, hasPermission, isValid

' Database objects - use obj prefix
Dim objConn, objCmd, objRS

' SQL queries - use str prefix
Dim strSQL, strSQL2

' Counters/indexes - single letter or descriptive
Dim i, j, rowCount, itemIndex
```

### Constants

**Style:** UPPER_CASE_WITH_UNDERSCORES

```vbscript
Const DB_SERVER = "192.168.122.1"
Const MAX_FILE_SIZE = 10485760
Const SESSION_TIMEOUT = 30
Const DEFAULT_PAGE_SIZE = 50
```

### Functions

**Style:** PascalCase, verb-noun format

```vbscript
Function GetMachineById(machineId)
Function ValidateIPAddress(ipAddress)
Function RenderVendorDropdown(selectedId, filterType)
Function CreateJSONResponse(success, message, data)
Function CalculateTotalCost(items)
```

### Subroutines

**Style:** PascalCase, verb-noun format

```vbscript
Sub InitializeErrorHandling(pageName)
Sub CleanupResources()
Sub RequireAuthentication()
Sub LogError(source, errorNum, errorDesc)
```

### Files

**Display Pages (single record):** display[noun-singular].asp
- `displaymachine.asp`
- `displayprinter.asp`
- `displaypc.asp`

**List Pages (multiple records):** display[noun-plural].asp
- `displaymachines.asp`
- `displayprinters.asp`
- `displaypcs.asp`

**Edit Pages:** edit[noun-singular].asp
- `editmachine.asp`
- `editprinter.asp`
- `editpc.asp`

**Add Pages:** add[noun-singular].asp
- `addmachine.asp`
- `addprinter.asp`
- `addpc.asp`

**Form Processors:** [verb][noun].asp
- `savemachine.asp`
- `updatemachine.asp`
- `deletemachine.asp`

**Include Files:** descriptive lowercase
- `sql.asp`
- `config.asp`
- `validation.asp`
- `error_handler.asp`
- `auth_check.asp`

### Database Tables

**Style:** lowercase, plural nouns

```sql
machines       -- Unified table: Equipment, PCs, Network Devices
printers
communications -- Network interfaces (IP/MAC)
machinetypes
vendors
models
```

### Database Columns

**Style:** lowercase, descriptive

```sql
machineid
machinenumber
serialnumber
ipaddress
isactive
createdate
lastupdated
```

### Column Naming Gotchas

**IMPORTANT:** Be aware of these non-obvious column names:

| Expected | Actual | Table |
|----------|--------|-------|
| `ipaddress` | `address` | communications |
| `gateway` | `defaultgateway` | communications |
| `communicationid` | `comid` | communications |
| `notes` | `machinenotes` | machines |
| `pcid` | `machineid` | machines (PCs are in unified table) |
| `pc_comm_config` | `commconfig` | (table name) |
| `pc_dnc_config` | `dncconfig` | (table name) |

**PC Identification:** PCs are in the `machines` table, identified by:
- `pctypeid IS NOT NULL`
- `machinetypeid = 33` (generic PC type)

> **Note:** Redundant PC machinetypes (34-46) were removed. All PCs now use machinetypeid=33 with pctypeid for categorization (Standard, Engineer, Shopfloor, CMM, etc.)

---

## Documentation Standards

### Inline Comments

**REQUIRED:** Comment complex logic and business rules.

```vbscript
'-----------------------------------------------------------------------------
' Search Logic:
' 1. Check if input matches machine number (exact) or alias (partial)
' 2. If starts with "csf" and length=5, search printer CSF names
' 3. If 7 alphanumeric chars, treat as PC serial number
' 4. If valid IP, find containing subnet
' 5. If 9 digits, treat as SSO employee number
' 6. If starts with ticket prefix, redirect to ServiceNow
' 7. Otherwise, full-text search knowledge base
'-----------------------------------------------------------------------------
```

### SQL Query Comments

**RECOMMENDED:** Document complex queries.

```vbscript
'-----------------------------------------------------------------------------
' QUERY: Get machine with all related data
'
' Retrieves:
' - Machine details (machines table)
' - Type and function account (for billing)
' - Model and vendor information
' - Business unit assignment
' - Associated printer (LEFT JOIN - may be NULL)
' - Associated PC (LEFT JOIN - may be NULL)
'
' LEFT JOINs used because not all machines have printers/PCs.
'-----------------------------------------------------------------------------
strSQL = "SELECT m.*, mt.machinetype, mdl.modelnumber, " & _
         "       v.vendor, bu.businessunit, " & _
         "       p.ipaddress AS printerip " & _
         "FROM machines m " & _
         "INNER JOIN machinetypes mt ON m.machinetypeid = mt.machinetypeid " & _
         "LEFT JOIN printers p ON m.printerid = p.printerid " & _
         "WHERE m.machineid = ?"
```

### Function Documentation

**MANDATORY:** Document all functions and subroutines.

```vbscript
'-----------------------------------------------------------------------------
' FUNCTION: ValidateIPAddress
' PURPOSE: Validates that a string is a valid IPv4 address
'
' PARAMETERS:
'   ipAddress (String) - The IP address to validate
'
' RETURNS:
'   Boolean - True if valid IPv4 address, False otherwise
'
' EXAMPLES:
'   ValidateIPAddress("192.168.1.1")     -> True
'   ValidateIPAddress("192.168.1.256")   -> False
'   ValidateIPAddress("not an ip")       -> False
'
' VALIDATION:
'   - Must match pattern: XXX.XXX.XXX.XXX
'   - Each octet must be 0-255
'   - No leading zeros allowed
'-----------------------------------------------------------------------------
Function ValidateIPAddress(ipAddress)
    Dim objRegEx, pattern
    Set objRegEx = New RegExp
    pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
    objRegEx.Pattern = pattern
    ValidateIPAddress = objRegEx.Test(ipAddress)
End Function
```

### TODO Comments

**ENCOURAGED:** Use standardized TODO format.

```vbscript
' TODO: SECURITY - Add authentication check (HIGH PRIORITY)
' TODO: PERFORMANCE - Cache this query result
' TODO: VALIDATION - Add email format validation
' TODO: REFACTOR - Extract to reusable function
' TODO: BUG - Handle null value edge case
```

---

## Performance Standards

### Database Query Optimization

**REQUIRED:** Follow these query optimization practices.

#### Use Specific Columns
```vbscript
' BAD
strSQL = "SELECT * FROM machines"

' GOOD
strSQL = "SELECT machineid, machinenumber, machinetype FROM machines"
```

#### Use Appropriate JOINs
```vbscript
' Use INNER JOIN when relationship is required
' Use LEFT JOIN when relationship is optional
' Avoid RIGHT JOIN (use LEFT JOIN instead for clarity)
```

#### Limit Result Sets
```vbscript
' For list views, always implement paging
strSQL = "SELECT * FROM machines WHERE isactive = 1 " & _
         "ORDER BY machinenumber " & _
         "LIMIT " & pageSize & " OFFSET " & offset
```

### Caching Strategy

**REQUIRED:** Cache reference data at application scope.

```vbscript
' In global.asa Application_OnStart
Sub Application_OnStart
    ' Cache rarely-changing reference data
    Call LoadVendorCache()
    Call LoadModelCache()
    Call LoadMachineTypeCache()
End Sub

' In data_cache.asp
Function GetCachedVendors()
    If IsEmpty(Application("CachedVendors")) Or _
       DateDiff("s", Application("VendorCacheTime"), Now()) > CACHE_DURATION Then
        Call LoadVendorCache()
    End If
    GetCachedVendors = Application("CachedVendors")
End Function
```

**Cache Invalidation:**
- Time-based: 5-30 minutes for reference data
- Event-based: Invalidate when data is modified
- Manual: Provide admin function to clear cache

### Response Buffering

**REQUIRED:** Enable response buffering.

```vbscript
<%
Response.Buffer = True
%>
```

**Benefits:**
- Allows headers to be set after content generation
- Enables proper error handling with redirects
- Improves performance by sending larger chunks

### Minimize Database Roundtrips

**PREFERRED:** Consolidate queries when possible.

```vbscript
' BAD - 4 separate queries
Set rsVendors = objConn.Execute("SELECT * FROM vendors")
Set rsModels = objConn.Execute("SELECT * FROM models")
Set rsTypes = objConn.Execute("SELECT * FROM machinetypes")
Set rsUnits = objConn.Execute("SELECT * FROM businessunits")

' BETTER - Use cached data
Response.Write(RenderCachedVendorDropdown())
Response.Write(RenderCachedModelDropdown())
Response.Write(RenderCachedTypeDropdown())
Response.Write(RenderCachedUnitDropdown())
```

---

## Testing Standards

### Automated Form Testing

**REQUIRED:** Run the comprehensive test suite after making changes to ASP pages.

```bash
./tests/test_forms.sh
```

This script tests 41 endpoints including:
- Page load tests (dashboards, list views, maps)
- Add form page loads
- Form submissions (notifications, equipment, printers, subnets, applications, KB, vendors, models, network devices)
- API endpoints

Test data uses `AUTOTEST_` prefix for easy cleanup. See `tests/cleanup_test_data.sql`.

### API Endpoint Testing

**REQUIRED:** Test API endpoints used by PowerShell scripts after changes to api.asp.

```bash
# Health check
curl -s "http://192.168.122.151:8080/api.asp?action=getDashboardData"

# Get shopfloor PCs
curl -s "http://192.168.122.151:8080/api.asp?action=getShopfloorPCs"

# Simulate PowerShell PC data collection
curl -s -X POST "http://192.168.122.151:8080/api.asp" \
  -d "action=updateCompleteAsset" \
  -d "hostname=TESTPC01" \
  -d "serialNumber=TEST123" \
  -d "manufacturer=Dell Inc." \
  -d "model=OptiPlex 7080" \
  -d "osVersion=Microsoft Windows 11 Pro" \
  -d "pcType=Standard"

# Get recorded IP (POST method)
curl -s -X POST "http://192.168.122.151:8080/api.asp" \
  -d "action=getRecordedIP" \
  -d "hostname=TESTPC01"
```

**Key API Endpoints:**
| Action | Method | Purpose |
|--------|--------|---------|
| getDashboardData | GET | Health check |
| getShopfloorPCs | GET | List shopfloor PCs |
| updateCompleteAsset | POST | PC data collection (main PowerShell endpoint) |
| getRecordedIP | POST | Get recorded IP for hostname |
| updatePrinterMapping | POST | Link printer to PC |
| updateInstalledApps | POST | Record installed applications |

### Unit Testing

**REQUIRED:** Test all validation functions.

Create test file: `tests/test_validation.asp`

```vbscript
<!--#include file="../includes/validation.asp"-->
<%
Sub TestValidateIPAddress()
    If ValidateIPAddress("192.168.1.1") Then
        Response.Write("PASS: Valid IP accepted<br>")
    Else
        Response.Write("FAIL: Valid IP rejected<br>")
    End If

    If Not ValidateIPAddress("999.999.999.999") Then
        Response.Write("PASS: Invalid IP rejected<br>")
    Else
        Response.Write("FAIL: Invalid IP accepted<br>")
    End If
End Sub

Call TestValidateIPAddress()
%>
```

### Integration Testing

**RECOMMENDED:** Test critical user flows.

**Test Cases:**
1. User login flow
2. Machine creation flow
3. Machine update flow
4. Search functionality
5. Report generation

### Security Testing

**REQUIRED:** Test for common vulnerabilities.

**Test Checklist:**
- [ ] SQL injection attempts on all input fields
- [ ] XSS payloads in all text fields
- [ ] Access control bypass attempts
- [ ] Session hijacking scenarios
- [ ] CSRF token validation

### Load Testing

**RECOMMENDED:** Test under expected load.

**Metrics to Monitor:**
- Response time per page
- Database connection pool usage
- Memory consumption
- Concurrent user capacity

---

## Code Review Checklist

Before committing code, verify:

### Security
- [ ] Authentication check present
- [ ] All queries use parameterization
- [ ] All output is HTML-encoded
- [ ] Input validation implemented
- [ ] No credentials in code

### Error Handling
- [ ] Error handler included
- [ ] Resources cleaned up on all paths
- [ ] No technical details exposed to users
- [ ] Errors logged to server

### Code Quality
- [ ] File header present
- [ ] Complex logic commented
- [ ] Naming conventions followed
- [ ] No code duplication
- [ ] No commented-out debug code

### Performance
- [ ] Queries optimized
- [ ] Appropriate caching used
- [ ] Resources properly closed
- [ ] Result sets limited/paged

### Testing
- [ ] Manually tested happy path
- [ ] Tested error conditions
- [ ] Tested with invalid input
- [ ] Cross-browser tested (if UI changes)

---

## Configuration Management

### Environment-Specific Configurations

**Structure:**
```
/includes/
    config.asp.example  (Template with placeholder credentials - tracked in git)
    config.asp          (Actual credentials - gitignored, never commit)
```

**Deployment Process:**
1. Copy `config.asp.example` to `config.asp`
2. Fill in actual credentials
3. Set `USE_DSN = True` for production, `False` for development
4. `config.asp` is gitignored - never committed to source control

**Configuration includes:**
- ShopDB credentials (with DSN/direct ODBC toggle)
- Employee database credentials (with DSN/direct ODBC toggle)
- Zabbix API URL and token
- Application settings (session timeout, page size, cache duration)
- Business logic constants (serial number length, CSF prefix, etc.)

### Secrets Management

**MANDATORY:** Store sensitive credentials in `secrets.md` (gitignored).

**Contents:**
- Zabbix API URL and token
- Gitea API URL and token
- Database credentials (per environment)

**NEVER commit:**
- API tokens
- Database passwords
- Authentication credentials

### Configuration Template

See `includes/config.asp.example` for the full template. Key sections:

```vbscript
<%
'=============================================================================
' Database Configuration - ShopDB
'=============================================================================
' Toggle between DSN (production) and direct ODBC (development)
Const USE_DSN = False  ' Set True for production

' DSN configuration (production)
Const DB_DSN = "shopdb"
Const DB_DSN_USER = "YOUR_DB_USER"
Const DB_DSN_PASSWORD = "YOUR_DB_PASSWORD"

' Direct ODBC configuration (development)
Const DB_DRIVER = "MySQL ODBC 9.4 Unicode Driver"
Const DB_SERVER = "192.168.122.1"
Const DB_PORT = "3306"
Const DB_NAME = "shopdb"
Const DB_USER = "YOUR_DB_USER"
Const DB_PASSWORD = "YOUR_DB_PASSWORD"

'=============================================================================
' Database Configuration - Employee Database
'=============================================================================
Const USE_EMP_DSN = True  ' Usually DSN-based

Const EMP_DB_DSN = "wjf_employees"
Const EMP_DB_DSN_USER = "YOUR_EMP_USER"
Const EMP_DB_DSN_PASSWORD = "YOUR_EMP_PASSWORD"

'=============================================================================
' External Services
'=============================================================================
Const ZABBIX_URL = "http://your-zabbix-server/api_jsonrpc.php"
Const ZABBIX_API_TOKEN = "YOUR_ZABBIX_API_TOKEN"

'=============================================================================
' Helper Functions
'=============================================================================
Function GetConnectionString()
    If USE_DSN Then
        GetConnectionString = "DSN=" & DB_DSN & ";Uid=" & DB_DSN_USER & _
                             ";Pwd=" & DB_DSN_PASSWORD & ";Option=3;Pooling=True;Max Pool Size=100;"
    Else
        GetConnectionString = "Driver={" & DB_DRIVER & "};Server=" & DB_SERVER & _
                             ";Port=" & DB_PORT & ";Database=" & DB_NAME & _
                             ";User=" & DB_USER & ";Password=" & DB_PASSWORD & _
                             ";Option=3;Pooling=True;Max Pool Size=100;"
    End If
End Function
%>
```

---

## Migration Guide

### Updating Existing Files to Meet Standards

**Priority Order:**
1. Add authentication check
2. Fix SQL injection vulnerabilities
3. Add HTML encoding to output
4. Add error handling
5. Add file header documentation
6. Refactor for code quality

### Example Migration

**Before (Non-Compliant):**
```vbscript
<!DOCTYPE html>
<html>
<head><title>Machine</title></head>
<body>
<%
machineid = Request.QueryString("machineid")
strSQL = "SELECT * FROM machines WHERE machineid = " & machineid
set rs = objconn.Execute(strSQL)
%>
<h1><%=rs("machinename")%></h1>
</body>
</html>
```

**After (Standards-Compliant):**
```vbscript
<%@ Language=VBScript %>
<%
Option Explicit
%>
<!--#include file="./includes/config.asp"-->
<!--#include file="./includes/sql.asp"-->
<!--#include file="./includes/validation.asp"-->
<!--#include file="./includes/auth_check.asp"-->
<!--#include file="./includes/error_handler.asp"-->
<!DOCTYPE html>
<html lang="en">
<head>
  <!--#include file="./includes/header.asp"-->
  <title>Machine Details</title>
</head>
<%
'-----------------------------------------------------------------------------
' FILE: displaymachine.asp
' PURPOSE: Display machine details
'-----------------------------------------------------------------------------

Call RequireAuthentication()
Call InitializeErrorHandling("displaymachine.asp")

Dim machineId, strSQL, objRS

machineId = GetSafeInteger("QS", "machineid", 0, 1, 999999)
If machineId = 0 Then
    Call CleanupResources()
    Response.Redirect("error.asp?msg=INVALID_ID")
    Response.End
End If

strSQL = "SELECT * FROM machines WHERE machineid = ?"
Set objRS = ExecuteParameterizedQuery(objConn, strSQL, Array(machineId))
Call CheckForErrors()

If objRS.EOF Then
    Call CleanupResources()
    Response.Redirect("error.asp?msg=NOT_FOUND")
    Response.End
End If
%>
<body>
<h1><%=Server.HTMLEncode(objRS("machinename"))%></h1>
</body>
</html>
<%
Call CleanupResources()
%>
```

---

## Enforcement

### Code Review Process

**MANDATORY:** All code changes must be reviewed before deployment.

**Reviewer Checklist:**
1. Standards compliance verified
2. Security vulnerabilities checked
3. Performance impact assessed
4. Documentation adequate
5. Tests passed

### Automated Checks

**RECOMMENDED:** Implement automated scanning where possible.

**Tools:**
- SQL injection scanner
- XSS vulnerability scanner
- Code style checker
- Dead code detector

### Training

**REQUIRED:** All developers must:
1. Read this standards document
2. Complete security training
3. Review example compliant code
4. Pass knowledge assessment

---

## Version History

| Version | Date | Changes | Author |
|---------|------|---------|--------|
| 1.0 | 2025-10-10 | Initial standards document created from audit findings | Claude |
| 1.1 | 2025-12-11 | Updated for Phase 2 schema (unified machines table), added test script reference, secrets management, column naming gotchas | Claude |
| 1.2 | 2025-12-12 | Added DSN toggle documentation, API endpoint testing section, updated PC identification (machinetypeid=33 only), added comid column gotcha, noted auth not yet implemented, updated config file structure | Claude |

---

## Questions & Support

For questions about these standards:
1. Review the examples in this document
2. Check existing compliant code for patterns
3. Consult with team lead
4. Document unclear areas for future clarification

---

**REMEMBER:** These standards exist to protect our application and data. Following them is not optional—it's a requirement for all development work.
