diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b8fc2bf..4e796ed 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,3 +2,4 @@ /.github/ @aws/aws-code-editor /patches/sagemaker.series @aws/aws-code-editor @aws/sagemaker-code-editor /patches/sagemaker/ @aws/aws-code-editor @aws/sagemaker-code-editor +/sagemaker-tests/ @aws/aws-code-editor @aws/sagemaker-code-editor diff --git a/.gitignore b/.gitignore index da03cda..55b6cc3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ build-artifacts build-private code-editor-src vscode-reh-web-* -code-editor-src-* \ No newline at end of file +code-editor-src-* +node_modules +package-lock.json +package.json \ No newline at end of file diff --git a/sagemaker-tests/README.md b/sagemaker-tests/README.md new file mode 100644 index 0000000..d6ace57 --- /dev/null +++ b/sagemaker-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/sagemaker-tests/base-path-compatibility.test.ts b/sagemaker-tests/base-path-compatibility.test.ts new file mode 100644 index 0000000..79d8c4a --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/custom-extensions-marketplace.test.ts b/sagemaker-tests/custom-extensions-marketplace.test.ts new file mode 100644 index 0000000..94f0193 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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'); + const productPath = join(PATCHED_VSCODE_DIR, 'product.json'); + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + const productContent = readFileSync(productPath, '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 - not needed for Code Editor, reference patches/web-server/marketplace.diff and patches/common/integration.diff + // 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 (!productContent.includes(openVsxGallery)) { + throw new Error(`Expected open-vsx gallery URL not found in ${productPath}`); + } + + // Check for item URL + const itemUrl = '"itemUrl": "https://open-vsx.org/vscode/item",'; + if (!productContent.includes(itemUrl)) { + throw new Error(`Expected open-vsx item URL not found in ${productPath}`); + } + + // Check for resource URL template + const resourceUrl = '"resourceUrlTemplate": "https://open-vsx.org/vscode/unpkg/{publisher}/{name}/{version}/{path}",'; + if (!productContent.includes(resourceUrl)) { + throw new Error(`Expected open-vsx resource URL template not found in ${productPath}`); + } + + // 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/sagemaker-tests/disable-online-services.test.ts b/sagemaker-tests/disable-online-services.test.ts new file mode 100644 index 0000000..7dd4f65 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/disable-telemetry.test.ts b/sagemaker-tests/disable-telemetry.test.ts new file mode 100644 index 0000000..419b5db --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/display-language.test.ts b/sagemaker-tests/display-language.test.ts new file mode 100644 index 0000000..9e83e7f --- /dev/null +++ b/sagemaker-tests/display-language.test.ts @@ -0,0 +1,72 @@ +import { readFileSync, existsSync } from 'fs'; +import { join } from 'path'; +import './test-framework'; + +const PATCHED_VSCODE_DIR = join(process.cwd(), 'code-editor-src'); + +describe('display-language.diff validation', () => { + test('remoteLanguagePacks.ts should have locale functions', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/server/node/remoteLanguagePacks.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + if (!content.includes('export const getLocaleFromConfig') || + !content.includes('export async function getBrowserNLSConfiguration')) { + throw new Error(`Failed to find locale functions in ${filePath}`); + } + + console.log('PASS: Locale functions found in remoteLanguagePacks.ts'); + }); + + test('webClientServer.ts should use locale from args', () => { + 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'); + + if (!content.includes('this._environmentService.args.locale')) { + throw new Error(`Failed to find locale from args in ${filePath}`); + } + + console.log('PASS: Locale from args found in webClientServer.ts'); + }); + + test('serverEnvironmentService.ts should have locale option', () => { + 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'); + + if (!content.includes("'locale': { type: 'string' }")) { + throw new Error(`Failed to find locale option in ${filePath}`); + } + + console.log('PASS: Locale option found in serverEnvironmentService.ts'); + }); + + test('languagePacks.ts should use remote service', () => { + const filePath = join(PATCHED_VSCODE_DIR, 'src/vs/platform/languagePacks/browser/languagePacks.ts'); + + if (!existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = readFileSync(filePath, 'utf8'); + + if (!content.includes('ProxyChannel.toService')) { + throw new Error(`Failed to find ProxyChannel usage in ${filePath}`); + } + + console.log('PASS: Remote language pack service found in languagePacks.ts'); + }); +}); diff --git a/sagemaker-tests/globals.d.ts b/sagemaker-tests/globals.d.ts new file mode 100644 index 0000000..b63ece9 --- /dev/null +++ b/sagemaker-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/sagemaker-tests/license.test.ts b/sagemaker-tests/license.test.ts new file mode 100644 index 0000000..4a06935 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/local-storage.test.ts b/sagemaker-tests/local-storage.test.ts new file mode 100644 index 0000000..71a173e --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/post-startup-notifications.test.ts b/sagemaker-tests/post-startup-notifications.test.ts new file mode 100644 index 0000000..8223ad8 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-extension-smus-support.test.ts b/sagemaker-tests/sagemaker-extension-smus-support.test.ts new file mode 100644 index 0000000..d0442e1 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-extension.test.ts b/sagemaker-tests/sagemaker-extension.test.ts new file mode 100644 index 0000000..13bfaaf --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-extensions-sync.test.ts b/sagemaker-tests/sagemaker-extensions-sync.test.ts new file mode 100644 index 0000000..f229fdf --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-idle-extension.test.ts b/sagemaker-tests/sagemaker-idle-extension.test.ts new file mode 100644 index 0000000..34d4e84 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-integration.test.ts b/sagemaker-tests/sagemaker-integration.test.ts new file mode 100644 index 0000000..1b734b8 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-open-notebook-extension.test.ts b/sagemaker-tests/sagemaker-open-notebook-extension.test.ts new file mode 100644 index 0000000..7d4fff1 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-ui-dark-theme.test.ts b/sagemaker-tests/sagemaker-ui-dark-theme.test.ts new file mode 100644 index 0000000..5de9baf --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/sagemaker-ui-post-startup.test.ts b/sagemaker-tests/sagemaker-ui-post-startup.test.ts new file mode 100644 index 0000000..fa6fe49 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/signature-verification.test.ts b/sagemaker-tests/signature-verification.test.ts new file mode 100644 index 0000000..65bebe9 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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 - not required in Code Editor due to patches/common/allow-unused-vars.diff + // 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 - not required in Code Editor due to patches/common/allow-unused-vars.diff + // 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/sagemaker-tests/terminal-crash-mitigation.test.ts b/sagemaker-tests/terminal-crash-mitigation.test.ts new file mode 100644 index 0000000..a018db5 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/test-framework.ts b/sagemaker-tests/test-framework.ts new file mode 100644 index 0000000..4559973 --- /dev/null +++ b/sagemaker-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/sagemaker-tests/tsconfig.json b/sagemaker-tests/tsconfig.json new file mode 100644 index 0000000..74e907c --- /dev/null +++ b/sagemaker-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/sagemaker-tests/update-csp.test.ts b/sagemaker-tests/update-csp.test.ts new file mode 100644 index 0000000..c64d4a7 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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/sagemaker-tests/webview.test.ts b/sagemaker-tests/webview.test.ts new file mode 100644 index 0000000..2c1ec78 --- /dev/null +++ b/sagemaker-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(), 'code-editor-src'); + +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-Oi71Tq4Buohx0KDH3yEbVJUzABnqYv9iVLo420HZXqI=' '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' 'wasm-unsafe-eval' 'sha256-yhZXuB8LS6t73dvNg6rtLX8y4PHLnqRm5+6DdOGkOcw=' https: http://localhost:* blob:;"; + + 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'); + }); +}); diff --git a/scripts/run-sagemaker-unit-tests.sh b/scripts/run-sagemaker-unit-tests.sh new file mode 100755 index 0000000..148fa90 --- /dev/null +++ b/scripts/run-sagemaker-unit-tests.sh @@ -0,0 +1,70 @@ +#!/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 code-editor-src exists, if not prepare it +if [ ! -d "code-editor-src" ]; then + echo "INFO: code-editor-src not found, preparing source..." + ./scripts/prepare-src.sh code-editor-sagemaker-server +fi + +# 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="sagemaker-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