Files

318 lines
16 KiB
JavaScript

// Tests for the protoc-gen-pbjs plugin.
var tape = require("tape");
var path = require("path");
var Module = require("module");
var protobuf = require("..");
var child_process = require("child_process");
function cliTest(test, testFunc) {
// pbjs does not seem to work with Node v4, so skip this test if we're running on it
if (process.versions.node.match(/^4\./)) {
test.end();
return;
}
// Alter the require cache to make the cli work since it requires "protobufjs"
// and we don't want to mess with "npm link"
var savedResolveFilename = Module._resolveFilename;
Module._resolveFilename = function(request, parent) {
if (request === "protobufjs")
return request;
if (request === "protobufjs/ext/descriptor")
return path.resolve("ext/descriptor/index.js");
if (request === "protobufjs/google/protobuf/compiler/plugin.json")
return path.resolve("google/protobuf/compiler/plugin.json");
return savedResolveFilename(request, parent);
};
require.cache.protobufjs = require.cache[path.resolve("index.js")];
try {
testFunc();
} finally {
// Rollback all the require() related mess we made
delete require.cache.protobufjs;
Module._resolveFilename = savedResolveFilename;
}
}
tape.test("protoc-gen-pbjs generates static-module responses", function(test) {
cliTest(test, function() {
var descriptor = require("../ext/descriptor");
var plugin = require("../cli/protoc-gen-pbjs");
var root = protobuf.loadSync("tests/data/cli/test.proto").resolveAll();
var set = root.toDescriptor("proto3");
var request = plugin.CodeGeneratorRequest.encode({
fileToGenerate: [ "tests/data/cli/test.proto" ],
parameter: "file=awesome.js,target=static-module,wrap=esm,dts",
protoFile: set.file
}).finish();
test.ok(descriptor.FileDescriptorSet, "descriptor extension loaded");
plugin.run(request, function(err, response) {
test.error(err, "plugin request decoded");
test.notOk(response.error, "response has no error");
test.equal(response.supportedFeatures, 3, "advertises proto3 optional and editions support");
test.equal(response.minimumEdition, 1000, "advertises minimum edition 2023");
test.equal(response.maximumEdition, 1001, "advertises maximum edition 2024");
test.equal(response.file.length, 2, "writes js and dts files");
test.equal(response.file[0].name, "awesome.js", "writes requested js file");
test.equal(response.file[1].name, "awesome.d.ts", "derives dts file");
test.ok(response.file[0].content.indexOf("protobufjs/minimal.js") >= 0, "uses minimal runtime for esm static module");
test.ok(response.file[0].content.indexOf("regularField") >= 0, "camel-cases descriptor field names by default");
test.ok(response.file[0].content.indexOf("regular_field") < 0, "does not keep snake_case by default");
test.ok(response.file[1].content.indexOf("constructor(properties?: OneofContainer.$Properties);") >= 0, "emits constructable static declarations");
test.end();
});
});
});
tape.test("protoc-gen-pbjs generates json-module responses", function(test) {
cliTest(test, function() {
var plugin = require("../cli/protoc-gen-pbjs");
var root = protobuf.loadSync("tests/data/package.proto").resolveAll();
var set = root.toDescriptor("proto3");
var request = plugin.CodeGeneratorRequest.encode({
fileToGenerate: [ "tests/data/package.proto" ],
parameter: "file=bundle.js,target=json-module,wrap=esm,dts",
protoFile: set.file
}).finish();
plugin.run(request, function(err, response) {
test.error(err, "plugin request decoded");
test.notOk(response.error, "response has no error");
test.equal(response.file.length, 2, "writes js and dts files");
test.ok(response.file[0].content.indexOf("protobufjs/light.js") >= 0, "uses light runtime for esm json module");
test.ok(response.file[0].content.indexOf("export const Package") >= 0, "emits named export for reflected type");
test.ok(response.file[1].content.indexOf("private constructor();") >= 0, "marks reflection-backed declarations as non-constructable");
test.ok(response.file[1].content.indexOf("Reflection-backed declarations are not constructable. Use Package.create(...) instead.") >= 0, "explains create usage");
test.end();
});
});
});
tape.test("protoc-gen-pbjs preserves descriptor field names", function(test) {
cliTest(test, function() {
var descriptor = require("../ext/descriptor");
var plugin = require("../cli/protoc-gen-pbjs");
var set = descriptor.FileDescriptorSet.create({
file: [ {
name: "names.proto",
syntax: "proto3",
package: "pkg",
messageType: [ {
name: "Names",
field: [
{
name: "field_name17__",
number: 1,
label: 1,
type: 5
},
{
name: "custom_json",
jsonName: "customOutput",
number: 2,
label: 1,
type: 9
},
{
name: "map_field17__",
number: 3,
label: 3,
type: 11,
typeName: ".pkg.Names.MapField17Entry"
}
],
nestedType: [ {
name: "MapField17Entry",
field: [
{
name: "key",
number: 1,
label: 1,
type: 9
},
{
name: "value",
number: 2,
label: 1,
type: 5
}
],
options: {
mapEntry: true
}
} ]
} ]
} ]
});
function request(parameter) {
return plugin.CodeGeneratorRequest.encode({
fileToGenerate: [ "names.proto" ],
parameter: parameter,
protoFile: set.file
}).finish();
}
plugin.run(request("file=names.js,target=json-module,wrap=commonjs,dts"), function(defaultErr, defaultResponse) {
test.error(defaultErr, "default request decoded");
test.notOk(defaultResponse.error, "default response has no error");
var defaultJs = defaultResponse.file[0].content;
var defaultDts = defaultResponse.file[1].content;
test.ok(defaultDts.indexOf("fieldName17?:") >= 0, "uses synthesized json_name by default");
test.ok(defaultDts.indexOf("customOutput?:") >= 0, "uses custom descriptor json_name by default");
test.ok(defaultDts.indexOf("mapField17?:") >= 0, "uses synthesized map field json_name by default");
test.ok(defaultDts.indexOf("field_name17__?:") < 0, "does not keep proto names by default");
test.ok(defaultJs.indexOf("\"protoName\": \"field_name17__\"") >= 0, "keeps original field proto name");
test.ok(defaultJs.indexOf("\"protoName\": \"map_field17__\"") >= 0, "keeps original map proto name");
test.ok(defaultJs.indexOf("\"jsonName\": \"customOutput\"") >= 0, "keeps custom JSON name");
plugin.run(request("file=names.js,target=json-module,wrap=commonjs,dts,keep-case"), function(keepErr, keepResponse) {
test.error(keepErr, "keep-case request decoded");
test.notOk(keepResponse.error, "keep-case response has no error");
var keepJs = keepResponse.file[0].content;
var keepDts = keepResponse.file[1].content;
test.ok(keepDts.indexOf("field_name17__?:") >= 0, "keeps field proto names when requested");
test.ok(keepDts.indexOf("custom_json?:") >= 0, "keeps custom field proto names when requested");
test.ok(keepDts.indexOf("map_field17__?:") >= 0, "keeps map field proto names when requested");
test.ok(keepDts.indexOf("fieldName17?:") < 0, "does not use json_name when keep-case is requested");
test.ok(keepJs.indexOf("\"jsonName\": \"customOutput\"") >= 0, "still keeps custom JSON name");
test.end();
});
});
});
});
tape.test("protoc-gen-pbjs validates options", function(test) {
cliTest(test, function() {
var plugin = require("../cli/protoc-gen-pbjs");
var root = protobuf.loadSync("tests/data/package.proto").resolveAll();
var set = root.toDescriptor("proto3");
function request(parameter) {
return plugin.CodeGeneratorRequest.encode({
fileToGenerate: [ "tests/data/package.proto" ],
parameter: parameter,
protoFile: set.file
}).finish();
}
plugin.run(request("target=static-module"), function(defaultErr, defaultResponse) {
test.error(defaultErr, "default file request decoded");
test.notOk(defaultResponse.error, "default file response has no error");
test.equal(defaultResponse.file[0].name, "index.js", "defaults to index.js");
plugin.run(request("file=./normalized.js,target=static-module"), function(normalizedErr, normalizedResponse) {
test.error(normalizedErr, "normalized file request decoded");
test.notOk(normalizedResponse.error, "normalized file response has no error");
test.equal(normalizedResponse.file[0].name, "normalized.js", "normalizes current-directory path components");
plugin.run(request("file=gen/./normalized.js,target=static-module"), function(nestedErr, nestedResponse) {
test.error(nestedErr, "nested normalized file request decoded");
test.notOk(nestedResponse.error, "nested normalized file response has no error");
test.equal(nestedResponse.file[0].name, "gen/normalized.js", "normalizes nested current-directory path components");
plugin.run(request("file=../bad.js,target=static-module"), function(pathErr, pathResponse) {
test.error(pathErr, "bad path request decoded");
test.equal(pathResponse.error, "file must not contain '..': ../bad.js", "rejects parent paths");
plugin.run(request("file=gen/../bad.js,target=static-module"), function(nestedPathErr, nestedPathResponse) {
test.error(nestedPathErr, "nested bad path request decoded");
test.equal(nestedPathResponse.error, "file must not contain '..': gen/../bad.js", "rejects nested parent paths");
plugin.run(request("file=bad.txt,target=static-module"), function(extErr, extResponse) {
test.error(extErr, "bad extension request decoded");
test.equal(extResponse.error, "file must end in .js, .mjs or .cjs: bad.txt", "rejects non-js output files");
plugin.run(request("file=bad.js,target=json"), function(targetErr, targetResponse) {
test.error(targetErr, "bad target request decoded");
test.equal(targetResponse.error, "unsupported target: json", "rejects unsupported target");
plugin.run(request("file=bad.js,unknown=true"), function(optionErr, optionResponse) {
test.error(optionErr, "unknown option request decoded");
test.equal(optionResponse.error, "unknown option: unknown", "rejects unknown option");
test.end();
});
});
});
});
});
});
});
});
});
});
tape.test("protoc-gen-pbjs supports keep-case and edition 2024 descriptors", function(test) {
cliTest(test, function() {
var descriptor = require("../ext/descriptor");
var plugin = require("../cli/protoc-gen-pbjs");
var set = descriptor.FileDescriptorSet.create({
file: [ {
name: "edition.proto",
syntax: "editions",
edition: descriptor.Edition.EDITION_2024,
messageType: [ {
name: "EditionMessage",
field: [ {
name: "some_field",
number: 1,
label: 1,
type: 9
} ]
} ]
} ]
});
var request = plugin.CodeGeneratorRequest.encode({
fileToGenerate: [ "edition.proto" ],
parameter: "file=edition.js,keep-case",
protoFile: set.file
}).finish();
plugin.run(request, function(err, response) {
test.error(err, "plugin request decoded");
test.notOk(response.error, "response has no error");
test.equal(response.maximumEdition, 1001, "advertises edition 2024");
test.ok(response.file[0].content.indexOf("some_field") >= 0, "keeps snake_case when requested");
test.end();
});
});
});
tape.test("protoc-gen-pbjs binary reads request and writes response", function(test) {
cliTest(test, function() {
var plugin = require("../cli/protoc-gen-pbjs");
var root = protobuf.loadSync("tests/data/package.proto").resolveAll();
var request = plugin.CodeGeneratorRequest.encode({
fileToGenerate: [ "tests/data/package.proto" ],
parameter: "",
protoFile: root.toDescriptor("proto3").file
}).finish();
var child = child_process.spawn(process.execPath, [ "cli/bin/protoc-gen-pbjs" ], {
stdio: [ "pipe", "pipe", "pipe" ]
});
var stdout = [];
var stderr = [];
child.stdout.on("data", function(chunk) {
stdout.push(chunk);
});
child.stderr.on("data", function(chunk) {
stderr.push(chunk);
});
child.on("close", function(code) {
test.equal(code, 0, "binary exits successfully");
test.equal(Buffer.concat(stderr).toString("utf8"), "", "binary writes no stderr");
var response = plugin.CodeGeneratorResponse.decode(Buffer.concat(stdout));
test.notOk(response.error, "response has no error");
test.equal(response.file.length, 1, "writes one js file by default");
test.equal(response.file[0].name, "index.js", "writes default file");
test.end();
});
child.stdin.end(Buffer.from(request));
});
});