diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0467d904d..5960bf5d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,20 +19,22 @@ jobs: with: submodules: recursive - # Verify CSP line exists in target TypeScript file - - name: Check CSP configuration in webClientServer.ts - run: | - TARGET_FILE="patched-vscode/src/vs/server/node/webClientServer.ts" - REQUIRED_TEXT="'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;'" + # Install dependencies + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' - if [ ! -f "$TARGET_FILE" ]; then - echo "❌ FAIL: Target file $TARGET_FILE does not exist." - exit 1 - fi + # Install quilt for patch management + - name: Install quilt + run: sudo apt-get update && sudo apt-get install -y quilt - if grep -F "$REQUIRED_TEXT" "$TARGET_FILE" > /dev/null; then - echo "✅ PASS: Required CSP text exists." - else - echo "❌ FAIL: Required CSP text NOT found in $TARGET_FILE" - exit 1 - fi \ No newline at end of file + # Install system dependencies for native modules + - name: Install system dependencies + run: sudo apt-get install -y libkrb5-dev libx11-dev libxkbfile-dev pkg-config + + # Run unit tests + - name: Run patch validation tests + run: | + chmod +x ./scripts/run-unit-tests.sh + ./scripts/run-unit-tests.sh \ No newline at end of file diff --git a/scripts/run-unit-tests.sh b/scripts/run-unit-tests.sh new file mode 100644 index 000000000..1b006a532 --- /dev/null +++ b/scripts/run-unit-tests.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -e + +echo "INFO: Running SageMaker Code Editor Unit Tests" + +# Get project root +PROJ_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +cd "$PROJ_ROOT" + +# Check if Node.js and npx are available +if ! command -v node &> /dev/null || ! command -v npx &> /dev/null; then + echo "ERROR: Node.js and npm are required to run tests" + exit 1 +fi + +#Install required dependencies +echo "Installing dependencies..." +npm install -g typescript +npm install --save-dev @types/node + +# Compile and run each test file +TEST_DIR="tests" +FAILED_TESTS=0 +TOTAL_TESTS=0 + +# First compile all TypeScript files +echo "Compiling TypeScript files..." +if ! npx tsc --project "$TEST_DIR/tsconfig.json" --outDir /tmp/tests; then + echo "ERROR: TypeScript compilation failed" + exit 1 +fi + +for test_file in "$TEST_DIR"/*.test.ts; do + if [ -f "$test_file" ]; then + TOTAL_TESTS=$((TOTAL_TESTS + 1)) + test_name=$(basename "$test_file" .test.ts) + + echo "Running $test_name tests..." + + # Run the compiled JavaScript + if node "/tmp/tests/$(basename "$test_file" .ts).js"; then + echo "SUCCESS: $test_name tests passed" + else + echo "FAILED: $test_name tests failed" + FAILED_TESTS=$((FAILED_TESTS + 1)) + fi + echo "" + fi +done + +# Summary +echo "INFO: Test Summary:" +echo "Total test suites: $TOTAL_TESTS" +echo "Failed test suites: $FAILED_TESTS" +echo "Passed test suites: $((TOTAL_TESTS - FAILED_TESTS))" + +if [ $FAILED_TESTS -eq 0 ]; then + echo "SUCCESS: All tests passed!" + exit 0 +else + echo "FAILED: $FAILED_TESTS test suite(s) failed" + exit 1 +fi diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..d6ace57fc --- /dev/null +++ b/tests/README.md @@ -0,0 +1,60 @@ +# SageMaker Code Editor Unit Tests + +This directory contains TypeScript unit tests that validate all patches applied to the VSCode codebase. + +## Test Structure + +Each patch file in `patches/series` has a corresponding test file: + +- `sagemaker-extension.test.ts` - Validates sagemaker-extension.diff patch +- `disable-online-services.test.ts` - Validates disable-online-services.diff patch +- `disable-telemetry.test.ts` - Validates disable-telemetry.diff patch +- `update-csp.test.ts` - Validates update-csp.diff patch +- `webview.test.ts` - Validates webview.diff patch +- `local-storage.test.ts` - Validates local-storage.diff patch +- `sagemaker-integration.test.ts` - Validates sagemaker-integration.diff patch +- `license.test.ts` - Validates license.diff patch +- `base-path-compatibility.test.ts` - Validates base-path-compatibility.diff patch +- `sagemaker-idle-extension.test.ts` - Validates sagemaker-idle-extension.patch +- `terminal-crash-mitigation.test.ts` - Validates terminal-crash-mitigation.patch +- `sagemaker-open-notebook-extension.test.ts` - Validates sagemaker-open-notebook-extension.patch +- `sagemaker-ui-dark-theme.test.ts` - Validates sagemaker-ui-dark-theme.patch +- `sagemaker-ui-post-startup.test.ts` - Validates sagemaker-ui-post-startup.patch +- `sagemaker-extension-smus-support.test.ts` - Validates sagemaker-extension-smus-support.patch +- `post-startup-notifications.test.ts` - Validates post-startup-notifications.patch +- `sagemaker-extensions-sync.test.ts` - Validates sagemaker-extensions-sync.patch +- `custom-extensions-marketplace.test.ts` - Validates custom-extensions-marketplace.diff patch +- `signature-verification.test.ts` - Validates signature-verification.diff patch +- `display-language.test.ts` - Validates display-language.patch + +**Total: 20 test files covering all patches in the series** + +## Running Tests + +### Locally +```bash +./scripts/run-unit-tests.sh +``` + +### In CI +Tests run automatically on every push via GitHub Actions in `.github/workflows/ci.yml` + +## Test Framework + +Tests use a simple Node.js-based framework defined in `test-framework.ts` with: +- `describe()` - Test suite grouping +- `test()` - Individual test cases + +## What Tests Validate + +Tests check that: +1. Patches are properly applied to `patched-vscode/` directory +2. Expected code modifications exist in target files +3. New files/directories are created where needed +4. Configuration changes are present + +## Requirements + +- Node.js 20+ +- TypeScript compiler (npx tsc) +- Patches must be applied (script handles this automatically) diff --git a/tests/base-path-compatibility.test.ts b/tests/base-path-compatibility.test.ts new file mode 100644 index 000000000..abb15043c --- /dev/null +++ b/tests/base-path-compatibility.test.ts @@ -0,0 +1,37 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('base-path-compatibility.diff validation', () => { + test('serverEnvironmentService.ts should have base-path option added', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/server/node/serverEnvironmentService.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for base-path option in serverOptions + const basePathOption = "'base-path': { type: 'string' },"; + if (!content.includes(basePathOption)) { + throw new Error(`Expected base-path option not found in ${filePath}`); + } + + // Check for base-path in ServerParsedArgs interface + const basePathArg = "'base-path'?: string,"; + if (!content.includes(basePathArg)) { + throw new Error(`Expected base-path argument type not found in ${filePath}`); + } + + // Check for constructor modification + const constructorLogic = "if (args['base-path']) {\n\t\t\targs['server-base-path'] = args['base-path'];\n\t\t}"; + if (!content.includes(constructorLogic)) { + throw new Error(`Expected constructor base-path mapping not found in ${filePath}`); + } + + console.log('PASS: Base path compatibility modifications found in serverEnvironmentService.ts'); + }); +}); diff --git a/tests/custom-extensions-marketplace.test.ts b/tests/custom-extensions-marketplace.test.ts new file mode 100644 index 000000000..ff6106882 --- /dev/null +++ b/tests/custom-extensions-marketplace.test.ts @@ -0,0 +1,61 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('custom-extensions-marketplace.diff validation', () => { + test('product.ts should have custom extensions gallery logic', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/platform/product/common/product.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for custom extensions gallery environment variable check + const customGalleryCheck = "if (env['EXTENSIONS_GALLERY']) {"; + if (!content.includes(customGalleryCheck)) { + throw new Error(`Expected custom extensions gallery check not found in ${filePath}`); + } + + // Check for custom gallery parsing log + const customGalleryLog = "console.log(`Custom extensions gallery detected. Parsing...`);"; + if (!content.includes(customGalleryLog)) { + throw new Error(`Expected custom gallery log not found in ${filePath}`); + } + + // Check for default gallery log + const defaultGalleryLog = "console.log(`Using default extensions gallery.`);"; + if (!content.includes(defaultGalleryLog)) { + throw new Error(`Expected default gallery log not found in ${filePath}`); + } + + // Check for open-vsx gallery configuration + const openVsxGallery = 'serviceUrl: "https://open-vsx.org/vscode/gallery",'; + if (!content.includes(openVsxGallery)) { + throw new Error(`Expected open-vsx gallery URL not found in ${filePath}`); + } + + // Check for item URL + const itemUrl = 'itemUrl: "https://open-vsx.org/vscode/item",'; + if (!content.includes(itemUrl)) { + throw new Error(`Expected open-vsx item URL not found in ${filePath}`); + } + + // Check for resource URL template + const resourceUrl = 'resourceUrlTemplate: "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}",'; + if (!content.includes(resourceUrl)) { + throw new Error(`Expected open-vsx resource URL template not found in ${filePath}`); + } + + // Check for gallery logging + const galleryLogging = "console.log(JSON.stringify(product.extensionsGallery, null, 2));"; + if (!content.includes(galleryLogging)) { + throw new Error(`Expected gallery logging not found in ${filePath}`); + } + + console.log('PASS: Custom extensions marketplace logic found in product.ts'); + }); +}); diff --git a/tests/disable-online-services.test.ts b/tests/disable-online-services.test.ts new file mode 100644 index 000000000..7715b550c --- /dev/null +++ b/tests/disable-online-services.test.ts @@ -0,0 +1,31 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('disable-online-services.diff validation', () => { + test('update.config.contribution.ts should disable automatic updates', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/platform/update/common/update.config.contribution.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check update mode is set to none + const updateModeDefault = "default: 'none',"; + if (!content.includes(updateModeDefault)) { + throw new Error(`Expected update mode 'none' not found in ${filePath}`); + } + + // Check release notes are disabled + const releaseNotesDefault = "default: false,"; + if (!content.includes(releaseNotesDefault)) { + throw new Error(`Expected release notes disabled not found in ${filePath}`); + } + + console.log('PASS: Online services disabled in update configuration'); + }); +}); diff --git a/tests/disable-telemetry.test.ts b/tests/disable-telemetry.test.ts new file mode 100644 index 000000000..fae61c585 --- /dev/null +++ b/tests/disable-telemetry.test.ts @@ -0,0 +1,73 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('disable-telemetry.diff validation', () => { + test('telemetryService.ts should have telemetry disabled by default', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/platform/telemetry/common/telemetryService.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check that enum only contains OFF + const enumLine = "'enum': [TelemetryConfiguration.OFF],"; + if (!content.includes(enumLine)) { + throw new Error(`Expected telemetry enum restriction not found in ${filePath}`); + } + + // Check that default is OFF + const defaultLine = "'default': TelemetryConfiguration.OFF,"; + if (!content.includes(defaultLine)) { + throw new Error(`Expected telemetry default OFF not found in ${filePath}`); + } + + console.log('PASS: Telemetry disabled by default in telemetryService.ts'); + }); + + test('desktop.contribution.ts should have crash reporter disabled', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/electron-sandbox/desktop.contribution.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check crash reporter is disabled + const crashReporterDisabled = "'default': false,"; + if (!content.includes(crashReporterDisabled)) { + throw new Error(`Expected crash reporter disabled not found in ${filePath}`); + } + + console.log('PASS: Crash reporter disabled in desktop.contribution.ts'); + }); + + test('1dsAppender.ts should have Microsoft endpoints blocked', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/platform/telemetry/common/1dsAppender.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check endpoints are redirected to 0.0.0.0 + const blockedEndpoint = "const endpointUrl = 'https://0.0.0.0/OneCollector/1.0';"; + const blockedHealthEndpoint = "const endpointHealthUrl = 'https://0.0.0.0/ping';"; + + if (!content.includes(blockedEndpoint)) { + throw new Error(`Expected blocked endpoint not found in ${filePath}`); + } + + if (!content.includes(blockedHealthEndpoint)) { + throw new Error(`Expected blocked health endpoint not found in ${filePath}`); + } + + console.log('PASS: Microsoft telemetry endpoints blocked in 1dsAppender.ts'); + }); +}); diff --git a/tests/display-language.test.ts b/tests/display-language.test.ts new file mode 100644 index 000000000..e87370f3b --- /dev/null +++ b/tests/display-language.test.ts @@ -0,0 +1,49 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('display-language.patch validation', () => { + test('platform.ts should have NLS modifications', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/base/common/platform.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for NLSConfig interface + const nlsConfigInterface = "interface NLSConfig {\n\tlocale: string;\n\tosLocale: string;\n\tavailableLanguages: { [key: string]: string };\n\t_translationsConfigFile: string;\n}"; + if (!content.includes(nlsConfigInterface)) { + throw new Error(`Expected NLSConfig interface not found in ${filePath}`); + } + + // Check that nls import is removed (should not be present) + const nlsImport = "import * as nls from '../../nls.js';"; + if (content.includes(nlsImport)) { + throw new Error(`NLS import should be removed from ${filePath}`); + } + + // Check for modified NLS config parsing + const nlsConfigParsing = "const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig);"; + if (!content.includes(nlsConfigParsing)) { + throw new Error(`Expected NLSConfig parsing not found in ${filePath}`); + } + + // Check for resolved language logic + const resolvedLanguage = "const resolved = nlsConfig.availableLanguages['*'];"; + if (!content.includes(resolvedLanguage)) { + throw new Error(`Expected resolved language logic not found in ${filePath}`); + } + + // Check for locale assignment changes + const localeAssignment = "_locale = nlsConfig.locale;"; + if (!content.includes(localeAssignment)) { + throw new Error(`Expected locale assignment not found in ${filePath}`); + } + + console.log('PASS: Display language modifications found in platform.ts'); + }); +}); diff --git a/tests/globals.d.ts b/tests/globals.d.ts new file mode 100644 index 000000000..b63ece9b9 --- /dev/null +++ b/tests/globals.d.ts @@ -0,0 +1,6 @@ +declare global { + function describe(name: string, fn: () => void): void; + function test(name: string, fn: () => void): void; +} + +export {}; diff --git a/tests/license.test.ts b/tests/license.test.ts new file mode 100644 index 000000000..ca21b6b2d --- /dev/null +++ b/tests/license.test.ts @@ -0,0 +1,40 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('license.diff validation', () => { + test('LICENSE file should exist with Amazon copyright', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'LICENSE'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for MIT License + if (!content.includes('MIT License')) { + throw new Error(`Expected MIT License header not found in ${filePath}`); + } + + // Check for Amazon copyright + const amazonCopyright = "Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved."; + if (!content.includes(amazonCopyright)) { + throw new Error(`Expected Amazon copyright not found in ${filePath}`); + } + + console.log('PASS: Amazon MIT License found in LICENSE file'); + }); + + test('LICENSE-THIRD-PARTY file should exist', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'LICENSE-THIRD-PARTY'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + console.log('PASS: Third-party license file exists'); + }); +}); diff --git a/tests/local-storage.test.ts b/tests/local-storage.test.ts new file mode 100644 index 000000000..8640aacc7 --- /dev/null +++ b/tests/local-storage.test.ts @@ -0,0 +1,66 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('local-storage.diff validation', () => { + test('webClientServer.ts should pass userDataPath to browser', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/server/node/webClientServer.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedLine = "userDataPath: this._environmentService.userDataPath,"; + + if (!content.includes(expectedLine)) { + throw new Error(`Expected userDataPath configuration not found in ${filePath}`); + } + + console.log('PASS: userDataPath configuration found in webClientServer.ts'); + }); + + test('web.api.ts should have userDataPath property', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/browser/web.api.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for userDataPath property + const userDataPathProperty = "readonly userDataPath?: string"; + if (!content.includes(userDataPathProperty)) { + throw new Error(`Expected userDataPath property not found in ${filePath}`); + } + + console.log('PASS: userDataPath property found in web.api.ts'); + }); + + test('environmentService.ts should have userDataPath getter', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/services/environment/browser/environmentService.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for userDataPath getter + const userDataPathGetter = "get userDataPath(): string {"; + if (!content.includes(userDataPathGetter)) { + throw new Error(`Expected userDataPath getter not found in ${filePath}`); + } + + // Check for modified userRoamingDataHome + const userRoamingDataHome = "get userRoamingDataHome(): URI { return joinPath(URI.file(this.userDataPath).with({ scheme: Schemas.vscodeRemote }), 'User'); }"; + if (!content.includes(userRoamingDataHome)) { + throw new Error(`Expected modified userRoamingDataHome not found in ${filePath}`); + } + + console.log('PASS: Local storage modifications found in environmentService.ts'); + }); +}); diff --git a/tests/post-startup-notifications.test.ts b/tests/post-startup-notifications.test.ts new file mode 100644 index 000000000..757888eb7 --- /dev/null +++ b/tests/post-startup-notifications.test.ts @@ -0,0 +1,70 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('post-startup-notifications.patch validation', () => { + test('post-startup-notifications should have .vscode/extensions.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/post-startup-notifications/.vscode/extensions.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const cleanContent = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); + const extensionsJson = JSON.parse(cleanContent); + + // Check for recommended extensions + const expectedRecommendations = ['dbaeumer.vscode-eslint', 'amodio.tsl-problem-matcher', 'ms-vscode.extension-test-runner']; + for (const recommendation of expectedRecommendations) { + if (!extensionsJson.recommendations.includes(recommendation)) { + throw new Error(`Expected recommendation '${recommendation}' not found in ${filePath}`); + } + } + + console.log('PASS: Post-startup notifications .vscode/extensions.json found'); + }); + + test('post-startup-notifications should have .vscode/launch.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/post-startup-notifications/.vscode/launch.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const cleanContent = content.replace(/^\s*\/\/.*$/gm, ''); + const launchJson = JSON.parse(cleanContent); + + // Check for Run Extension configuration + const runExtensionConfig = launchJson.configurations.find((config: any) => config.name === 'Run Extension'); + if (!runExtensionConfig) { + throw new Error(`Expected 'Run Extension' configuration not found in ${filePath}`); + } + + if (runExtensionConfig.type !== 'extensionHost') { + throw new Error(`Expected extensionHost type not found in ${filePath}`); + } + + console.log('PASS: Post-startup notifications .vscode/launch.json found'); + }); + + test('post-startup-notifications should have package.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/post-startup-notifications/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'post-startup-notifications') { + throw new Error(`Expected extension name 'post-startup-notifications', got: ${packageJson.name}`); + } + + console.log('PASS: Post-startup notifications package.json is valid'); + }); +}); diff --git a/tests/sagemaker-extension-smus-support.test.ts b/tests/sagemaker-extension-smus-support.test.ts new file mode 100644 index 000000000..157e612c9 --- /dev/null +++ b/tests/sagemaker-extension-smus-support.test.ts @@ -0,0 +1,37 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-extension-smus-support.patch validation', () => { + test('constant.ts should have SMUS support constants', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-extension/src/constant.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for SMUS service name constant + const smusServiceName = "export const SMUS_SERVICE_NAME = 'SageMakerUnifiedStudio';"; + if (!content.includes(smusServiceName)) { + throw new Error(`Expected SMUS service name constant not found in ${filePath}`); + } + + // Check for service name environment variable + const serviceNameEnvVar = "export const SERVICE_NAME_ENV_VAR = 'SERVICE_NAME';"; + if (!content.includes(serviceNameEnvVar)) { + throw new Error(`Expected service name env var constant not found in ${filePath}`); + } + + // Check for AdditionalMetadata interface extension + const additionalMetadata = "AdditionalMetadata?: {\n\t\tDataZoneDomainId?: string\n\t\tDataZoneProjectId?: string\n\t\tDataZoneDomainRegion?: string\n\t}"; + if (!content.includes(additionalMetadata)) { + throw new Error(`Expected AdditionalMetadata interface not found in ${filePath}`); + } + + console.log('PASS: SMUS support constants found in sagemaker-extension constant.ts'); + }); +}); diff --git a/tests/sagemaker-extension.test.ts b/tests/sagemaker-extension.test.ts new file mode 100644 index 000000000..f7820f92d --- /dev/null +++ b/tests/sagemaker-extension.test.ts @@ -0,0 +1,60 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-extension.diff validation', () => { + test('sagemaker-extension should have main extension.ts with required imports', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-extension/src/extension.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for SessionWarning import + const sessionWarningImport = 'import { SessionWarning } from "./sessionWarning";'; + if (!content.includes(sessionWarningImport)) { + throw new Error(`Expected SessionWarning import not found in ${filePath}`); + } + + // Check for constants import + const constantsImport = 'SAGEMAKER_METADATA_PATH,'; + if (!content.includes(constantsImport)) { + throw new Error(`Expected constants import not found in ${filePath}`); + } + + // Check for command constants + const parseCommand = "const PARSE_SAGEMAKER_COOKIE_COMMAND = 'sagemaker.parseCookies';"; + if (!content.includes(parseCommand)) { + throw new Error(`Expected parse cookie command not found in ${filePath}`); + } + + // Check for showWarningDialog function + const warningFunction = 'function showWarningDialog() {'; + if (!content.includes(warningFunction)) { + throw new Error(`Expected showWarningDialog function not found in ${filePath}`); + } + + console.log('PASS: SageMaker extension main file has required content'); + }); + + test('sagemaker-extension should have package.json with correct configuration', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-extension/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'sagemaker-extension') { + throw new Error(`Expected extension name 'sagemaker-extension', got: ${packageJson.name}`); + } + + console.log('PASS: SageMaker extension package.json is valid'); + }); +}); diff --git a/tests/sagemaker-extensions-sync.test.ts b/tests/sagemaker-extensions-sync.test.ts new file mode 100644 index 000000000..15d06091f --- /dev/null +++ b/tests/sagemaker-extensions-sync.test.ts @@ -0,0 +1,75 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-extensions-sync.patch validation', () => { + test('gulpfile.extensions.js should include sagemaker-extensions-sync', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'build/gulpfile.extensions.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedEntry = "'extensions/sagemaker-extensions-sync/tsconfig.json',"; + + if (!content.includes(expectedEntry)) { + throw new Error(`Expected gulpfile entry not found in ${filePath}`); + } + + console.log('PASS: Extensions sync added to gulpfile.extensions.js'); + }); + + test('dirs.js should include sagemaker-extensions-sync', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'build/npm/dirs.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedEntry = "'extensions/sagemaker-extensions-sync',"; + + if (!content.includes(expectedEntry)) { + throw new Error(`Expected dirs.js entry not found in ${filePath}`); + } + + console.log('PASS: Extensions sync added to dirs.js'); + }); + + test('sagemaker-extensions-sync should have .vscodeignore', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-extensions-sync/.vscodeignore'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for .vscode ignore pattern + if (!content.includes('.vscode/**')) { + throw new Error(`Expected .vscode ignore pattern not found in ${filePath}`); + } + + console.log('PASS: Extensions sync .vscodeignore found'); + }); + + test('sagemaker-extensions-sync should have package.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-extensions-sync/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'sagemaker-extensions-sync') { + throw new Error(`Expected extension name 'sagemaker-extensions-sync', got: ${packageJson.name}`); + } + + console.log('PASS: Extensions sync package.json is valid'); + }); +}); diff --git a/tests/sagemaker-idle-extension.test.ts b/tests/sagemaker-idle-extension.test.ts new file mode 100644 index 000000000..eb480e6eb --- /dev/null +++ b/tests/sagemaker-idle-extension.test.ts @@ -0,0 +1,58 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-idle-extension.patch validation', () => { + test('sagemaker-idle-extension should have README with correct description', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-idle-extension/README.md'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedDescription = "The Code Editor Idle Extension tracks user activity and logs the last active timestamp (in UTC) to a local file."; + + if (!content.includes(expectedDescription)) { + throw new Error(`Expected README description not found in ${filePath}`); + } + + console.log('PASS: SageMaker idle extension README found with correct description'); + }); + + test('sagemaker-idle-extension should have webpack config', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-idle-extension/extension-browser.webpack.config.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedEntry = "entry: {\n extension: './src/extension.ts'\n },"; + + if (!content.includes(expectedEntry)) { + throw new Error(`Expected webpack entry not found in ${filePath}`); + } + + console.log('PASS: SageMaker idle extension webpack config found'); + }); + + test('sagemaker-idle-extension should have package.json with correct name', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-idle-extension/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'sagemaker-idle-extension') { + throw new Error(`Expected extension name 'sagemaker-idle-extension', got: ${packageJson.name}`); + } + + console.log('PASS: SageMaker idle extension package.json is valid'); + }); +}); diff --git a/tests/sagemaker-integration.test.ts b/tests/sagemaker-integration.test.ts new file mode 100644 index 000000000..7922e6a6c --- /dev/null +++ b/tests/sagemaker-integration.test.ts @@ -0,0 +1,62 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-integration.diff validation', () => { + test('client.ts should exist with SagemakerServerClient class', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/browser/client.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for SagemakerServerClient class + const clientClass = "export class SagemakerServerClient extends Disposable {"; + if (!content.includes(clientClass)) { + throw new Error(`Expected SagemakerServerClient class not found in ${filePath}`); + } + + // Check for registerSagemakerCommands method call + const registerCommands = "this.registerSagemakerCommands();"; + if (!content.includes(registerCommands)) { + throw new Error(`Expected registerSagemakerCommands call not found in ${filePath}`); + } + + // Check for getCookieValue method + const getCookieMethod = "private getCookieValue(name: string): string | undefined {"; + if (!content.includes(getCookieMethod)) { + throw new Error(`Expected getCookieValue method not found in ${filePath}`); + } + + console.log('PASS: SagemakerServerClient integration found in client.ts'); + }); + + test('web.main.ts should instantiate SagemakerServerClient', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/browser/web.main.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for SagemakerServerClient import + const clientImport = "import { SagemakerServerClient } from" + + if (!content.includes(clientImport)) { + throw new Error(`Expected SagemakerServerClient import not found in ${filePath}`); + } + + // Check for SagemakerServerClient instantiation with register + const clientInstantiation = "this._register(instantiationService.createInstance(SagemakerServerClient));"; + if (!content.includes(clientInstantiation)) { + throw new Error(`Expected SagemakerServerClient instantiation not found in ${filePath}`); + } + + console.log('PASS: SagemakerServerClient integration found in web.main.ts'); + }); +}); diff --git a/tests/sagemaker-open-notebook-extension.test.ts b/tests/sagemaker-open-notebook-extension.test.ts new file mode 100644 index 000000000..599adf500 --- /dev/null +++ b/tests/sagemaker-open-notebook-extension.test.ts @@ -0,0 +1,68 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-open-notebook-extension.patch validation', () => { + test('gulpfile.extensions.js should include sagemaker-open-notebook-extension', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'build/gulpfile.extensions.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedEntry = "'extensions/sagemaker-open-notebook-extension/tsconfig.json',"; + + if (!content.includes(expectedEntry)) { + throw new Error(`Expected gulpfile entry not found in ${filePath}`); + } + + console.log('PASS: Open notebook extension added to gulpfile.extensions.js'); + }); + + test('dirs.js should include sagemaker-open-notebook-extension', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'build/npm/dirs.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedEntry = "'extensions/sagemaker-open-notebook-extension',"; + + if (!content.includes(expectedEntry)) { + throw new Error(`Expected dirs.js entry not found in ${filePath}`); + } + + console.log('PASS: Open notebook extension added to dirs.js'); + }); + + test('sagemaker-open-notebook-extension should have package.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-open-notebook-extension/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'sagemaker-open-notebook-extension') { + throw new Error(`Expected extension name 'sagemaker-open-notebook-extension', got: ${packageJson.name}`); + } + + console.log('PASS: Open notebook extension package.json is valid'); + }); + + test('sagemaker-open-notebook-extension should have main extension file', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-open-notebook-extension/src/extension.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + console.log('PASS: Open notebook extension main file exists'); + }); +}); diff --git a/tests/sagemaker-ui-dark-theme.test.ts b/tests/sagemaker-ui-dark-theme.test.ts new file mode 100644 index 000000000..68f7bd005 --- /dev/null +++ b/tests/sagemaker-ui-dark-theme.test.ts @@ -0,0 +1,79 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-ui-dark-theme.patch validation', () => { + test('sagemaker-ui-dark-theme should have README', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-ui-dark-theme/README.md'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedTitle = '# SageMaker UI Dark Theme'; + + if (!content.includes(expectedTitle)) { + throw new Error(`Expected README title not found in ${filePath}`); + } + + console.log('PASS: SageMaker UI dark theme README found'); + }); + + test('sagemaker-ui-dark-theme should have .vscodeignore', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-ui-dark-theme/.vscodeignore'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for specific ignore patterns + const ignorePatterns = ['.vscode/**', 'src/**', 'cgmanifest.json']; + for (const pattern of ignorePatterns) { + if (!content.includes(pattern)) { + throw new Error(`Expected ignore pattern '${pattern}' not found in ${filePath}`); + } + } + + console.log('PASS: UI dark theme .vscodeignore found'); + }); + + test('sagemaker-ui-dark-theme should have webpack config', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-ui-dark-theme/extension-browser.webpack.config.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for Amazon copyright + const copyright = 'Copyright Amazon.com Inc. or its affiliates. All rights reserved.'; + if (!content.includes(copyright)) { + throw new Error(`Expected Amazon copyright not found in ${filePath}`); + } + + console.log('PASS: UI dark theme webpack config found'); + }); + + test('sagemaker-ui-dark-theme should have package.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-ui-dark-theme/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'sagemaker-ui-dark-theme') { + throw new Error(`Expected extension name 'sagemaker-ui-dark-theme', got: ${packageJson.name}`); + } + + console.log('PASS: UI dark theme package.json is valid'); + }); +}); diff --git a/tests/sagemaker-ui-post-startup.test.ts b/tests/sagemaker-ui-post-startup.test.ts new file mode 100644 index 000000000..ec18a039c --- /dev/null +++ b/tests/sagemaker-ui-post-startup.test.ts @@ -0,0 +1,63 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('sagemaker-ui-post-startup.patch validation', () => { + test('webClientServer.ts should have post-startup imports and constants', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/server/node/webClientServer.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for spawn import + const spawnImport = "import { spawn } from 'child_process';"; + if (!content.includes(spawnImport)) { + throw new Error(`Expected spawn import not found in ${filePath}`); + } + + // Check for fs import + const fsImport = "import * as fs from 'fs';"; + if (!content.includes(fsImport)) { + throw new Error(`Expected fs import not found in ${filePath}`); + } + + // Check for ServiceName enum + const serviceNameEnum = "const enum ServiceName {\n\tSAGEMAKER_UNIFIED_STUDIO = 'SageMakerUnifiedStudio',\n}"; + if (!content.includes(serviceNameEnum)) { + throw new Error(`Expected ServiceName enum not found in ${filePath}`); + } + + // Check for POST_STARTUP_SCRIPT_PATH constant + const postStartupPath = "const POST_STARTUP_SCRIPT_PATH = `/api/poststartup`;"; + if (!content.includes(postStartupPath)) { + throw new Error(`Expected POST_STARTUP_SCRIPT_PATH constant not found in ${filePath}`); + } + + console.log('PASS: Post-startup modifications found in webClientServer.ts'); + }); + + test('gettingStarted.ts should exist and may contain UI modifications', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + console.log('PASS: gettingStarted.ts file exists'); + }); + + test('gettingStartedContent.ts should exist and may contain content modifications', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + console.log('PASS: gettingStartedContent.ts file exists'); + }); +}); diff --git a/tests/signature-verification.test.ts b/tests/signature-verification.test.ts new file mode 100644 index 000000000..672bfe331 --- /dev/null +++ b/tests/signature-verification.test.ts @@ -0,0 +1,37 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('signature-verification.diff validation', () => { + test('extensionManagementService.ts should have signature verification disabled', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/platform/extensionManagement/node/extensionManagementService.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for first @ts-expect-error comment before VerifyExtensionSignatureConfigKey + const firstBypassComment = "\t// @ts-expect-error no-unused-variable\n\tVerifyExtensionSignatureConfigKey,"; + if (!content.includes(firstBypassComment)) { + throw new Error(`Expected first @ts-expect-error comment not found in ${filePath}`); + } + + // Check for second @ts-expect-error comment before configurationService + const secondBypassComment = "\t\t// @ts-expect-error no-unused-variable\n\t\t@IConfigurationService private readonly configurationService: IConfigurationService,"; + if (!content.includes(secondBypassComment)) { + throw new Error(`Expected second @ts-expect-error comment not found in ${filePath}`); + } + + // Check for verifySignature = false modification + const verifySignatureFalse = "\t\t\tverifySignature = false;"; + if (!content.includes(verifySignatureFalse)) { + throw new Error(`Expected verifySignature = false not found in ${filePath}`); + } + + console.log('PASS: All signature verification modifications found in extensionManagementService.ts'); + }); +}); diff --git a/tests/terminal-crash-mitigation.test.ts b/tests/terminal-crash-mitigation.test.ts new file mode 100644 index 000000000..62d2a2c8b --- /dev/null +++ b/tests/terminal-crash-mitigation.test.ts @@ -0,0 +1,62 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('terminal-crash-mitigation.patch validation', () => { + test('sagemaker-terminal-crash-mitigation should have .vscodeignore', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-terminal-crash-mitigation/.vscodeignore'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for specific ignore patterns + const ignorePatterns = ['.vscode/**', 'src/**', 'tsconfig.json']; + for (const pattern of ignorePatterns) { + if (!content.includes(pattern)) { + throw new Error(`Expected ignore pattern '${pattern}' not found in ${filePath}`); + } + } + + console.log('PASS: Terminal crash mitigation .vscodeignore found'); + }); + + test('sagemaker-terminal-crash-mitigation should have webpack config', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-terminal-crash-mitigation/extension-browser.webpack.config.js'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + // Check for Amazon copyright + const copyright = 'Copyright Amazon.com Inc. or its affiliates. All rights reserved.'; + if (!content.includes(copyright)) { + throw new Error(`Expected Amazon copyright not found in ${filePath}`); + } + + console.log('PASS: Terminal crash mitigation webpack config found'); + }); + + test('sagemaker-terminal-crash-mitigation should have package.json', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'extensions/sagemaker-terminal-crash-mitigation/package.json'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const packageJson = JSON.parse(content); + + if (packageJson.name !== 'sagemaker-terminal-crash-mitigation') { + throw new Error(`Expected extension name 'sagemaker-terminal-crash-mitigation', got: ${packageJson.name}`); + } + + console.log('PASS: Terminal crash mitigation package.json is valid'); + }); +}); diff --git a/tests/test-framework.ts b/tests/test-framework.ts new file mode 100644 index 000000000..45599739d --- /dev/null +++ b/tests/test-framework.ts @@ -0,0 +1,24 @@ +// Simple test framework for Node.js +export function describe(name: string, fn: () => void) { + console.log(`\n${name}`); + try { + fn(); + } catch (error) { + console.error(`Test suite failed: ${error.message}`); + process.exit(1); + } +} + +export function test(name: string, fn: () => void) { + try { + fn(); + console.log(` ${name}`); + } catch (error) { + console.error(`${name}: ${error.message}`); + throw error; + } +} + +// Make functions global for test files +(global as any).describe = describe; +(global as any).test = test; diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 000000000..74e907cd9 --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["*.ts", "globals.d.ts"] +} diff --git a/tests/update-csp.test.ts b/tests/update-csp.test.ts new file mode 100644 index 000000000..0620b26f7 --- /dev/null +++ b/tests/update-csp.test.ts @@ -0,0 +1,51 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('update-csp.diff validation', () => { + test('webClientServer.ts should have required CSP configuration', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/server/node/webClientServer.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const cspKeywords = [ + 'connect-src', + 'https://main.vscode-cdn.net', + 'http://localhost:*', + 'https://localhost:*', + 'https://login.microsoftonline.com/', + 'https://update.code.visualstudio.com', + 'https://*.vscode-unpkg.net/', + 'https://default.exp-tas.com/vscode/ab', + 'https://vscode-sync.trafficmanager.net', + 'https://vscode-sync-insiders.trafficmanager.net', + 'https://*.gallerycdn.vsassets.io', + 'https://marketplace.visualstudio.com', + 'https://openvsxorg.blob.core.windows.net', + 'https://az764295.vo.msecnd.net', + 'https://code.visualstudio.com', + 'https://*.gallery.vsassets.io', + 'https://*.rel.tunnels.api.visualstudio.com', + 'https://*.servicebus.windows.net/', + 'https://vscode.blob.core.windows.net', + 'https://vscode.search.windows.net', + 'https://vsmarketplacebadges.dev', + 'https://vscode.download.prss.microsoft.com', + 'https://download.visualstudio.microsoft.com', + 'https://*.vscode-unpkg.net', + 'https://open-vsx.org' + ]; + + const hasAllKeywords = cspKeywords.every(keyword => content.includes(keyword)); + if (!hasAllKeywords) { + throw new Error(`Required CSP directive not found in ${filePath}`); + } + + console.log('PASS: Required CSP configuration found in webClientServer.ts'); + }); +}); diff --git a/tests/webview.test.ts b/tests/webview.test.ts new file mode 100644 index 000000000..66a0e2193 --- /dev/null +++ b/tests/webview.test.ts @@ -0,0 +1,109 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'patched-vscode'); + +describe('webview.diff validation', () => { + test('environmentService.ts should have webview endpoint modification', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/services/environment/browser/environmentService.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedLine = 'const endpoint = (this.options.webviewEndpoint && new URL(this.options.webviewEndpoint, window.location.toString()).toString())'; + + if (!content.includes(expectedLine)) { + throw new Error(`Expected webview endpoint modification not found in ${filePath}`); + } + + console.log('PASS: webview endpoint modification found in environmentService.ts'); + }); + + test('webClientServer.ts should have webviewEndpoint configuration', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/server/node/webClientServer.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedLine = "webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre',"; + + if (!content.includes(expectedLine)) { + throw new Error(`Expected webviewEndpoint configuration not found in ${filePath}`); + } + + console.log('PASS: webviewEndpoint configuration found in webClientServer.ts'); + }); + + test('webview pre/index.html should have updated CSP hash', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/contrib/webview/browser/pre/index.html'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedHash = "script-src 'sha256-1qYtPnTQa4VwKNJO61EOhs2agF9TvuQSYIJ27OgzZqI=' 'self'"; + + if (!content.includes(expectedHash)) { + throw new Error(`Expected CSP hash not found in ${filePath}`); + } + + console.log('PASS: Updated CSP hash found in webview pre/index.html'); + }); + + test('webview pre/index.html should have hostname bypass logic', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/contrib/webview/browser/pre/index.html'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedLogic = 'if (parent.hostname === hostname) {\n\t\t\t\t\treturn start(parentOrigin)\n\t\t\t\t}'; + + if (!content.includes(expectedLogic)) { + throw new Error(`Expected hostname bypass logic not found in ${filePath}`); + } + + console.log('PASS: Hostname bypass logic found in webview pre/index.html'); + }); + + test('webWorkerExtensionHostIframe.html should have updated CSP hash', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedHash = "script-src 'self' 'unsafe-eval' 'sha256-yhZXuB8LS6t73dvNg6rtLX8y4PHLnqRm5+6DdOGkOcw=' https:;"; + + if (!content.includes(expectedHash)) { + throw new Error(`Expected CSP hash not found in ${filePath}`); + } + + console.log('PASS: Updated CSP hash found in webWorkerExtensionHostIframe.html'); + }); + + test('webWorkerExtensionHostIframe.html should have hostname bypass logic', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const expectedLogic = 'if (parent.hostname === hostname) {\n\t\t\treturn start()\n\t\t}'; + + if (!content.includes(expectedLogic)) { + throw new Error(`Expected hostname bypass logic not found in ${filePath}`); + } + + console.log('PASS: Hostname bypass logic found in webWorkerExtensionHostIframe.html'); + }); +});