Skip to content

Commit

Permalink
feat: support provider branch/tags on verification published
Browse files Browse the repository at this point in the history
  • Loading branch information
YOU54F committed Oct 4, 2024
1 parent d0a2d90 commit bcb8469
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 38 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,10 @@ Options:
-A, --additionalPropertiesInResponse [boolean] allow additional properties in response bodies, default false
-R, --requiredPropertiesInResponse [boolean] allows required properties in response bodies, default false
--publish Allows publication of verification result to pact broker, default false
--providerApplicationVersion [string] Version of provider, used when publishing result to broker
--providerApplicationVersion [string] Version of provider, used when publishing result to broker, required if --publish is set
--buildUrl [string] Url to build/pipeline, used when publishing result to broker
--providerBranch [string] Branch of provider, used when publishing result to broker
--providerTags [string] Tags of provider, used when publishing result to broker, comma separated
-h, --help display help for command

```
Expand Down
4 changes: 4 additions & 0 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ program
.option('-R, --requiredPropertiesInResponse [boolean]', 'allows required properties in response bodies, default false')
.option('--publish', 'Allows publication of verification result to pact broker, default false')
.option('--providerApplicationVersion [string]', 'Version of provider, used when publishing result to broker')
.option('--providerBranch [string]', 'Branch of provider, used when publishing result to broker')
.option('--providerTags [string]', 'Tags of provider, used when publishing result to broker, comma seperated')
.option('--buildUrl [string]', 'Url to build/pipeline, used when publishing result to broker')
.description(
`Confirms the swagger spec and mock are compatible with each other.
Expand Down Expand Up @@ -91,6 +93,8 @@ If the pact broker has basic auth enabled, pass a --user option with username an
additionalPropertiesInResponse: options.additionalPropertiesInResponse,
requiredPropertiesInResponse: options.requiredPropertiesInResponse,
providerApplicationVersion: options.providerApplicationVersion,
providerBranch: options.providerBranch,
providerTags: options.providerTags,
buildUrl: options.buildUrl,
publish: options.publish
});
Expand Down
9 changes: 9 additions & 0 deletions lib/swagger-mock-validator/clients/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ export class HttpClient {
validateStatus: (status) => status >= 200 && status <= 299
});
}
public async put(url: string, body: any, auth?: string): Promise<void> {

Check warning on line 25 in lib/swagger-mock-validator/clients/http-client.ts

View workflow job for this annotation

GitHub Actions / build-and-test-osx (20.x)

Unexpected any. Specify a different type

Check warning on line 25 in lib/swagger-mock-validator/clients/http-client.ts

View workflow job for this annotation

GitHub Actions / build-and-test-osx (18.x)

Unexpected any. Specify a different type

Check warning on line 25 in lib/swagger-mock-validator/clients/http-client.ts

View workflow job for this annotation

GitHub Actions / build-and-test-osx (22.x)

Unexpected any. Specify a different type

Check warning on line 25 in lib/swagger-mock-validator/clients/http-client.ts

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu (20.x)

Unexpected any. Specify a different type

Check warning on line 25 in lib/swagger-mock-validator/clients/http-client.ts

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu (18.x)

Unexpected any. Specify a different type

Check warning on line 25 in lib/swagger-mock-validator/clients/http-client.ts

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu (22.x)

Unexpected any. Specify a different type
await axios.put(url, body, {
headers: {
...(auth ? {authorization: 'Basic ' + Buffer.from(auth).toString('base64')} : {})
},
timeout: 5000,
validateStatus: (status) => status >= 200 && status <= 299
});
}
}
9 changes: 9 additions & 0 deletions lib/swagger-mock-validator/clients/pact-broker-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,13 @@ export class PactBrokerClient {
);
}
}
public async put(url: string, body: object): Promise<void> {
try {
await this.httpClient.put(url, body, this.auth);
} catch (error) {
throw new SwaggerMockValidatorErrorImpl(
'SWAGGER_MOCK_VALIDATOR_READ_ERROR', `Unable to put "${url}"`, error
);
}
}
}
2 changes: 2 additions & 0 deletions lib/swagger-mock-validator/mock-parser/parsed-mock.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export interface ParsedMock {
pathOrUrl: string;
provider: string;
verificationUrl?: string;
verificationBranchVersionUrl?: string;
verificationTagVersionUrl?: string;
}

