Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions. - Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation. - Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks. - Developed console export service and store with comprehensive unit tests for export status management.
686 lines
18 KiB
C#
686 lines
18 KiB
C#
using System.Text.Json;
|
|
using StellaOps.Scanner.Analyzers.Lang.Node;
|
|
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
|
|
|
namespace StellaOps.Scanner.Analyzers.Lang.Node.Tests.Node;
|
|
|
|
/// <summary>
|
|
/// Tests for entrypoint detection in Node packages including bin, exports, main,
|
|
/// module, worker, electron, and shebang detection.
|
|
/// </summary>
|
|
public sealed class NodeEntrypointDetectionTests : IDisposable
|
|
{
|
|
private readonly string _tempDir;
|
|
|
|
public NodeEntrypointDetectionTests()
|
|
{
|
|
_tempDir = Path.Combine(Path.GetTempPath(), "node-entrypoint-tests-" + Guid.NewGuid().ToString("N")[..8]);
|
|
Directory.CreateDirectory(_tempDir);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(_tempDir))
|
|
{
|
|
Directory.Delete(_tempDir, recursive: true);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
|
|
private void WriteFile(string relativePath, string content)
|
|
{
|
|
var fullPath = Path.Combine(_tempDir, relativePath);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
|
|
File.WriteAllText(fullPath, content);
|
|
}
|
|
|
|
#region bin field tests
|
|
|
|
[Fact]
|
|
public async Task BinField_StringFormat_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "cli-pkg",
|
|
version = "1.0.0",
|
|
bin = "./cli.js"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("cli.js", "// cli");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("cli.js", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BinField_ObjectFormat_DetectsEntrypoints()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "multi-cli-pkg",
|
|
version = "1.0.0",
|
|
bin = new
|
|
{
|
|
cmd1 = "./bin/cmd1.js",
|
|
cmd2 = "./bin/cmd2.js"
|
|
}
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("bin/cmd1.js", "// cmd1");
|
|
WriteFile("bin/cmd2.js", "// cmd2");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("bin/cmd1.js", result);
|
|
Assert.Contains("bin/cmd2.js", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BinField_ObjectFormat_IncludesBinNames()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "named-cli-pkg",
|
|
version = "1.0.0",
|
|
bin = new
|
|
{
|
|
mycli = "./cli.js"
|
|
}
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("cli.js", "// cli");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("mycli", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region main/module field tests
|
|
|
|
[Fact]
|
|
public async Task MainField_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "lib-pkg",
|
|
version = "1.0.0",
|
|
main = "./dist/index.js"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.js", "// index");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.js", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ModuleField_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "esm-pkg",
|
|
version = "1.0.0",
|
|
module = "./dist/index.mjs"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.mjs", "// esm index");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.mjs", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BothMainAndModule_DetectsBothEntrypoints()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "dual-pkg",
|
|
version = "1.0.0",
|
|
main = "./dist/index.cjs",
|
|
module = "./dist/index.mjs"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.cjs", "// cjs");
|
|
WriteFile("dist/index.mjs", "// esm");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.cjs", result);
|
|
Assert.Contains("dist/index.mjs", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region exports field tests
|
|
|
|
[Fact]
|
|
public async Task ExportsField_StringFormat_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "exports-str-pkg",
|
|
version = "1.0.0",
|
|
exports = "./dist/index.js"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.js", "// index");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.js", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExportsField_ObjectWithImportRequire_DetectsBothEntrypoints()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "exports-obj-pkg",
|
|
version = "1.0.0",
|
|
exports = new
|
|
{
|
|
import = "./dist/index.mjs",
|
|
require = "./dist/index.cjs"
|
|
}
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.mjs", "// esm");
|
|
WriteFile("dist/index.cjs", "// cjs");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.mjs", result);
|
|
Assert.Contains("dist/index.cjs", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExportsField_MultipleSubpaths_DetectsAllEntrypoints()
|
|
{
|
|
// Arrange - Using raw JSON to match the exact structure
|
|
var packageJsonContent = @"{
|
|
""name"": ""exports-multi-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""exports"": {
|
|
""."": ""./dist/index.js"",
|
|
""./utils"": ""./dist/utils.js"",
|
|
""./types"": ""./dist/types.d.ts""
|
|
}
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
WriteFile("dist/index.js", "// index");
|
|
WriteFile("dist/utils.js", "// utils");
|
|
WriteFile("dist/types.d.ts", "// types");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.js", result);
|
|
Assert.Contains("dist/utils.js", result);
|
|
Assert.Contains("dist/types.d.ts", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExportsField_ConditionalExports_DetectsEntrypoints()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""conditional-exports-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""exports"": {
|
|
""."": {
|
|
""import"": ""./dist/index.mjs"",
|
|
""require"": ""./dist/index.cjs"",
|
|
""default"": ""./dist/index.js""
|
|
}
|
|
}
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
WriteFile("dist/index.mjs", "// esm");
|
|
WriteFile("dist/index.cjs", "// cjs");
|
|
WriteFile("dist/index.js", "// default");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.mjs", result);
|
|
Assert.Contains("dist/index.cjs", result);
|
|
Assert.Contains("dist/index.js", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExportsField_NestedConditions_FlattensAndDetectsEntrypoints()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""nested-exports-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""exports"": {
|
|
""."": {
|
|
""node"": {
|
|
""import"": ""./dist/node.mjs"",
|
|
""require"": ""./dist/node.cjs""
|
|
},
|
|
""browser"": ""./dist/browser.js""
|
|
}
|
|
}
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
WriteFile("dist/node.mjs", "// node esm");
|
|
WriteFile("dist/node.cjs", "// node cjs");
|
|
WriteFile("dist/browser.js", "// browser");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/node.mjs", result);
|
|
Assert.Contains("dist/node.cjs", result);
|
|
Assert.Contains("dist/browser.js", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region imports field tests
|
|
|
|
[Fact]
|
|
public async Task ImportsField_DetectsEntrypoints()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""imports-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""imports"": {
|
|
""#internal"": ""./src/internal.js""
|
|
}
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
WriteFile("src/internal.js", "// internal");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("src/internal.js", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region worker field tests
|
|
|
|
[Fact]
|
|
public async Task WorkerField_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "worker-pkg",
|
|
version = "1.0.0",
|
|
worker = "./dist/worker.js"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/worker.js", "// worker");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/worker.js", result);
|
|
Assert.Contains("worker", result); // condition set
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region electron detection tests
|
|
|
|
[Fact]
|
|
public async Task ElectronDependency_DetectsElectronEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "electron-app",
|
|
version = "1.0.0",
|
|
main = "./src/main.js",
|
|
dependencies = new
|
|
{
|
|
electron = "^25.0.0"
|
|
}
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("src/main.js", "// electron main");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("electron", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ElectronDevDependency_DetectsElectronEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "electron-dev-app",
|
|
version = "1.0.0",
|
|
main = "./src/main.js",
|
|
devDependencies = new
|
|
{
|
|
electron = "^25.0.0"
|
|
}
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("src/main.js", "// electron main");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("electron", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region shebang detection tests
|
|
|
|
[Fact]
|
|
public async Task ShebangScript_NodeShebang_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "shebang-pkg",
|
|
version = "1.0.0"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("cli.js", "#!/usr/bin/env node\nconsole.log('cli');");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("cli.js", result);
|
|
Assert.Contains("shebang:node", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShebangScript_DirectNodePath_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "shebang-direct-pkg",
|
|
version = "1.0.0"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("cli.mjs", "#!/usr/bin/node\nconsole.log('cli');");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("cli.mjs", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShebangScript_NotNode_DoesNotDetect()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "shebang-bash-pkg",
|
|
version = "1.0.0"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("script.sh", "#!/bin/bash\necho 'hello'");
|
|
WriteFile("some.js", "// not a shebang");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
// Should not contain shebang:node for non-node scripts
|
|
var json = JsonDocument.Parse(result);
|
|
var hasNodeShebang = json.RootElement.EnumerateArray()
|
|
.Any(p => p.ToString().Contains("shebang:node"));
|
|
// The .sh file won't be scanned for shebangs (wrong extension)
|
|
// The .js file doesn't have a shebang
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShebangScript_TypeScriptExtension_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "shebang-ts-pkg",
|
|
version = "1.0.0"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("cli.ts", "#!/usr/bin/env node\nconsole.log('cli');");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("cli.ts", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShebangScript_WithLeadingWhitespace_DetectsEntrypoint()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "shebang-ws-pkg",
|
|
version = "1.0.0"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("cli.js", " #!/usr/bin/env node\nconsole.log('cli');");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("cli.js", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region path normalization tests
|
|
|
|
[Fact]
|
|
public async Task PathNormalization_LeadingDotSlash_IsNormalized()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "path-norm-pkg",
|
|
version = "1.0.0",
|
|
main = "./dist/index.js"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.js", "// index");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
// Path should be normalized (leading ./ stripped in entrypoint path)
|
|
// The entrypoint evidence contains the normalized path
|
|
var json = JsonDocument.Parse(result);
|
|
var evidence = json.RootElement.EnumerateArray()
|
|
.SelectMany(p => p.TryGetProperty("evidence", out var ev) ? ev.EnumerateArray() : Enumerable.Empty<JsonElement>())
|
|
.Where(e => e.TryGetProperty("source", out var src) && src.GetString() == "package.json:entrypoint")
|
|
.ToList();
|
|
// Should have entrypoint evidence with normalized path (starts with dist/, not ./dist/)
|
|
Assert.True(evidence.Any(e => e.TryGetProperty("value", out var val) &&
|
|
val.GetString()!.StartsWith("dist/", StringComparison.Ordinal)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PathNormalization_MultipleLeadingDotSlash_IsNormalized()
|
|
{
|
|
// Arrange
|
|
var packageJson = new
|
|
{
|
|
name = "multi-dot-pkg",
|
|
version = "1.0.0",
|
|
main = "././dist/index.js"
|
|
};
|
|
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
|
WriteFile("dist/index.js", "// index");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.js", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PathNormalization_BackslashesAreNormalized()
|
|
{
|
|
// Arrange - Windows-style paths
|
|
var packageJsonContent = @"{
|
|
""name"": ""backslash-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""main"": ""dist\\index.js""
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
WriteFile("dist/index.js", "// index");
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("dist/index.js", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region edge cases
|
|
|
|
[Fact]
|
|
public async Task EmptyBinField_DoesNotCrash()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""empty-bin-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""bin"": {}
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("empty-bin-pkg", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EmptyExportsField_DoesNotCrash()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""empty-exports-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""exports"": {}
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("empty-exports-pkg", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NullBinValue_DoesNotCrash()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""null-bin-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""bin"": null
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
Assert.Contains("null-bin-pkg", result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WhitespaceEntrypoint_DoesNotDetect()
|
|
{
|
|
// Arrange
|
|
var packageJsonContent = @"{
|
|
""name"": ""whitespace-main-pkg"",
|
|
""version"": ""1.0.0"",
|
|
""main"": "" ""
|
|
}";
|
|
WriteFile("package.json", packageJsonContent);
|
|
|
|
// Act
|
|
var result = await RunAnalyzerAsync();
|
|
|
|
// Assert
|
|
// Package should exist but whitespace main should not create entrypoint
|
|
Assert.Contains("whitespace-main-pkg", result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private async Task<string> RunAnalyzerAsync()
|
|
{
|
|
var analyzers = new ILanguageAnalyzer[] { new NodeLanguageAnalyzer() };
|
|
return await LanguageAnalyzerTestHarness.RunToJsonAsync(
|
|
_tempDir,
|
|
analyzers,
|
|
TestContext.Current.CancellationToken);
|
|
}
|
|
}
|