Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Copy this file to .env in the project root before running sandbox examples
# or sandbox integration tests.
#
# Read-only default-template validation:
# npm run test:sandbox:integration:default-templates
# node examples/sandbox_templates.js

# Sandbox API key auth.
# Required for sandbox examples and integration tests.
QINIU_SANDBOX_API_KEY=

# Optional custom endpoint.
Expand All @@ -10,6 +14,9 @@ QINIU_SANDBOX_ENDPOINT=
# Optional template alias or ID. Defaults to base.
QINIU_SANDBOX_TEMPLATE=base

# Opt in to create and delete a template in examples/sandbox_templates.js.
QINIU_SANDBOX_TEMPLATE_MUTATION_EXAMPLE=false

# Required only for injection-rule and Kodo resource examples.
QINIU_SANDBOX_ACCESS_KEY=
QINIU_SANDBOX_SECRET_KEY=
Expand Down
16 changes: 15 additions & 1 deletion examples/sandbox_templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ runExample(() => {
const client = sandboxClient({
accessToken: env('QINIU_SANDBOX_ACCESS_TOKEN')
});
const allowMutation = env('QINIU_SANDBOX_TEMPLATE_MUTATION_EXAMPLE') === 'true';
const templateName = `nodejs-sdk-example-${Date.now()}`;
let templateID;
let buildID;

return client.listDefaultTemplates().then(defaultTemplates => {
console.log('Default templates:', Array.isArray(defaultTemplates) ? defaultTemplates.length : defaultTemplates);
console.log('Default templates:', Array.isArray(defaultTemplates)
? defaultTemplates.map(template => ({
templateID: template.templateID,
names: template.names,
buildStatus: template.buildStatus
}))
: defaultTemplates);
return client.listTemplates({ limit: 10 });
}).then(templates => {
console.log('Templates:', Array.isArray(templates) ? templates.map(item => item.templateID || item.template_id || item.name) : templates);
Expand All @@ -34,6 +41,10 @@ runExample(() => {
});
});
}).then(() => {
if (!allowMutation) {
console.log('Skipping template mutation; set QINIU_SANDBOX_TEMPLATE_MUTATION_EXAMPLE=true to enable it.');
return null;
}
const advanced = qiniu.sandbox.Template()
.fromDockerfile('FROM node:22\nWORKDIR /app\nENV NODE_ENV=production\nRUN npm ci')
.copyItems([{ src: 'package.json', dest: '/app/' }])
Expand All @@ -55,6 +66,9 @@ runExample(() => {
tags: ['nodejs-sdk-example']
});
}).then(result => {
if (!result) {
return null;
}
templateID = result.templateID || result.template_id || result.id;
buildID = result.buildID || result.build_id;
console.log('Template created:', templateID, 'build:', buildID);
Expand Down
8 changes: 7 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ export declare namespace sandbox {
[key: string]: any;
}

type TemplateBuildStatus = 'building' | 'waiting' | 'ready' | 'error' | 'uploaded';

interface DefaultTemplateInfo extends TemplateInfo {
buildStatus: TemplateBuildStatus;
}

interface TemplateWithBuilds extends TemplateInfo {
isOwner: boolean;
builds: any[];
Expand Down Expand Up @@ -491,7 +497,7 @@ export declare namespace sandbox {
createTemplateV3(options?: any): Promise<any>;
createTemplateV2(options?: any): Promise<any>;
getTemplateFiles(templateID: string, hash: string): Promise<any>;
listDefaultTemplates(): Promise<any>;
listDefaultTemplates(): Promise<DefaultTemplateInfo[]>;
listTemplates(options?: any): Promise<any>;
getTemplate(templateID: string, options?: any): Promise<TemplateWithBuilds>;
deleteTemplate(templateID: string): Promise<null>;
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"check-type": "tsc --noEmit",
"test": "NODE_ENV=test mocha -t 300000 --retries 3",
"test:sandbox": "NODE_ENV=test mocha -t 300000 --retries 3 test/sandbox_client.test.js test/sandbox_commands.test.js test/sandbox_facade.test.js test/sandbox_filesystem.test.js test/sandbox_git.test.js test/sandbox_pty.test.js test/sandbox_template.test.js",
"test:sandbox:default-templates": "NODE_ENV=test mocha -t 300000 test/sandbox_template.test.js --grep 'default templates'",
"test:sandbox:integration:default-templates": "QINIU_SANDBOX_REQUIRE_API_KEY=true NODE_ENV=test mocha -t 300000 test/sandbox_integration.test.js --grep 'lists default templates'",
"cover": "nyc npm run test",
"report": "nyc report --reporter=html",
"lint": "eslint ."
Expand Down
17 changes: 17 additions & 0 deletions test/sandbox_integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ function integrationConfig () {
}

const config = integrationConfig();
if (process.env.QINIU_SANDBOX_REQUIRE_API_KEY === 'true' && !config.apiKey) {
throw new Error('QINIU_SANDBOX_API_KEY is required for this integration test');
}
const describeIntegration = config.apiKey ? describe : describe.skip;

function authedGitUrl (repoUrl, username, password) {
Expand Down Expand Up @@ -434,6 +437,20 @@ describeIntegration('sandbox integration', function () {
return null;
});
});

it('lists default templates', function () {
const client = sandboxClient();

return client.listDefaultTemplates().then(templates => {
Array.isArray(templates).should.eql(true);
templates.length.should.be.above(0);
templates.forEach(template => {
should(template.templateID).be.String();
Array.isArray(template.names).should.eql(true);
});
integrationLog('default template integration completed', templates.length);
});
});
});

if (!config.apiKey) {
Expand Down
63 changes: 63 additions & 0 deletions test/sandbox_template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,69 @@ const {
} = require('./sandbox_helpers');

describe('test sandbox template module', function () {
it('lists default templates with their public fields', function () {
return startServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.statusCode = 200;
res.end(JSON.stringify([{
templateID: 'base',
public: true,
aliases: ['base'],
names: ['qiniu/base'],
buildStatus: 'ready'
}]));
}).then(fixture => {
const client = new qiniu.sandbox.SandboxClient({
apiKey: 'sandbox-key',
endpoint: fixture.endpoint
});

return client.listDefaultTemplates().then(templates => {
templates.should.eql([{
templateID: 'base',
public: true,
aliases: ['base'],
names: ['qiniu/base'],
buildStatus: 'ready'
}]);
fixture.requests.length.should.eql(1);
fixture.requests[0].method.should.eql('GET');
fixture.requests[0].url.should.eql('/default-templates');
fixture.requests[0].headers['x-api-key'].should.eql('sandbox-key');
}).then(() => closeServer(fixture.server), err => {
return closeServer(fixture.server).then(() => {
throw err;
});
});
});
});

it('surfaces errors from the default templates API', function () {
return startServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.statusCode = 401;
res.end(JSON.stringify({ message: 'unauthorized' }));
}).then(fixture => {
const client = new qiniu.sandbox.SandboxClient({
apiKey: 'invalid-key',
endpoint: fixture.endpoint
});

return client.listDefaultTemplates().then(() => {
throw new Error('expected listDefaultTemplates to fail');
}, err => {
err.name.should.eql('SandboxError');
err.message.should.eql('Sandbox API request failed with status 401: unauthorized');
err.response.statusCode.should.eql(401);
err.data.should.eql({ message: 'unauthorized' });
}).then(() => closeServer(fixture.server), err => {
return closeServer(fixture.server).then(() => {
throw err;
});
});
});
});