export interface ParsedMockInteraction extends ParsedMockValue<any> {
Expand Down
135 changes: 100 additions & 35 deletions lib/swagger-mock-validator/pact-broker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export class PactBroker {
return _.get(pactBrokerRootResponse, template);
}

public constructor(private readonly pactBrokerClient: PactBrokerClient) {
}
public constructor(private readonly pactBrokerClient: PactBrokerClient) {}

public async loadPacts(options: PactBrokerUserOptions): Promise<SerializedMock[]> {
const providerPactsUrl = await this.getUrlForProviderPacts(options);
Expand All @@ -29,17 +28,79 @@ export class PactBroker {
}

public async publishVerificationResult(
{providerApplicationVersion, buildUrl}: ParsedSwaggerMockValidatorOptions,
{verificationUrl}: ParsedMock,
{success}: ValidationOutcome
{
providerApplicationVersion,
buildUrl,
providerBranch,
providerTags,
mockPathOrUrl,
mockSource,
providerName,
}: ParsedSwaggerMockValidatorOptions,
{ verificationUrl }: ParsedMock,
// {verificationUrl,verificationBranchVersionUrl,verificationTagVersionUrl}: ParsedMock,
{ success }: ValidationOutcome,
): Promise<void> {
// if (mockSource !== 'pactBroker') {
// throw new SwaggerMockValidatorErrorImpl(
// 'SWAGGER_MOCK_VALIDATOR_READ_ERROR',
// `verification results can only be published for pacts sourced from a Pact Broker`,
// );
// }
if (!providerApplicationVersion) {
throw new SwaggerMockValidatorErrorImpl(
'SWAGGER_MOCK_VALIDATOR_READ_ERROR',
`providerApplicationVersion is required to publish verification results`,
);
}
if (!verificationUrl) {
throw new SwaggerMockValidatorErrorImpl(
'SWAGGER_MOCK_VALIDATOR_READ_ERROR',
`No verification publication url available in pact`
`No verification publication url available in pact`,
);
}

let branchVersionUrl;
let versionTagUrl;
if ((providerBranch || providerTags) && providerName) {
const pactBrokerRootResponse = await this.pactBrokerClient.loadAsObject<PactBrokerRootResponse>(
mockPathOrUrl
);
const pactBrokerPacticipantUrl = pactBrokerRootResponse._links['pb:pacticipant'].href;
const pactBrokerPacticipantResponse = await this.pactBrokerClient.loadAsObject<PactBrokerPacticipantResponse>(
this.getSpecificUrlFromTemplate(pactBrokerPacticipantUrl,{pacticipant: providerName}),
);
branchVersionUrl = pactBrokerPacticipantResponse._links['pb:branch-version'].href;
versionTagUrl = pactBrokerPacticipantResponse._links['pb:version-tag'].href;
}

if (providerBranch && providerName && branchVersionUrl) {
await this.pactBrokerClient.put(
this.getSpecificUrlFromTemplate(branchVersionUrl, {
provider: providerName,
branch: providerBranch,
version: providerApplicationVersion,
}),
{
version: providerApplicationVersion,
branch: providerBranch,
},
);
}

if (providerTags && providerName && versionTagUrl) {
const tags = providerTags.split(',');
for (const tag of tags) {
await this.pactBrokerClient.put(
this.getSpecificUrlFromTemplate(versionTagUrl, { tag: tag, version: providerApplicationVersion }),
{
version: providerApplicationVersion,
tag: tag,
},
);
}
}

return this.pactBrokerClient.post(verificationUrl, {
success,
providerApplicationVersion,
Expand All @@ -48,60 +109,61 @@ export class PactBroker {
}

private async getUrlForProviderPacts(options: PactBrokerUserOptions): Promise<string> {
const pactBrokerRootResponse =
await this.pactBrokerClient.loadAsObject<PactBrokerRootResponse>(options.pactBrokerUrl);
const pactBrokerRootResponse = await this.pactBrokerClient.loadAsObject<PactBrokerRootResponse>(
options.pactBrokerUrl,
);

return options.tag
? this.getUrlForProviderPactsByTag(pactBrokerRootResponse, {
pactBrokerUrl: options.pactBrokerUrl,
providerName: options.providerName,
tag: options.tag
}) : this.getUrlForAllProviderPacts(pactBrokerRootResponse, options);
pactBrokerUrl: options.pactBrokerUrl,
providerName: options.providerName,
tag: options.tag,
})
: this.getUrlForAllProviderPacts(pactBrokerRootResponse, options);
}

private getUrlForProviderPactsByTag(pactBrokerRootResponse: PactBrokerRootResponse,
options: PactBrokerUserOptionsWithTag): string {
private getUrlForProviderPactsByTag(
pactBrokerRootResponse: PactBrokerRootResponse,
options: PactBrokerUserOptionsWithTag,
): string {
const providerTemplateUrl = PactBroker.getProviderTemplateUrl(
pactBrokerRootResponse,
'_links.pb:latest-provider-pacts-with-tag.href'
'_links.pb:latest-provider-pacts-with-tag.href',
);

if (!providerTemplateUrl) {
throw new SwaggerMockValidatorErrorImpl(
'SWAGGER_MOCK_VALIDATOR_READ_ERROR',
`Unable to read "${options.pactBrokerUrl}": No latest pact file url found for tag`
`Unable to read "${options.pactBrokerUrl}": No latest pact file url found for tag`,
);
}

return this.getSpecificUrlFromTemplate(
providerTemplateUrl, {provider: options.providerName, tag: options.tag}
);
return this.getSpecificUrlFromTemplate(providerTemplateUrl, {
provider: options.providerName,
tag: options.tag,
});
}

private getUrlForAllProviderPacts(
pactBrokerRootResponse: PactBrokerRootResponse,
options: PactBrokerUserOptions
options: PactBrokerUserOptions,
): string {
const providerTemplateUrl = PactBroker.getProviderTemplateUrl(
pactBrokerRootResponse,
'_links.pb:latest-provider-pacts.href'
'_links.pb:latest-provider-pacts.href',
);

if (!providerTemplateUrl) {
throw new SwaggerMockValidatorErrorImpl(
'SWAGGER_MOCK_VALIDATOR_READ_ERROR',
`Unable to read "${options.pactBrokerUrl}": No latest pact file url found`
`Unable to read "${options.pactBrokerUrl}": No latest pact file url found`,
);
}

return this.getSpecificUrlFromTemplate(
providerTemplateUrl, {provider: options.providerName}
);
return this.getSpecificUrlFromTemplate(providerTemplateUrl, { provider: options.providerName });
}

private getSpecificUrlFromTemplate(
providerTemplateUrl: string, parameters: { [key: string]: string }
): string {
private getSpecificUrlFromTemplate(providerTemplateUrl: string, parameters: { [key: string]: string }): string {
let specificUrl = providerTemplateUrl;
Object.keys(parameters).forEach((key) => {
const encodedParameterValue = encodeURIComponent(parameters[key]);
Expand All @@ -112,18 +174,21 @@ export class PactBroker {
}

private async getPactUrls(providerPactsUrl: string): Promise<string[]> {
const providerUrlResponse =
await this.pactBrokerClient.loadAsObject<PactBrokerProviderPacts>(providerPactsUrl);
const providerUrlResponse = await this.pactBrokerClient.loadAsObject<PactBrokerProviderPacts>(providerPactsUrl);
const providerPactEntries: PactBrokerProviderPactsLinksPact[] = _.get(providerUrlResponse, '_links.pacts', []);

return _.map(providerPactEntries, (providerPact) => providerPact.href);
}

private async getPacts(pactUrls: string[]): Promise<SerializedMock[]> {
return Promise.all(pactUrls.map(async (mockPathOrUrl): Promise<SerializedMock> => ({
content: await this.pactBrokerClient.loadAsString(mockPathOrUrl),
format: 'auto-detect',
pathOrUrl: mockPathOrUrl
})));
return Promise.all(
pactUrls.map(
async (mockPathOrUrl): Promise<SerializedMock> => ({
content: await this.pactBrokerClient.loadAsString(mockPathOrUrl),
format: 'auto-detect',
pathOrUrl: mockPathOrUrl,
}),
),
);
}
}
19 changes: 19 additions & 0 deletions lib/swagger-mock-validator/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,29 @@ import {SwaggerMockValidatorOptionsMockType, SwaggerMockValidatorOptionsSpecType
export interface PactBrokerRootResponse {
_links: PactBrokerLinks;
}
export interface PactBrokerPacticipantResponse {
_links: PactBrokerPacticipantResponseLinks;
}

interface PactBrokerLinks {
'pb:pacticipant': PactBrokerLinksPacticipant;
'pb:latest-provider-pacts': PactBrokerLinksLatestProviderPacts;
'pb:latest-provider-pacts-with-tag': PactBrokerLinksLatestProviderPacts;
}
interface PactBrokerPacticipantResponseLinks {
'pb:version-tag': PactBrokerLinksPacticipantVersionTag;
'pb:branch-version': PactBrokerLinksPacticipantBranchVersion;
}

interface PactBrokerLinksPacticipant {
href: string;
}
interface PactBrokerLinksPacticipantVersionTag {
href: string;
}
interface PactBrokerLinksPacticipantBranchVersion {
href: string;
}
interface PactBrokerLinksLatestProviderPacts {
href: string;
}
Expand Down Expand Up @@ -80,6 +97,8 @@ interface ParsedSwaggerMockValidatorOptions {
additionalPropertiesInResponse: boolean;
requiredPropertiesInResponse: boolean;
providerApplicationVersion?: string;
providerBranch?: string;
providerTags?: string;
buildUrl?: string;
publish: boolean;
}
Expand Down
10 changes: 8 additions & 2 deletions test/e2e/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,12 @@ describe('swagger-mock-validator/cli', () => {
let mockPactBroker: jasmine.SpyObj<{
get: (requestHeaders: object, requestUrl: string) => void,
post: (body: object, requestUrl: string) => void
put: (body: object, requestUrl: string) => void
}>;
let mockAnalytics: jasmine.SpyObj<{ post: (body: object) => void }>;

beforeAll((done) => {
mockPactBroker = jasmine.createSpyObj('mockPactBroker', ['get', 'post']);
mockPactBroker = jasmine.createSpyObj('mockPactBroker', ['get', 'post', 'put']);
mockAnalytics = jasmine.createSpyObj('mockAnalytics', ['post']);

const expressApp = express();
Expand All @@ -93,10 +94,14 @@ describe('swagger-mock-validator/cli', () => {
mockAnalytics.post(request.body);
response.status(201).end();
});
expressApp.post('/*', bodyParser.json(), (request, response) => {
expressApp.post(/(.*)/, bodyParser.json(), (request, response) => {
mockPactBroker.post(request.body, request.url);
response.status(201).end();
});
expressApp.post(/(.*)/, bodyParser.json(), (request, response) => {
mockPactBroker.put(request.body, request.url);
response.status(201).end();
});
expressApp.use(express.static('.'));

mockServer = expressApp.listen(serverPort, done);
Expand All @@ -106,6 +111,7 @@ describe('swagger-mock-validator/cli', () => {
mockAnalytics.post.calls.reset();
mockPactBroker.get.calls.reset();
mockPactBroker.post.calls.reset();
mockPactBroker.post.calls.reset();
});

afterAll((done) => mockServer.close(done));
Expand Down

0 comments on commit bcb8469

Please sign in to comment.