Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@autorest/testmodeler",
"comment": "add option --explicit-types to allow customize tagged types in exported code model.",
"type": "patch"
}
],
"packageName": "@autorest/testmodeler"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@autorest/testmodeler",
"comment": "fix bugs in loading remote api scenario",
"type": "patch"
}
],
"packageName": "@autorest/testmodeler"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@autorest/testmodeler",
"comment": "fix bug in outputVariableModel generation when there is no response in scenario step",
"type": "patch"
}
],
"packageName": "@autorest/testmodeler"
}
12 changes: 12 additions & 0 deletions tools/sdk-testgen/packages/autorest.testmodeler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ isDataAction: false
count: 64
```

### --testmodeler.explicit-types

A list for types need to explicitly tagged when export-explicit-type is true. The default explicitTypes are ['bool', 'int', 'float', 'timestamp']. Test generators can change it like below:

```
testmodeler:
explicit-types:
- bool
- int
- float
```

## Autorest Pipeline Configurations

```yaml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum Config {
addArmTemplatePayloadString = 'testmodeler.add-armtemplate-payload-string',
apiScenarioLoaderOption = 'testmodeler.api-scenario-loader-option',
exportExplicitType = 'testmodeler.export-explicit-type',
explicitTypes = 'testmodeler.explicit-types',
}