it('maps template, build, tag, and access-token APIs', function () {
return startServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
Expand Down
18 changes: 18 additions & 0 deletions test/sandbox_types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import * as qiniu from '../index';

type IsAny<T> = 0 extends (1 & T) ? true : false;
type AssertFalse<T extends false> = T;

async function useSandboxTypes () {
const client = new qiniu.sandbox.SandboxClient({
endpoint: 'https://sandbox.example.com',
Expand Down Expand Up @@ -142,6 +145,13 @@ async function useSandboxTypes () {
const templateInfo: qiniu.sandbox.TemplateWithBuilds = await client.getTemplate('typed-template');
const templateNames: string[] = templateInfo.names;
const isOwner: boolean = templateInfo.isOwner;
const defaultTemplates = await client.listDefaultTemplates();
type DefaultTemplate = typeof defaultTemplates[number];
const defaultTemplateIsTyped: AssertFalse<IsAny<DefaultTemplate>> = false;
const defaultTemplateBuildStatusIsTyped: AssertFalse<IsAny<DefaultTemplate['buildStatus']>> = false;
const defaultTemplate: qiniu.sandbox.DefaultTemplateInfo | undefined = defaultTemplates[0];
const defaultTemplateBuildStatus: qiniu.sandbox.TemplateBuildStatus | undefined =
defaultTemplate && defaultTemplate.buildStatus;
qiniu.Sandbox.list({ client, query: { template: templateNames } });
qiniu.CommandExitError.name;
bytes.length;
Expand All @@ -155,6 +165,14 @@ async function useSandboxTypes () {
if (isOwner) {
isOwner.valueOf();
}
if (defaultTemplate) {
defaultTemplate.templateID.length;
}
if (defaultTemplateBuildStatus) {
defaultTemplateBuildStatus.length;
}
defaultTemplateIsTyped.valueOf();
defaultTemplateBuildStatusIsTyped.valueOf();
}

void useSandboxTypes;
Loading