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
5 changes: 4 additions & 1 deletion .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
.claude/
.github/CODEOWNERS
.github/workflows/claude-code.yml
.github/workflows/ci.yml
.gitignore
WASM_VERSION
scripts/download-wasm.sh
CLAUDE.md
LICENSE
README.md
Expand Down Expand Up @@ -31,7 +35,6 @@ src/SchematicHQ.Client/Schematic.cs
src/SchematicHQ.Client/SchematicHQ.Client.Custom.props
src/SchematicHQ.Client/Webhooks/WebhookUtils/
src/SchematicHQ.Client/OpenFeature/
src/SchematicHQ.Client/generate-schema-hash.sh
.fern/replay.lock
.fern/replay.yml
.gitattributes
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ concurrency:

env:
DOTNET_NOLOGO: true
# Authenticates `gh release download` in scripts/download-wasm.sh; the rules engine
# WASM lives in the private SchematicHQ/schematic-api repo, so the default
# GITHUB_TOKEN (scoped to this repo) cannot read it. Requires a GH_TOKEN repo
# secret with read access to schematic-api releases.
GH_TOKEN: ${{ secrets.GH_TOKEN }}

jobs:
ci:
Expand All @@ -22,6 +27,9 @@ jobs:
with:
dotnet-version: 10.x

- name: Download WASM binary
run: ./scripts/download-wasm.sh

- name: Install tools
run: dotnet tool restore

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,7 @@ $RECYCLE.BIN/

# Vim temporary swap files
*.swp

# Rules engine WASM binary (downloaded at build time from schematic-api GitHub Releases)
src/SchematicHQ.Client/RulesEngine/Wasm/rulesengine.wasm
src/SchematicHQ.Client/RulesEngine/Wasm/.wasm_version
68 changes: 68 additions & 0 deletions scripts/download-wasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash
set -e

# Downloads the rules engine WASM binary from the schematic-api GitHub Release.
# Reads the pinned version from WASM_VERSION at the repo root.
#
# The binary is intentionally NOT tracked in git (see .gitignore); it is fetched
# at build time both locally (via the DownloadWasm MSBuild target) and in CI.

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WASM_DIR="$REPO_ROOT/src/SchematicHQ.Client/RulesEngine/Wasm"
VERSION_FILE="$REPO_ROOT/WASM_VERSION"

GITHUB_REPO="SchematicHQ/schematic-api"

if [ ! -f "$VERSION_FILE" ]; then
echo "ERROR: WASM_VERSION file not found at $VERSION_FILE"
exit 1
fi

VERSION=$(tr -d '[:space:]' < "$VERSION_FILE")
TAG="rulesengine/v${VERSION}"
ASSET_NAME="rulesengine-wasm-csharp-v${VERSION}.tar.gz"

# Skip download if binary already exists and version matches
if [ -f "$WASM_DIR/rulesengine.wasm" ] && [ -f "$WASM_DIR/.wasm_version" ]; then
CURRENT=$(tr -d '[:space:]' < "$WASM_DIR/.wasm_version")
if [ "$CURRENT" = "$VERSION" ]; then
echo "WASM binary already at version $VERSION, skipping download."
exit 0
fi
fi

echo "Downloading rules engine WASM v${VERSION}..."
mkdir -p "$WASM_DIR"

TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT

if ! gh release download "$TAG" \
-R "$GITHUB_REPO" \
-p "$ASSET_NAME" \
-D "$TMPDIR" 2>/dev/null; then
echo "ERROR: Failed to download WASM binary"
echo "Tag: $TAG"
echo "Asset: $ASSET_NAME"
echo ""
echo "If this is a new version, ensure a release exists at:"
echo " https://github.com/${GITHUB_REPO}/releases/tag/${TAG}"
echo ""
echo "Ensure the GitHub CLI is authenticated with access to ${GITHUB_REPO}: gh auth status"
exit 1
fi

tar -xzf "$TMPDIR/$ASSET_NAME" -C "$TMPDIR"

if [ ! -f "$TMPDIR/rulesengine.wasm" ]; then
echo "ERROR: rulesengine.wasm not found in release archive"
ls -la "$TMPDIR"
exit 1
fi

cp "$TMPDIR"/rulesengine.wasm "$WASM_DIR/"

