This commit enhances application security against potential attacks by isolating dependencies that access the host system (like file operations) from the renderer process. It narrows the exposed functionality to script execution only, adding an extra security layer. The changes allow secure and scalable API exposure, preparing for future functionalities such as desktop notifications for script errors (#264), improved script execution handling (#296), and creating restore points (#50) in a secure and repeatable way. Changes include: - Inject `CodeRunner` into Vue components via dependency injection. - Move `CodeRunner` to the application layer as an abstraction for better domain-driven design alignment. - Refactor `SystemOperations` and related interfaces, removing the `I` prefix. - Update architecture documentation for clarity. - Update return types in `NodeSystemOperations` to match the Node APIs. - Improve `WindowVariablesProvider` integration tests for better error context. - Centralize type checks with common functions like `isArray` and `isNumber`. - Change `CodeRunner` to use `os` parameter, ensuring correct window variable injection. - Streamline API exposure to the renderer process: - Automatically bind function contexts to prevent loss of original context. - Implement a way to create facades (wrapper/proxy objects) for increased security.
187 lines
5.7 KiB
TypeScript
187 lines
5.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
|
|
|
|
describe('CodeBuilder', () => {
|
|
class CodeBuilderConcrete extends CodeBuilder {
|
|
private commentDelimiter = '//';
|
|
|
|
private newLineTerminator = '\n';
|
|
|
|
public withCommentDelimiter(delimiter: string): CodeBuilderConcrete {
|
|
this.commentDelimiter = delimiter;
|
|
return this;
|
|
}
|
|
|
|
public withNewLineTerminator(terminator: string): CodeBuilderConcrete {
|
|
this.newLineTerminator = terminator;
|
|
return this;
|
|
}
|
|
|
|
protected getCommentDelimiter(): string {
|
|
return this.commentDelimiter;
|
|
}
|
|
|
|
protected writeStandardOut(text: string): string {
|
|
return text;
|
|
}
|
|
|
|
protected getNewLineTerminator(): string {
|
|
return this.newLineTerminator;
|
|
}
|
|
}
|
|
describe('appendLine', () => {
|
|
it('when empty appends empty line', () => {
|
|
// arrange
|
|
const sut = new CodeBuilderConcrete();
|
|
// act
|
|
sut.appendLine().appendLine().appendLine();
|
|
// assert
|
|
expect(sut.toString()).to.equal('\n\n');
|
|
});
|
|
it('when not empty append string in new line', () => {
|
|
// arrange
|
|
const sut = new CodeBuilderConcrete();
|
|
const expected = 'str';
|
|
// act
|
|
sut.appendLine()
|
|
.appendLine(expected);
|
|
// assert
|
|
const result = sut.toString();
|
|
const lines = getLines(result);
|
|
expect(lines[1]).to.equal('str');
|
|
});
|
|
describe('append multi-line string as multiple lines', () => {
|
|
describe('recognizes different line terminators', () => {
|
|
const delimiters = ['\n', '\r\n', '\r'];
|
|
for (const delimiter of delimiters) {
|
|
it(`using "${JSON.stringify(delimiter)}"`, () => {
|
|
// arrange
|
|
const line1 = 'line1';
|
|
const line2 = 'line2';
|
|
const code = `${line1}${delimiter}${line2}`;
|
|
const sut = new CodeBuilderConcrete();
|
|
// act
|
|
sut.appendLine(code);
|
|
// assert
|
|
const result = sut.toString();
|
|
const lines = getLines(result);
|
|
expect(lines).to.have.lengthOf(2);
|
|
expect(lines[0]).to.equal(line1);
|
|
expect(lines[1]).to.equal(line2);
|
|
});
|
|
}
|
|
});
|
|
it('normalizes different line terminators', () => {
|
|
// arrange
|
|
const lineTerminator = 'ðŸ’';
|
|
const lines = ['line1', 'line2', 'line3', 'line4'];
|
|
const code = `${lines[0]}\n${lines[1]}\r\n${lines[2]}\r${lines[3]}`;
|
|
const expected = `${lines[0]}${lineTerminator}${lines[1]}${lineTerminator}${lines[2]}${lineTerminator}${lines[3]}`;
|
|
const sut = new CodeBuilderConcrete()
|
|
.withNewLineTerminator(lineTerminator);
|
|
// act
|
|
const actual = sut
|
|
.appendLine(code)
|
|
.toString();
|
|
// assert
|
|
expect(actual).to.equal(expected);
|
|
});
|
|
});
|
|
});
|
|
it('appendFunction', () => {
|
|
// arrange
|
|
const sut = new CodeBuilderConcrete();
|
|
const functionName = 'expected-function-name';
|
|
const code = 'expected-code';
|
|
// act
|
|
sut.appendFunction(functionName, code);
|
|
// assert
|
|
const result = sut.toString();
|
|
expect(result).to.include(functionName);
|
|
expect(result).to.include(code);
|
|
});
|
|
it('appendTrailingHyphensCommentLine', () => {
|
|
// arrange
|
|
const commentDelimiter = '//';
|
|
const sut = new CodeBuilderConcrete()
|
|
.withCommentDelimiter(commentDelimiter);
|
|
const totalHyphens = 5;
|
|
const expected = `${commentDelimiter} ${'-'.repeat(totalHyphens)}`;
|
|
// act
|
|
sut.appendTrailingHyphensCommentLine(totalHyphens);
|
|
// assert
|
|
const result = sut.toString();
|
|
const lines = getLines(result);
|
|
expect(lines[0]).to.equal(expected);
|
|
});
|
|
it('appendCommentLine', () => {
|
|
// arrange
|
|
const commentDelimiter = '//';
|
|
const sut = new CodeBuilderConcrete()
|
|
.withCommentDelimiter(commentDelimiter);
|
|
const comment = 'comment';
|
|
const expected = `${commentDelimiter} comment`;
|
|
// act
|
|
const result = sut
|
|
.appendCommentLine(comment)
|
|
.toString();
|
|
// assert
|
|
const lines = getLines(result);
|
|
expect(lines[0]).to.equal(expected);
|
|
});
|
|
it('appendCommentLineWithHyphensAround', () => {
|
|
// arrange
|
|
const commentDelimiter = '//';
|
|
const sut = new CodeBuilderConcrete()
|
|
.withCommentDelimiter(commentDelimiter);
|
|
const sectionName = 'section';
|
|
const totalHyphens = sectionName.length + 3 * 2;
|
|
const expected = `${commentDelimiter} ---section---`;
|
|
// act
|
|
const result = sut
|
|
.appendCommentLineWithHyphensAround(sectionName, totalHyphens)
|
|
.toString();
|
|
// assert
|
|
const lines = getLines(result);
|
|
expect(lines[1]).to.equal(expected);
|
|
});
|
|
describe('currentLine', () => {
|
|
it('no lines returns zero', () => {
|
|
// arrange & act
|
|
const sut = new CodeBuilderConcrete();
|
|
// assert
|
|
expect(sut.currentLine).to.equal(0);
|
|
});
|
|
it('single line returns one', () => {
|
|
// arrange
|
|
const sut = new CodeBuilderConcrete();
|
|
// act
|
|
sut.appendLine();
|
|
// assert
|
|
expect(sut.currentLine).to.equal(1);
|
|
});
|
|
it('multiple lines returns as expected', () => {
|
|
// arrange
|
|
const sut = new CodeBuilderConcrete();
|
|
// act
|
|
sut.appendLine('1')
|
|
.appendCommentLine('2')
|
|
.appendLine();
|
|
// assert
|
|
expect(sut.currentLine).to.equal(3);
|
|
});
|
|
it('multiple lines in code', () => {
|
|
// arrange
|
|
const sut = new CodeBuilderConcrete();
|
|
// act
|
|
sut.appendLine('hello\ncode-here\nwith-3-lines');
|
|
// assert
|
|
expect(sut.currentLine).to.equal(3);
|
|
});
|
|
});
|
|
});
|
|
|
|
function getLines(text: string): string[] {
|
|
return text.split(/\r\n|\r|\n/);
|
|
}
|