Skip to content

Commit

Permalink
fix: validate Score files locally when there is no org set
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszjenek committed Jun 13, 2024
1 parent c03f7bd commit 8fcdbf4
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 104 deletions.
2 changes: 1 addition & 1 deletion .vscode-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default defineConfig([
timeout: 20000,
asyncOnly: true,
},
launchArgs: [`--user-data-dir=${userDir}`],
launchArgs: [`--user-data-dir=${userDir}`, '--disable-extensions'],
},
// you can specify additional test configurations, too
]);
1 change: 1 addition & 0 deletions src/controllers/HumanitecSidebarController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export class HumanitecSidebarController {
item.id
);
}
vscode.commands.executeCommand('humanitec.score.validate');
} else if (item instanceof Application) {
await configurationRepository.set(ConfigKey.HUMANITEC_ENV, '');
const orgId = await configurationRepository.get(
Expand Down
52 changes: 45 additions & 7 deletions src/controllers/ValidateScoreFileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
} from 'vscode';
import { isHumanitecExtensionError } from '../errors/IHumanitecExtensionError';
import { ILoggerService } from '../services/LoggerService';
import { IConfigurationRepository } from '../repos/ConfigurationRepository';
import { ConfigKey } from '../domain/ConfigKey';

export class ValidateScoreFileController {
private static instance: ValidateScoreFileController;
Expand All @@ -18,20 +20,21 @@ export class ValidateScoreFileController {

private constructor(
private validationService: IScoreValidationService,
private logger: ILoggerService
private config: IConfigurationRepository
) {
this.diagnosticCollections = new Map();
}

static register(
context: vscode.ExtensionContext,
validationService: IScoreValidationService,
config: IConfigurationRepository,
logger: ILoggerService
) {
if (this.instance === undefined) {
this.instance = new ValidateScoreFileController(
validationService,
logger
config
);
}

Expand All @@ -51,7 +54,11 @@ export class ValidateScoreFileController {
if (this.instance.isScoreFile(textDocument)) {
const diagnosticCollection =
this.instance.getDiagnosticCollections(textDocument.uri.path);
await this.instance.validate(textDocument, diagnosticCollection);
await this.instance.validate(
textDocument,
diagnosticCollection,
context
);
}
} catch (error) {
if (isHumanitecExtensionError(error)) {
Expand Down Expand Up @@ -81,7 +88,11 @@ export class ValidateScoreFileController {
const diagnosticCollection = this.instance.getDiagnosticCollections(
textDocument.uri.path
);
await this.instance.validate(textDocument, diagnosticCollection);
await this.instance.validate(
textDocument,
diagnosticCollection,
context
);
}
} catch (error) {
logger.error(JSON.stringify({ error }));
Expand All @@ -107,7 +118,11 @@ export class ValidateScoreFileController {
const diagnosticCollection = this.instance.getDiagnosticCollections(
event.document.uri.path
);
await this.instance.validate(event.document, diagnosticCollection);
await this.instance.validate(
event.document,
diagnosticCollection,
context
);
}
} catch (error) {
logger.error(JSON.stringify({ error }));
Expand Down Expand Up @@ -152,12 +167,35 @@ export class ValidateScoreFileController {
}
}

private statusBarItem: vscode.StatusBarItem | undefined;

private async validate(
textDocument: TextDocument,
diagnosticCollection: vscode.DiagnosticCollection
diagnosticCollection: vscode.DiagnosticCollection,
context: vscode.ExtensionContext
) {
if (this.statusBarItem == undefined) {
this.statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right
);
this.statusBarItem.text = '$(warning) Only local validation';
this.statusBarItem.tooltip =
'There is no organization set so Humanitec Extension could only validate the Score files locally';
context.subscriptions.push(this.statusBarItem);
}

const isOrganizationSet =
(await this.config.get(ConfigKey.HUMANITEC_ORG)) !== '';

if (!isOrganizationSet) {
this.statusBarItem.show();
} else {
this.statusBarItem.hide();
}

const validationErrors = await this.validationService.validate(
textDocument.uri.path
textDocument.uri.path,
!isOrganizationSet
);

const diagnostics: Diagnostic[] = [];
Expand Down
3 changes: 2 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const loggerChannel = vscode.window.createOutputChannel('Humanitec');
export async function activate(context: vscode.ExtensionContext) {
const logger = new LoggerService(loggerChannel);
const configurationRepository = new ConfigurationRepository();
const secretRepository = new SecretRepository(context.secrets, logger);
const secretRepository = new SecretRepository();
const humctl = new HumctlAdapter(
configurationRepository,
secretRepository,
Expand Down Expand Up @@ -52,6 +52,7 @@ export async function activate(context: vscode.ExtensionContext) {
ValidateScoreFileController.register(
context,
new ScoreValidationService(humctl),
configurationRepository,
logger
);
InitializeScoreFileController.register(
Expand Down
7 changes: 1 addition & 6 deletions src/repos/SecretRepository.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import * as vscode from 'vscode';
import { SecretKey } from '../domain/SecretKey';
import { homedir } from 'os';
import path from 'path';
import { readFileSync, writeFileSync } from 'fs';
import { parse, stringify } from 'yaml';
import { ILoggerService } from '../services/LoggerService';

export interface ISecretRepository {
get(key: SecretKey): Promise<string>;
set(key: SecretKey, value: string): Promise<void>;
}

export class SecretRepository implements ISecretRepository {
constructor(
private secrets: vscode.SecretStorage,
private logger: ILoggerService
) {}
constructor() {}

async set(key: SecretKey, value: string): Promise<void> {
const configPath = path.join(homedir(), '.humctl');
Expand Down
15 changes: 12 additions & 3 deletions src/services/ScoreValidationService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IHumctlAdapter } from '../adapters/humctl/IHumctlAdapter';

export interface IScoreValidationService {
validate(filepath: string): Promise<ValidationError[]>;
validate(filepath: string, onlyLocal: boolean): Promise<ValidationError[]>;
}

export class ValidationError {
Expand All @@ -25,8 +25,17 @@ interface RawValidationError {
export class ScoreValidationService implements IScoreValidationService {
constructor(private humctl: IHumctlAdapter) {}

async validate(filepath: string): Promise<ValidationError[]> {
const result = await this.humctl.execute(['score', 'validate', filepath]);
async validate(
filepath: string,
onlyLocal: boolean
): Promise<ValidationError[]> {
const command = ['score', 'validate'];
if (onlyLocal) {
command.push('--local');
}
command.push(filepath);

const result = await this.humctl.execute(command);

const validationErrors: ValidationError[] = [];
// TODO: Make the handling better
Expand Down
87 changes: 1 addition & 86 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { suite, beforeEach, afterEach, test } from 'mocha';
import path from 'path';
import sinon from 'sinon';
import chai from 'chai';
import sinonChai from 'sinon-chai';
Expand All @@ -15,21 +14,10 @@ import { ConfigurationRepository } from '../../repos/ConfigurationRepository';
import { ConfigKey } from '../../domain/ConfigKey';
import { Environment } from '../../domain/Environment';
import { loggerChannel } from '../../extension';

const wait = (ms: number) =>
new Promise<void>(resolve => setTimeout(() => resolve(), ms));

const readEnv = (name: string): string => {
if (!process.env[name]) {
throw new Error(`${name} not set`);
}
return process.env[name] || '';
};
import { readEnv } from '../utils';

suite('Extension Test Suite', () => {
let workspaceFolder: string;
let humanitecOrg: string;
let humanitecToken: string;
let sandbox: sinon.SinonSandbox;
let showErrorMessage: sinon.SinonSpy;

Expand All @@ -52,12 +40,6 @@ suite('Extension Test Suite', () => {
});

humanitecOrg = readEnv('TEST_HUMANITEC_ORG');
humanitecToken = readEnv('TEST_HUMANITEC_TOKEN');

if (!vscode.workspace.workspaceFolders) {
throw new Error('Workspace folder not found');
}
workspaceFolder = vscode.workspace.workspaceFolders[0].uri.path;

const ext = vscode.extensions.getExtension('humanitec.humanitec');
if (!ext) {
Expand All @@ -70,73 +52,6 @@ suite('Extension Test Suite', () => {
sandbox.restore();
});

test('score.validate - without login', async () => {
const doc = await vscode.workspace.openTextDocument(
path.join(workspaceFolder, './score.yaml')
);

await vscode.window.showTextDocument(doc);
await vscode.commands.executeCommand('humanitec.score.validate');

await waitForExpect(
() => {
expect(showErrorMessage).to.have.been.called;
},
10000,
500
);

expect(showErrorMessage).to.have.been.calledWith(
'There is no enough context to process the request. Required context is: Organization'
);
});

test('score.validate - with login', async () => {
const doc = await vscode.workspace.openTextDocument(
path.join(workspaceFolder, './score.yaml')
);

await vscode.window.showTextDocument(doc);

sandbox.stub(vscode.window, 'showInputBox').resolves(humanitecToken);

await vscode.commands.executeCommand('humanitec.set_token');

await wait(100);

await vscode.commands.executeCommand(
'humanitec.sidebar.organization_structure.set_in_workspace',
new Organization(humanitecOrg, 'test-org')
);

await wait(100);

await vscode.commands.executeCommand('humanitec.score.validate');

let diagnostics: vscode.Diagnostic[] = [];

await waitForExpect(
() => {
diagnostics = vscode.languages.getDiagnostics(doc.uri);
expect(diagnostics).not.to.be.empty;
},
10000,
500
);

const invalidPropertyErrorMessage =
"additionalProperties 'invalid' not allowed";

const invalidProperty = diagnostics.find(
diagnostic => diagnostic.message === invalidPropertyErrorMessage
);

expect(
invalidProperty,
`Expected invalid property error in: ${JSON.stringify(diagnostics, null, 2)}`
).to.be.ok;
});

test('humanitec.sidebar.organization_structure - set organization in workspace', async () => {
const sidebarController = HumanitecSidebarController.getInstance();

Expand Down
Loading

0 comments on commit 8fcdbf4

Please sign in to comment.