export const configDefaults = {
Expand All @@ -26,6 +27,7 @@ export const configDefaults = {
[Config.useExampleModel]: true,
[Config.addArmTemplatePayloadString]: false,
[Config.exportExplicitType]: false,
[Config.explicitTypes]: ['bool', 'int', 'float', 'timestamp'],
};

export enum TestScenarioVariableNames {
Expand Down
37 changes: 21 additions & 16 deletions tools/sdk-testgen/packages/autorest.testmodeler/src/core/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ export enum OutputVariableModelType {
object = 'object',
}

function findResponseSchema(operation: Operation, statusCode: string): SchemaResponse {
for (const response of operation.responses || []) {
if ((response.protocol.http?.statusCodes || []).indexOf(statusCode) >= 0) {
return response as SchemaResponse;
}
}
return undefined;
}

export class TestCodeModeler {
public static instance: TestCodeModeler;
public testConfig: TestConfig;
Expand Down Expand Up @@ -338,18 +347,9 @@ export class TestCodeModeler {
}
}

function findResponseSchema(statusCode: string): SchemaResponse {
for (const response of operation.responses || []) {
if ((response.protocol.http?.statusCodes || []).indexOf(statusCode) >= 0) {
return response as SchemaResponse;
}
}
return undefined;
}

for (const [statusCode, response] of Object.entries(exampleExtension.responses)) {
const exampleExtensionResponse = response;
const schemaResponse = findResponseSchema(statusCode);
const schemaResponse = findResponseSchema(operation, statusCode);
if (schemaResponse) {
exampleModel.responses[statusCode] = ExampleResponse.createInstance(session, exampleExtensionResponse, schemaResponse.schema, schemaResponse.language);
}
Expand Down Expand Up @@ -486,7 +486,7 @@ export class TestCodeModeler {
// JsonPointer use '/' to seperate the token and only can point to one value. Token is a number or a string.
const valueParts = variableConfig.fromResponse.split('/');
// The root schema is from the http body. We only get value from the '200' response for now.
let currentSchema = step.exampleModel.responses['200'].body.schema;
let currentSchema = findResponseSchema(operation, '200')?.schema;
step.outputVariablesModel[variableName] = [];
for (let i = 1; i < valueParts.length; i++) {
const valuePart = valueParts[i];
Expand Down Expand Up @@ -583,14 +583,17 @@ export class TestCodeModeler {

public async loadTestResources(session: Session<TestCodeModel>) {
try {
const fileRoot = this.testConfig.getSwaggerFolder();
let fileRoot = this.testConfig.getSwaggerFolder() || '';
if (fileRoot.endsWith('/') || fileRoot.endsWith('\\')) {
fileRoot = fileRoot.substring(0, fileRoot.length - 1);
}
if (Array.isArray(this.testConfig.config[Config.testResources])) {
await this.loadTestResourcesFromConfig(session, fileRoot);
} else {
await this.loadAvailableTestResources(session, fileRoot);
}
} catch (error) {
session.warning('Exception occured when load test resource scenario: ${error.stack}', ['Test Modeler']);
session.warning(`Exception occured when load test resource scenario: ${error.stack}`, ['Test Modeler']);
}
}

Expand All @@ -606,14 +609,16 @@ export class TestCodeModeler {
}

public async loadTestResourcesFromConfig(session: Session<TestCodeModel>, fileRoot: string) {
const codemodelRestCallOnly = this.testConfig.getValue(Config.scenarioCodeModelRestCallOnly);
for (const testResource of this.testConfig.getValue(Config.testResources)) {
const testFile = typeof testResource === 'string' ? testResource : testResource[Config.test];
try {
const loader = ApiScenarioLoader.create(this.createApiScenarioLoaderOption(fileRoot));
const testDef = (await loader.load(testResource[Config.test])) as TestDefinitionModel;
this.initiateTestDefinition(session, testDef);
const testDef = (await loader.load(testFile)) as TestDefinitionModel;
this.initiateTestDefinition(session, testDef, codemodelRestCallOnly);
this.codeModel.testModel.scenarioTests.push(testDef);
} catch (error) {
session.warning(`Exception occured when load testdef ${testResource[Config.test]}: ${error}`, ['Test Modeler']);
session.warning(`Exception occured when load testdef ${testFile}: ${error}`, ['Test Modeler']);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export async function processRequest(host: AutorestExtensionHost): Promise<void>
codeModel.genMockTests(session);
await codeModel.loadTestResources(session);

await Helper.outputToModelerfour(host, session, config.getValue(Config.exportExplicitType));
await Helper.outputToModelerfour(host, session, config.getValue(Config.exportExplicitType), config.getValue(Config.explicitTypes));
if (config.getValue(Config.exportCodemodel)) {
Helper.addCodeModelDump(session, 'test-modeler.yaml', false);
if (config.getValue(Config.exportExplicitType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@ import { comment, serialize } from '@azure-tools/codegen';

export class Helper {
static dumpBuf: Record<string, any> = {};
public static async outputToModelerfour(host: AutorestExtensionHost, session: Session<CodeModel>, exportExplicitTypes: boolean): Promise<void> {
public static async outputToModelerfour(
host: AutorestExtensionHost,
session: Session<CodeModel>,
exportExplicitTypes: boolean,
explicitTypes: string[] = undefined,
): Promise<void> {
// write the final result first which is hardcoded in the Session class to use to build the model..
// overwrite the modelerfour which should be fine considering our change is backward compatible
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const modelerfourOptions = await session.getValue('modelerfour', {});
if (modelerfourOptions['emit-yaml-tags'] !== false) {
if (exportExplicitTypes) {
codeModelSchema.explicit = (codeModelSchema.explicit || []).concat(codeModelSchema.implicit);
codeModelSchema.implicit = [];
codeModelSchema.compiledExplicit = (codeModelSchema.compiledExplicit || []).concat(codeModelSchema.compiledImplicit);
codeModelSchema.compiledImplicit = [];
codeModelSchema.explicit = codeModelSchema.explicit.concat(codeModelSchema.implicit.filter((t) => Helper.isExplicitTypes(t.tag, explicitTypes)));
codeModelSchema.implicit = codeModelSchema.implicit.filter((t) => !Helper.isExplicitTypes(t.tag, explicitTypes));
codeModelSchema.compiledExplicit = codeModelSchema.compiledExplicit.concat(
codeModelSchema.compiledImplicit.filter((t) => Helper.isExplicitTypes(t.tag, explicitTypes)),
);
codeModelSchema.compiledImplicit = codeModelSchema.compiledImplicit.filter((t) => !Helper.isExplicitTypes(t.tag, explicitTypes));
}
host.writeFile({
filename: 'code-model-v4.yaml',
Expand All @@ -35,6 +42,10 @@ export class Helper {
}
}

private static isExplicitTypes(tag: string, explicitTypes: string[] = undefined): boolean {
return tag && (explicitTypes || []).some((t) => tag.endsWith(t));
}

public static addCodeModelDump(session: Session<CodeModel>, fileName: string, withTags: boolean, debugOnly = true) {
this.dumpBuf[(debugOnly ? '__debug/' : '') + fileName] = withTags ? serialize(session.model, { schema: codeModelSchema }) : serialize(session.model);
}
Expand Down
Loading