Skip to content

Commit

Permalink
Merge 8b2711c into 633bc96
Browse files Browse the repository at this point in the history
  • Loading branch information
mdlavin committed Sep 16, 2019
2 parents 633bc96 + 8b2711c commit 774fd5f
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 3 deletions.
64 changes: 64 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,38 @@ declare interface App {
* include a git tag or a commit hash, a branch or a workfow ID.
*/
triggerBuild(buildParams?: BuildOptions): Promise<Build>;

/**
* @see https://api-docs.bitrise.io/#/builds/build-list
*
* @param listParams Parameters for the builds to list.
*/
listBuilds(listParams?: ListBuildOptions): Promise<BuildList>;
}

declare interface ListBuildOptions {
readonly sort_by?: string;
readonly branch?: string;
readonly workflow?: string;
readonly commit_message?: string;
readonly trigger_event_type?: string;
readonly pull_request_id?: number;
readonly build_number?: number;
readonly after?: number;
readonly before?: number;
readonly status?: number;
readonly next?: string;
readonly limit?: number;
}

declare interface BuildList {
readonly builds: Build[];
readonly paging: PageInfo;
}

declare interface PageInfo {
readonly next?: string;
readonly total_item_count: number;
}

declare interface AbortOptions {
Expand Down Expand Up @@ -68,11 +100,43 @@ declare interface Build {

describe(): Promise<BuildDescription>;

/**
* @see https://api-docs.bitrise.io/#/build-artifact/artifact-list
*
* @param listParams Parameters for the artifacts to list.
*/
listArtifacts(listParams?: ListArtifactsOptions): Promise<ArtifactList>;

follow(options?: FollowOptions): Promise<void>;

isFinished(): Promise<Boolean>;
}

declare interface ArtifactDescription {
readonly artifact_meta: object
readonly artifact_type: string
readonly expiring_download_url: string
readonly file_size_bytes: number
readonly is_public_page_enabled: boolean
readonly public_install_page_url: string
readonly slug: string
readonly title: string
}

declare interface Artifact {
describe(): Promise<ArtifactDescription>;
}

declare interface ListArtifactsOptions {
readonly next?: string;
readonly limit?: number;
}

declare interface ArtifactList {
readonly artifacts: Artifact[];
readonly paging: PageInfo;
}

declare interface CommitPathsFilter {
readonly added?: string[];
readonly modified?: string[];
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"dependencies": {
"axios": "^0.18.0",
"axios-retry": "^3.1.1",
"lodash": "^4.17.10"
"lodash": "^4.17.10",
"query-string": "^6.8.3"
},
"devDependencies": {
"@lifeomic/eslint-plugin-node": "^2.0.1",
Expand Down
18 changes: 18 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const isNil = require('lodash/isNil');
const negate = require('lodash/negate');
const pickBy = require('lodash/pickBy');
const project = require('../package.json');
const queryString = require('query-string');

const buildEnvironment = (environment) => {
if (!environment) {
Expand Down Expand Up @@ -65,8 +66,25 @@ const triggerBuild = async ({ client, slug }, options = {}) => {
return build({ appSlug: slug, client, buildSlug: response.data.build_slug });
};

const listBuilds = async ({ client, slug }, options = {}) => {
const query = queryString.stringify(options);
const queryPart = query ? `?${query}` : '';

const response = await client.get(`/apps/${slug}/builds${queryPart}`);

const builds = response.data.data.map((buildInfo) => {
return build({ appSlug: slug, client, buildSlug: buildInfo.slug });
});

return {
builds,
paging: response.data.paging
};
};

module.exports = ({ client, slug }) => {
const app = { slug };
app.triggerBuild = triggerBuild.bind(app, { client, slug });
app.listBuilds = listBuilds.bind(app, { client, slug });
return app;
};
13 changes: 13 additions & 0 deletions src/artifact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const describeArtifact = async ({ appSlug, buildSlug, artifactSlug, client }) => {
const response = await client.get(`/apps/${appSlug}/builds/${buildSlug}/artifacts/${artifactSlug}`);
return response.data.data;
};

module.exports = ({ appSlug, buildSlug, artifactSlug, client }) => {
const artifact = { appSlug, buildSlug, artifactSlug };
const state = { appSlug, buildSlug, artifactSlug, client };

artifact.describe = describeArtifact.bind(artifact, state);

return artifact;
};
19 changes: 19 additions & 0 deletions src/build.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const isNil = require('lodash/isNil');
const negate = require('lodash/negate');
const pickBy = require('lodash/pickBy');
const queryString = require('query-string');
const artifact = require('./artifact');

const abortBuild = async ({ appSlug, buildSlug, client }, options = {}) => {
if (options.reason) {
Expand Down Expand Up @@ -69,6 +71,22 @@ const followBuild = async ({ appSlug, buildSlug, client }, options = {}) => {
}
};

const listArtifacts = async ({ appSlug, buildSlug, client }, options = {}) => {
const query = queryString.stringify(options);
const queryPart = query ? `?${query}` : '';

const response = await client.get(`/apps/${appSlug}/builds/${buildSlug}/artifacts${queryPart}`);
const artifacts = response.data.data.map((artifactDescription) => {
const artifactSlug = artifactDescription.slug;
return artifact({ appSlug, buildSlug, client, artifactSlug });
});

return {
artifacts,
paging: response.data.paging
};
};

const isFinished = async ({ appSlug, buildSlug, client }) => {
const attributes = await describeBuild({ appSlug, buildSlug, client });
return !!attributes.finished_at;
Expand All @@ -82,6 +100,7 @@ module.exports = ({ appSlug, buildSlug, client }) => {

build.abort = abortBuild.bind(build, state);
build.describe = describeBuild.bind(build, state);
build.listArtifacts = listArtifacts.bind(build, state);
build.follow = followBuild.bind(build, state);
build.isFinished = isFinished.bind(build, state);

Expand Down
27 changes: 26 additions & 1 deletion test/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const sinon = require('sinon');
const test = require('ava');
const uuid = require('uuid/v4');

const { stubTriggerBuild } = require('./stubs');
const { stubTriggerBuild, stubListBuilds } = require('./stubs');

test.beforeEach((test) => {
const client = axios.create();
Expand Down Expand Up @@ -280,3 +280,28 @@ test('environment variables can be supplied to a build', async (test) => {
})
);
});

test('an app can list builds', async (test) => {
const { app, client, slug } = test.context;
const stub = stubListBuilds({ appSlug: slug, axios: client });

const buildList = await app.listBuilds();
test.is(buildList.builds.length, 2);
test.is(buildList.builds[0].appSlug, slug);
test.is(buildList.builds[0].buildSlug, stub.builds[0].build_slug);
test.is(buildList.builds[1].appSlug, slug);
test.is(buildList.builds[1].buildSlug, stub.builds[1].build_slug);
});

test('an app can list a second page of builds', async (test) => {
const { app, client, slug } = test.context;
const next = uuid();
const stub = stubListBuilds({ appSlug: slug, axios: client, next });

const buildList = await app.listBuilds({ next });
test.is(buildList.builds.length, 2);
test.is(buildList.builds[0].appSlug, slug);
test.is(buildList.builds[0].buildSlug, stub.builds[0].build_slug);
test.is(buildList.builds[1].appSlug, slug);
test.is(buildList.builds[1].buildSlug, stub.builds[1].build_slug);
});
34 changes: 34 additions & 0 deletions test/artifact.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const axios = require('axios');
const artifact = require('../src/artifact');
const test = require('ava');
const uuid = require('uuid/v4');

const { stubGetArtifact } = require('./stubs');

test.beforeEach((test) => {
const appSlug = uuid();
const buildSlug = uuid();
const artifactSlug = uuid();
const client = axios.create();

test.context.appSlug = appSlug;
test.context.artifactSlug = artifactSlug;
test.context.artifact = artifact({ appSlug, buildSlug, artifactSlug, client });
test.context.buildSlug = buildSlug;
test.context.client = client;
});

test('has app, build, and artifact slugs', (test) => {
const { appSlug, artifact, buildSlug, artifactSlug } = test.context;
test.is(artifact.appSlug, appSlug);
test.is(artifact.buildSlug, buildSlug);
test.is(artifact.artifactSlug, artifactSlug);
});

test('describing an artifact returns the artifact attributes', async (test) => {
const { appSlug, artifact, buildSlug, artifactSlug, client } = test.context;
const stub = stubGetArtifact({ axios: client, appSlug, buildSlug, artifactSlug });

const description = await artifact.describe();
test.deepEqual(description, stub.artifact);
});
31 changes: 30 additions & 1 deletion test/build.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const test = require('ava');
const uuid = require('uuid/v4');

const { DateTime } = require('luxon');
const { stubAbortBuild, stubArchivedBuildLog, stubBuildLogStream, stubGetBuild } = require('./stubs');
const { stubAbortBuild, stubArchivedBuildLog, stubBuildLogStream, stubGetBuild, stubListArtifacts } = require('./stubs');

test.beforeEach((test) => {
const appSlug = uuid();
Expand Down Expand Up @@ -238,3 +238,32 @@ test.serial('a heartbeat can be emitted when a followed build has no new output'
write.restore();
}
});

test('an build can list artifacts', async (test) => {
const { build, client, appSlug, buildSlug } = test.context;
const stub = stubListArtifacts({ appSlug, buildSlug, axios: client });

const artifactList = await build.listArtifacts();
test.is(artifactList.artifacts.length, 2);
test.is(artifactList.artifacts[0].appSlug, appSlug);
test.is(artifactList.artifacts[0].buildSlug, buildSlug);
test.is(artifactList.artifacts[0].artifactSlug, stub.artifacts[0].slug);
test.is(artifactList.artifacts[1].appSlug, appSlug);
test.is(artifactList.artifacts[1].buildSlug, buildSlug);
test.is(artifactList.artifacts[1].artifactSlug, stub.artifacts[1].slug);
});

test('a build can list a second page of artifacts', async (test) => {
const { build, client, appSlug, buildSlug } = test.context;
const next = uuid();
const stub = stubListArtifacts({ appSlug, buildSlug, axios: client, next });

const artifactList = await build.listArtifacts({ next });
test.is(artifactList.artifacts.length, 2);
test.is(artifactList.artifacts[0].appSlug, appSlug);
test.is(artifactList.artifacts[0].buildSlug, buildSlug);
test.is(artifactList.artifacts[0].artifactSlug, stub.artifacts[0].slug);
test.is(artifactList.artifacts[1].appSlug, appSlug);
test.is(artifactList.artifacts[1].buildSlug, buildSlug);
test.is(artifactList.artifacts[1].artifactSlug, stub.artifacts[1].slug);
});
85 changes: 85 additions & 0 deletions test/stubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ const generateBuild = (options = {}) => ({
status: 0
});

const generateArtifact = (options = {}) => ({
artifact_meta: {},
artifact_type: 'android-apk',
expiring_download_url: `https://example.com/downloads/${uuid()}`,
file_size_bytes: 100,
is_public_page_enabled: true,
public_install_page_url: `https://example.com/install/${uuid()}`,
slug: options.artifactSlug,
title: `Artifact ${uuid()}`
});

const unmatchedRequest = async (...args) => {
throw new Error(
`Failed to match request with arguments: ${JSON.stringify(args, null, 2)}`
Expand Down Expand Up @@ -98,6 +109,20 @@ exports.stubGetBuild = ({ appSlug, axios, buildSlug }) => {
return stub;
};

exports.stubGetArtifact = ({ appSlug, axios, buildSlug, artifactSlug }) => {
const artifact = generateArtifact({ appSlug, buildSlug, artifactSlug });

const stub = getStub(axios, 'get')
.withArgs(`/apps/${appSlug}/builds/${buildSlug}/artifacts/${artifactSlug}`)
.resolves({
data: { data: artifact },
status: 200
});

stub.artifact = artifact;
return stub;
};

exports.stubTriggerBuild = ({ appSlug, axios, body }) => {
const build = generateBuild();

Expand All @@ -114,3 +139,63 @@ exports.stubTriggerBuild = ({ appSlug, axios, body }) => {
stub.build = build;
return stub;
};

exports.stubListBuilds = ({ appSlug, axios, next }) => {
const build1 = generateBuild();
const build2 = generateBuild();

const urlParts = [`/apps/${appSlug}/builds`];
if (next) {
urlParts.push(`?next=${next}`);
}
const url = urlParts.join('');

const stub = getStub(axios, 'get')
.withArgs(url)
.resolves({
data: {
data: [
{ slug: build1.build_slug },
{ slug: build2.build_slug }
],
paging: {
page_item_limit: 2,
total_item_count: 2
}
},
status: 200
});

stub.builds = [build1, build2];
return stub;
};

exports.stubListArtifacts = ({ appSlug, buildSlug, axios, next }) => {
const artifact1 = generateArtifact();
const artifact2 = generateArtifact();

const urlParts = [`/apps/${appSlug}/builds/${buildSlug}/artifacts`];
if (next) {
urlParts.push(`?next=${next}`);
}
const url = urlParts.join('');

const stub = getStub(axios, 'get')
.withArgs(url)
.resolves({
data: {
data: [
{ slug: artifact1.slug },
{ slug: artifact2.slug }
],
paging: {
page_item_limit: 2,
total_item_count: 2
}
},
status: 200
});

stub.artifacts = [artifact1, artifact2];
return stub;
};

0 comments on commit 774fd5f

Please sign in to comment.