// Copyright (c) 2025, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. import path from 'node:path'; import {beforeEach, describe, expect, it} from 'vitest'; import {assert, check_path, removeFileProtocol, setBaseDirectory, unwrap, unwrapString} from '../lib/assert.js'; describe('Assert module', () => { // Reset the base directory for each test beforeEach(() => { setBaseDirectory(new URL('file:///test/path/')); }); describe('setBaseDirectory', () => { it('should set the base directory from a URL', () => { const testUrl = new URL('file:///test/path/'); setBaseDirectory(testUrl); // We can verify the base directory was set by testing check_path // which uses the base directory internally const result = check_path('/test/path/', '/test/path/subdir/file.js'); expect(result).toEqual('subdir/file.js'); }); }); describe('removeFileProtocol', () => { it('should remove file:// prefix', () => { expect(removeFileProtocol('file:///path/to/file')).toEqual('/path/to/file'); }); it('should not modify paths without file:// prefix', () => { expect(removeFileProtocol('/path/to/file')).toEqual('/path/to/file'); }); }); describe('check_path', () => { it('should return relative path for valid subdirectories', () => { // This test now uses normalized paths with forward slashes for cross-platform compatibility expect(check_path('/root', '/root/sub/file.js')).toEqual('sub/file.js'); }); it('should return false for paths outside the parent directory', () => { expect(check_path('/root/sub', '/root/file.js')).toBe(false); expect(check_path('/root', '/other/file.js')).toBe(false); }); it('should return false for absolute paths', () => { expect(check_path('/root', '/root')).toBe(false); }); }); describe('get_diagnostic function components', () => { // Since get_diagnostic relies on stack traces which are hard to test directly, // we test each component separately to verify the logic is correct describe('stack trace handling', () => { it('should correctly process file paths with removeFileProtocol', () => { const pathWithProtocol = 'file:///path/to/file.js'; expect(removeFileProtocol(pathWithProtocol)).toEqual('/path/to/file.js'); const pathWithoutProtocol = '/path/to/file.js'; expect(removeFileProtocol(pathWithoutProtocol)).toEqual('/path/to/file.js'); }); it('should properly determine if a path is within the base directory using check_path', () => { // Valid paths inside base directory - these paths use forward slashes regardless of platform // for cross-platform test compatibility const testRoot = '/base/dir'; const testFile = path.join(testRoot, 'file.js'); const testSubdirFile = path.join(testRoot, 'subdir', 'file.js'); expect(check_path(testRoot, testFile)).toEqual('file.js'); expect(check_path(testRoot, testSubdirFile)).toEqual('subdir/file.js'); // Invalid paths expect(check_path('/base/dir', '/other/path/file.js')).toBe(false); expect(check_path('/base/dir', '/base')).toBe(false); }); it('should handle the combination of file protocol removal and path checking', () => { const baseDir = '/base/dir'; const filePathWithProtocol = 'file:///base/dir/file.js'; // First remove protocol (as done in get_diagnostic) const filePath = removeFileProtocol(filePathWithProtocol); // Then check if path is within base directory (as done in get_diagnostic) const relativePath = check_path(baseDir, filePath); // The relative path should be 'file.js' because it's directly in the base dir expect(relativePath).toEqual('file.js'); }); }); describe('file content extraction', () => { it('should correctly extract a specific line from file content', () => { // This mimics the line extraction logic in get_diagnostic const fileContent = 'line1\nline2\nline3\nline4\nline5'; const lines = fileContent.split('\n'); expect(lines[0].trim()).toEqual('line1'); expect(lines[2].trim()).toEqual('line3'); expect(lines[4].trim()).toEqual('line5'); }); }); }); describe('assert', () => { it('should not throw for truthy values', () => { expect(() => assert(true)).not.toThrow(); expect(() => assert(1)).not.toThrow(); expect(() => assert('string')).not.toThrow(); expect(() => assert({})).not.toThrow(); }); it('should throw for falsy values', () => { expect(() => assert(false)).toThrow('Assertion failed'); expect(() => assert(0)).toThrow('Assertion failed'); expect(() => assert('')).toThrow('Assertion failed'); expect(() => assert(null)).toThrow('Assertion failed'); expect(() => assert(undefined)).toThrow('Assertion failed'); }); it('should include custom message in error', () => { expect(() => assert(false, 'Custom message')).toThrow(/Custom message/); }); it('should include extra info in error message', () => { const extra = {a: 1, b: 2}; expect(() => assert(false, 'Message', extra)).toThrow(JSON.stringify([extra])); }); it('should include the assertion message', () => { try { assert(false, 'Test assertion'); // Should not reach here expect(true).toBe(false); } catch (e: any) { expect(e.message).toContain('Assertion failed: Test assertion'); } }); }); describe('unwrap', () => { it('should return the value for non-null/undefined values', () => { expect(unwrap(5)).toEqual(5); expect(unwrap('test')).toEqual('test'); expect(unwrap(false)).toEqual(false); expect(unwrap(0)).toEqual(0); const obj = {test: true}; expect(unwrap(obj)).toBe(obj); }); it('should throw for null values', () => { expect(() => unwrap(null)).toThrow('Unwrap failed'); }); it('should throw for undefined values', () => { expect(() => unwrap(undefined)).toThrow('Unwrap failed'); }); it('should include custom message in error', () => { expect(() => unwrap(null, 'Custom unwrap message')).toThrow(/Custom unwrap message/); }); it('should include extra info in error message', () => { const extra = {reason: 'test'}; expect(() => unwrap(null, 'Message', extra)).toThrow(JSON.stringify([extra])); }); }); describe('unwrapString', () => { it('should return string values', () => { expect(unwrapString('test')).toEqual('test'); expect(unwrapString('')).toEqual(''); }); it('should throw for non-string values', () => { expect(() => unwrapString(123)).toThrow('String unwrap failed'); expect(() => unwrapString(null)).toThrow('String unwrap failed'); expect(() => unwrapString(undefined)).toThrow('String unwrap failed'); expect(() => unwrapString({})).toThrow('String unwrap failed'); expect(() => unwrapString([])).toThrow('String unwrap failed'); }); it('should include custom message in error', () => { expect(() => unwrapString(123, 'Expected string value')).toThrow(/Expected string value/); }); it('should include extra info in error message', () => { const extra = {value: 123, type: 'number'}; expect(() => unwrapString(123, 'Message', extra)).toThrow(JSON.stringify([extra])); }); }); });