Unit tests configuration logic for endpoint scripting and data serialization.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Sandbox;
[TestClass]
public class SyncToolScriptFilesTests
{
private const string Header = "sourceVersion: 1\nkind: endpoint\nid: hello\n";
private static string NewFolder()
{
var folder = Path.Combine( Path.GetTempPath(), "sync-script-files-" + Guid.NewGuid().ToString( "N" ) );
Directory.CreateDirectory( folder );
return folder;
}
private static string Write( string folder, string name, string text )
{
var path = Path.Combine( folder, name );
File.WriteAllText( path, text );
return path;
}
[TestMethod]
public void EncodedScriptReadsBackExactly()
{
var text = "a \"quoted\" \\ back\nline\ttab \u0001 \u0085 \u2028 bom\uFEFF end\r\n";
var encoded = SyncToolConfig.EncodeYamlDoubleQuoted( text );
Assert.AreEqual( text, JsonSerializer.Deserialize<string>( encoded ) );
}
[TestMethod]
public void SplitMovesStringScriptToSiblingFile()
{
var data = new Dictionary<string, object> { ["runtime"] = "javascript", ["script"] = "return 1;\n", ["slug"] = "hello" };
var envelope = SyncToolConfig.SplitEndpointScript( "hello", data, "endpoints/hello.endpoint.yml", out var fileName, out var scriptText );
Assert.AreEqual( "hello.endpoint.js", fileName );
Assert.AreEqual( "return 1;\n", scriptText );
Assert.IsFalse( envelope.ContainsKey( "script" ) );
Assert.AreEqual( "hello.endpoint.js", (string)envelope["scriptFile"] );
Assert.AreEqual( "javascript", (string)envelope["runtime"] );
}
[TestMethod]
public void SplitKeepsLocalScriptFileNameWhenItFitsTheRuntime()
{
var folder = NewFolder();
var envelopePath = Write( folder, "hello.endpoint.yml", Header + "runtime: javascript\nscriptFile: custom-name.endpoint.js\n" );
var data = new Dictionary<string, object> { ["runtime"] = "javascript", ["script"] = "return 2;" };
var envelope = SyncToolConfig.SplitEndpointScript( "hello", data, envelopePath, out var fileName, out var scriptText );
Assert.AreEqual( "custom-name.endpoint.js", fileName );
Assert.AreEqual( "custom-name.endpoint.js", (string)envelope["scriptFile"] );
Assert.AreEqual( "return 2;", scriptText );
}
[TestMethod]
public void SplitFallsBackToDefaultNameWhenLocalNameDoesNotFitTheRuntime()
{
var folder = NewFolder();
var envelopePath = Write( folder, "hello.endpoint.yml", Header + "runtime: javascript\nscriptFile: custom-name.endpoint.js\n" );
var data = new Dictionary<string, object> { ["runtime"] = "typescript", ["script"] = "export {};" };
var envelope = SyncToolConfig.SplitEndpointScript( "hello", data, envelopePath, out var fileName, out _ );
Assert.AreEqual( "hello.endpoint.ts", fileName );
Assert.AreEqual( "hello.endpoint.ts", (string)envelope["scriptFile"] );
}
[TestMethod]
public void SplitAcceptsParsedJsonValues()
{
var data = JsonSerializer.Deserialize<Dictionary<string, object>>( "{\"runtime\":\"typescript\",\"script\":\"export {};\"}" );
var envelope = SyncToolConfig.SplitEndpointScript( "hello", data, "endpoints/hello.endpoint.yml", out var fileName, out var scriptText );
Assert.AreEqual( "hello.endpoint.ts", fileName );
Assert.AreEqual( "export {};", scriptText );
Assert.IsFalse( envelope.ContainsKey( "script" ) );
}
[TestMethod]
public void SplitKeepsJsonEnvelopeScriptInline()
{
var data = new Dictionary<string, object> { ["runtime"] = "javascript", ["script"] = "return 1;" };
var envelope = SyncToolConfig.SplitEndpointScript( "hello", data, "endpoints/hello.json", out var fileName, out var scriptText );
Assert.IsNull( fileName );
Assert.IsNull( scriptText );
Assert.AreEqual( "return 1;", envelope["script"] );
}
[TestMethod]
public void SplitLeavesEndpointWithoutScriptRuntimeAlone()
{
var data = new Dictionary<string, object> { ["script"] = "x" };
var envelope = SyncToolConfig.SplitEndpointScript( "hello", data, "endpoints/hello.endpoint.yml", out var fileName, out _ );
Assert.IsNull( fileName );
Assert.AreEqual( "x", envelope["script"] );
}
[TestMethod]
public void RawEndpointWithScriptIsSplitOnlyForYamlScriptRuntimes()
{
var yaml = Header + "runtime: javascript\nscript: \"return 1;\"\n";
var split = SyncToolConfig.RawEndpointToSplit( yaml, "endpoints/hello.endpoint.yml" );
Assert.IsNotNull( split );
Assert.AreEqual( "return 1;", ((JsonElement)split["script"]).GetString() );
Assert.IsNull( SyncToolConfig.RawEndpointToSplit( Header + "runtime: javascript\n", "endpoints/hello.endpoint.yml" ) );
Assert.IsNull( SyncToolConfig.RawEndpointToSplit( yaml, "endpoints/hello.json" ) );
}
[TestMethod]
public void ImplicitScriptFileIsInlinedAsScript()
{
var folder = NewFolder();
try
{
var envelope = Write( folder, "hello.endpoint.yml", Header + "runtime: javascript\n" );
Write( folder, "hello.endpoint.js", "return 1;\n" );
var inlined = SyncToolConfig.InlineEndpointScriptSource( envelope, File.ReadAllText( envelope ), out var error );
Assert.IsNull( error );
Assert.AreEqual( Header + "runtime: javascript\nscript: \"return 1;\\n\"\n", inlined );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void EndpointScriptFileForNamesTheImplicitSibling()
{
var folder = NewFolder();
try
{
var envelope = Write( folder, "hello.endpoint.yml", Header + "runtime: typescript\n" );
Assert.AreEqual( Path.Combine( folder, "hello.endpoint.ts" ), SyncToolConfig.EndpointScriptFileFor( envelope ) );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void ScriptFileWithFoldersIsRejected()
{
var folder = NewFolder();
try
{
var text = Header + "runtime: javascript\nscriptFile: sub/hello.endpoint.js\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
var inlined = SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "without folders" );
Assert.AreEqual( text, inlined );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void ScriptFileMustEndWithRuntimeExtension()
{
var folder = NewFolder();
try
{
var text = Header + "runtime: typescript\nscriptFile: hello.endpoint.js\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
Write( folder, "hello.endpoint.js", "return 1;" );
SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "must end with .ts" );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void ScriptFileWithoutRuntimeIsRejected()
{
var folder = NewFolder();
try
{
var text = Header + "scriptFile: hello.endpoint.js\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
Write( folder, "hello.endpoint.js", "return 1;" );
SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "needs runtime" );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void StrayScriptWithoutRuntimeIsReported()
{
var folder = NewFolder();
try
{
var text = Header + "slug: hello\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
Write( folder, "hello.endpoint.js", "return 1;" );
var inlined = SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "runtime is not javascript or typescript" );
Assert.AreEqual( text, inlined );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void JsonEnvelopeCannotReferenceScriptFile()
{
var folder = NewFolder();
try
{
var text = "{\"sourceVersion\":1,\"kind\":\"endpoint\",\"id\":\"hello\",\"runtime\":\"javascript\"}";
var envelope = Write( folder, "hello.json", text );
Write( folder, "hello.endpoint.js", "return 1;" );
var inlined = SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "JSON envelope" );
Assert.AreEqual( text, inlined );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void JavaScriptAndTypeScriptFilesAreAmbiguous()
{
var folder = NewFolder();
try
{
var text = Header + "runtime: javascript\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
Write( folder, "hello.endpoint.js", "return 1;" );
Write( folder, "hello.endpoint.ts", "export {};" );
SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "remove one of the two files" );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void InlineScriptAndScriptFileConflict()
{
var folder = NewFolder();
try
{
var text = Header + "runtime: javascript\nscript: \"old\"\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
Write( folder, "hello.endpoint.js", "return 1;" );
SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
StringAssert.Contains( error, "set both inline and in" );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void MissingScriptFileIsLeftForTheServer()
{
var folder = NewFolder();
try
{
var text = Header + "runtime: javascript\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
var inlined = SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
Assert.IsNull( error );
Assert.AreEqual( text, inlined );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void YamlOnlyEndpointIsUnchanged()
{
var folder = NewFolder();
try
{
var text = Header + "slug: hello\nmethod: GET\n";
var envelope = Write( folder, "hello.endpoint.yml", text );
var inlined = SyncToolConfig.InlineEndpointScriptSource( envelope, text, out var error );
Assert.IsNull( error );
Assert.AreEqual( text, inlined );
}
finally
{
Directory.Delete( folder, true );
}
}
[TestMethod]
public void WithoutPropertyDropsOnlyThatProperty()
{
var obj = JsonSerializer.Deserialize<JsonElement>( "{\"scriptFile\":\"hello.endpoint.js\",\"runtime\":\"javascript\"}" );
var stripped = SyncToolConfig.WithoutProperty( obj, "scriptFile" );
Assert.IsFalse( stripped.TryGetProperty( "scriptFile", out _ ) );
Assert.AreEqual( "javascript", stripped.GetProperty( "runtime" ).GetString() );
}
[TestMethod]
public void SourceLoaderKeepsCaseVariantNestedKeys()
{
var folder = NewFolder();
try
{
var yaml = Write( folder, "casekeys.collection.yml",
"sourceVersion: 1\nKind: collection\nid: casekeys\nschema:\n Foo:\n type: number\n foo:\n type: string\n" );
var json = Write( folder, "casekeys.json",
"{\"sourceVersion\":1,\"KIND\":\"collection\",\"id\":\"casekeys\",\"schema\":{\"Foo\":{\"type\":\"number\"},\"foo\":{\"type\":\"string\"}}}" );
foreach ( var path in new[] { yaml, json } )
{
Assert.IsTrue( SyncToolConfig.TryLoadSourceCanonicalResource( "collection", path, out var resource ), path );
var schema = resource.GetProperty( "schema" );
Assert.AreEqual( "number", schema.GetProperty( "Foo" ).GetProperty( "type" ).GetString(), path );
Assert.AreEqual( "string", schema.GetProperty( "foo" ).GetProperty( "type" ).GetString(), path );
}
}
finally
{
Directory.Delete( folder, true );
}
}
}