echo "$VERSION" > "$WASM_DIR/.wasm_version"

echo "Downloaded rules engine WASM v${VERSION} to $WASM_DIR/"
154 changes: 0 additions & 154 deletions src/SchematicHQ.Client.Test/RulesEngine/CompanyTests.cs

This file was deleted.

84 changes: 1 addition & 83 deletions src/SchematicHQ.Client.Test/RulesEngine/EnumResilienceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public void Setup()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
Converters = {
new ResilientEnumConverter(),
new ComparableTypeConverter()
new ResilientEnumConverter()
}
};
}
Expand All @@ -39,34 +38,6 @@ public void RuleType_UnknownValue_ShouldFallbackToUnknown()
});
}

[Test]
public void ComparableType_UnknownValue_ShouldFallbackToDefault()
{
// Arrange
string unknownComparableTypeJson = "\"unknown_comparable_type\"";

// Act & Assert
Assert.DoesNotThrow(() =>
{
var result = JsonSerializer.Deserialize<ComparableType>(unknownComparableTypeJson, _options);
Assert.That(result, Is.EqualTo(ComparableType.Unknown)); // First enum value
});
}

[Test]
public void ComparableType_EmptyString_ShouldFallbackToDefault()
{
// Arrange
string emptyComparableTypeJson = "\"\"";

// Act & Assert
Assert.DoesNotThrow(() =>
{
var result = JsonSerializer.Deserialize<ComparableType>(emptyComparableTypeJson, _options);
Assert.That(result, Is.EqualTo(ComparableType.Unknown));
});
}

[Test]
public void RuleType_ValidValue_ShouldParseCorrectly()
{
Expand All @@ -80,19 +51,6 @@ public void RuleType_ValidValue_ShouldParseCorrectly()
Assert.That(result, Is.EqualTo(RulesengineRuleType.Standard));
}

[Test]
public void ComparableType_ValidValue_ShouldParseCorrectly()
{
// Arrange
string validComparableTypeJson = "\"String\"";

// Act
var result = JsonSerializer.Deserialize<ComparableType>(validComparableTypeJson, _options);

// Assert
Assert.That(result, Is.EqualTo(ComparableType.String));
}

[Test]
public void RuleType_SnakeCaseValue_ShouldParseCorrectly()
{
Expand All @@ -106,20 +64,6 @@ public void RuleType_SnakeCaseValue_ShouldParseCorrectly()
Assert.That(result, Is.EqualTo(RulesengineRuleType.GlobalOverride));
}

[Test]
public void ComparableType_NullValue_ShouldFallbackToDefault()
{
// Arrange
string nullComparableTypeJson = "null";

// Act & Assert
Assert.DoesNotThrow(() =>
{
var result = JsonSerializer.Deserialize<ComparableType?>(nullComparableTypeJson, _options);
Assert.That(result, Is.Null);
});
}

[Test]
public void RuleType_InvalidValue_ShouldFallbackToUnknown()
{
Expand Down Expand Up @@ -153,19 +97,6 @@ public void RuleType_EmptyValue_ShouldFallbackToUnknown()
});
}

[TestCase("\"invalid_type\"", ComparableType.Unknown)]
[TestCase("\"nonexistent\"", ComparableType.Unknown)]
[TestCase("\"\"", ComparableType.Unknown)]
public void ComparableType_InvalidValues_ShouldFallbackToDefault(string json, ComparableType expected)
{
// Act & Assert
Assert.DoesNotThrow(() =>
{
var result = JsonSerializer.Deserialize<ComparableType>(json, _options);
Assert.That(result, Is.EqualTo(expected));
});
}

[Test]
public void RuleType_Serialization_ShouldUseSnakeCase()
{
Expand All @@ -191,18 +122,5 @@ public void RuleType_UnknownSerialization_ShouldSerializeAsEmptyString()
// Assert
Assert.That(json, Is.EqualTo("\"\""));
}

[Test]
public void ComparableType_Serialization_ShouldUseSnakeCase()
{
// Arrange
var comparableType = ComparableType.String;

// Act
var json = JsonSerializer.Serialize(comparableType, _options);

// Assert
Assert.That(json, Is.EqualTo("\"string\""));
}
}
}
Loading
Loading