Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add code testing #585

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
84 changes: 84 additions & 0 deletions .github/workflows/code:test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Code Snippets testing

on:
pull_request:
branches: [main]
paths:
- "code/**/*.test.ts" # Only trigger on test file changes
schedule:
- cron: "0 0 * * *" # Run daily at midnight UTC

jobs:
snippets-tests:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20]
fail-fast: false

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get changed test files
id: changed-files
uses: tj-actions/changed-files@v39
with:
files: |
code/**/*.test.ts

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "pnpm"

- name: Install dependencies
run: pnpm install

- name: Change Directory
run: cd code

- name: Run tests on changed files
if: github.event_name == 'pull_request'
run: |
CHANGED_FILES="${{ steps.changed-files.outputs.all_changed_files }}"

if [ -z "$CHANGED_FILES" ]; then
echo "No test files were changed"
exit 0
fi

# Create array for test files
declare -a test_files=()

# Collect only .test.ts files
for file in $CHANGED_FILES; do
if [[ $file == *.test.ts ]]; then
test_files+=("$file")
echo "Added test file: $file"
fi
done

# If we found test files, run them together
if [ ${#test_files[@]} -gt 0 ]; then
echo "Running tests for the following files:"
printf '%s\n' "${test_files[@]}"

# Run all test files in a single command
node --import tsx --test "${test_files[@]}" || exit 1
else
echo "No test files to run"
fi

- name: Run all tests
if: github.event_name == 'schedule'
run: |
echo "Running all tests in code directory"
pnpm turbo test
2 changes: 0 additions & 2 deletions .github/workflows/contentlayer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ jobs:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ jobs:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install Vercel CLI and pnpm
run: npm install --global vercel@latest
- name: Pull Vercel Environment Information
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ jobs:
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ package-lock.json
# translations are stored in the `i18n` via crowdin
i18n

# turborepo
.turbo

# code-import
code/node_modules
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PublicKey } from "@solana/web3.js";
import { PublicKey, Keypair } from "@solana/web3.js";

// Note that Keypair.generate() will always give a public key that is valid for users

Expand All @@ -15,5 +15,14 @@ const offCurveAddress = new PublicKey(
// Not on the ed25519 curve, therefore not suitable for users
console.log(PublicKey.isOnCurve(offCurveAddress.toBytes()));

// Not a valid public key
const errorPubkey = new PublicKey("testPubkey");
let errorPubkey;
try {
// Not a valid public key
errorPubkey = new PublicKey("testPubkey");
} catch (err) {
// Error will be caught here
}

const onCurve = PublicKey.isOnCurve(key.toBytes());

export { key, onCurve, offCurveAddress, errorPubkey };
5 changes: 5 additions & 0 deletions code/content/cookbook/wallets/create-keypair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Keypair } from "@solana/web3.js";

const keypair = Keypair.generate();

export { keypair };
5 changes: 5 additions & 0 deletions code/content/cookbook/wallets/generate-mnemonic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as bip39 from "bip39";

const mnemonic = bip39.generateMnemonic();

export { mnemonic };
1 change: 1 addition & 0 deletions code/content/cookbook/wallets/generate-vanity-address.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
solana-keygen grind --starts-with e1v1s:1
15 changes: 15 additions & 0 deletions code/content/cookbook/wallets/restore-bip39-mnemonic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Keypair } from "@solana/web3.js";
import * as bip39 from "bip39";

const mnemonic =
"pill tomorrow foster begin walnut borrow virtual kick shift mutual shoe scatter";

// arguments: (mnemonic, password)
const seed = bip39.mnemonicToSeedSync(mnemonic, "");
const keypair = Keypair.fromSeed(seed.slice(0, 32));

console.log(`${keypair.publicKey.toBase58()}`);

// output: 5ZWj7a1f8tWkjBESHKgrLmXshuXxqeY9SYcfbshpAqPG

export { keypair };
29 changes: 29 additions & 0 deletions code/content/cookbook/wallets/restore-bip44-mnemonic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Keypair } from "@solana/web3.js";
import { HDKey } from "micro-key-producer/slip10.js";
import * as bip39 from "bip39";

type Wallet = {
path: string;
keypair: Keypair;
publicKey: string;
};

const mnemonic =
"neither lonely flavor argue grass remind eye tag avocado spot unusual intact";

const seed = bip39.mnemonicToSeedSync(mnemonic, "");
const hd = HDKey.fromMasterSeed(seed.toString("hex"));

const wallets: Wallet[] = [];

for (let i = 0; i < 10; i++) {
const path = `m/44'/501'/${i}'/0'`;
const keypair = Keypair.fromSeed(hd.derive(path).privateKey);
wallets.push({
path,
keypair,
publicKey: keypair.publicKey.toBase58(),
});
}

export { mnemonic, wallets };
10 changes: 10 additions & 0 deletions code/content/cookbook/wallets/restore-keypair-from-bs58.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Keypair } from "@solana/web3.js";
import bs58 from "bs58";

const keypair = Keypair.fromSecretKey(
bs58.decode(
"4UzFMkVbk1q6ApxvDS8inUxg4cMBxCQRVXRx5msqQyktbi1QkJkt574Jda6BjZThSJi54CHfVoLFdVFX8XFn233L",
),
);

export { keypair as bs58Keypair };
12 changes: 12 additions & 0 deletions code/content/cookbook/wallets/restore-keypair-from-bytes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Keypair } from "@solana/web3.js";

const keypair = Keypair.fromSecretKey(
Uint8Array.from([
174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56,
222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246,
15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121,
121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135,
]),
);

export { keypair as bytesKeypair };
27 changes: 27 additions & 0 deletions code/content/cookbook/wallets/sign-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Keypair } from "@solana/web3.js";
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512";
import { utf8ToBytes } from "@noble/hashes/utils";

// Enable synchronous methods for noble-ed25519
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

const keypair = Keypair.fromSecretKey(
Uint8Array.from([
174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56,
222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246,
15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121,
121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135,
]),
);

const message = "The quick brown fox jumps over the lazy dog";
const messageBytes = utf8ToBytes(message);

// Sign using noble-ed25519
const signature = ed.sign(messageBytes, keypair.secretKey.slice(0, 32));

// Verify using noble-ed25519
const result = ed.verify(signature, messageBytes, keypair.publicKey.toBytes());

export { keypair, message, messageBytes, signature, result };
121 changes: 121 additions & 0 deletions code/content/cookbook/wallets/tests/check-publick.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { describe, test } from "node:test";
import { strict as assert } from "node:assert";
import { PublicKey } from "@solana/web3.js";
import { key, onCurve, offCurveAddress } from "../check-publickey";

describe("Check PublicKey", async () => {
test("should properly instantiate valid key", () => {
assert.ok(key instanceof PublicKey, "key should be a PublicKey instance");
assert.equal(
key.toString(),
"5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY",
"key should match the expected value",
);
});

test("should verify key is on ed25519 curve", () => {
assert.equal(onCurve, true, "key should be on the ed25519 curve");
// Double-check the curve status directly
assert.equal(
PublicKey.isOnCurve(key.toBytes()),
true,
"key should be verified on curve through direct check",
);
});

test("should identify valid address not on curve", () => {
assert.ok(
offCurveAddress instanceof PublicKey,
"offCurveAddress should be a PublicKey instance",
);
assert.equal(
offCurveAddress.toString(),
"4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e",
"offCurveAddress should match the expected value",
);
assert.equal(
PublicKey.isOnCurve(offCurveAddress.toBytes()),
false,
"offCurveAddress should not be on the curve",
);
});

test("should throw error for invalid public key format", () => {
assert.throws(
() => {
new PublicKey("testPubkey");
},
{
name: "Error",
message: /Invalid public key/,
},
"Should throw an error for invalid public key format",
);
});

test("should throw error for empty string public key", () => {
assert.throws(
() => {
new PublicKey("");
},
{
name: "Error",
message: /Invalid public key/,
},
"Should throw an error for empty string",
);
});

test("should handle conversion between different formats", () => {
const keyString = key.toString();
const keyBytes = key.toBytes();
const keyBase58 = key.toBase58();

// Test string conversion
assert.equal(
new PublicKey(keyString).toString(),
keyString,
"should maintain equality when converting through string",
);

// Test bytes conversion
assert.deepEqual(
new PublicKey(keyBytes).toBytes(),
keyBytes,
"should maintain equality when converting through bytes",
);

// Test base58 conversion
assert.equal(
new PublicKey(keyBase58).toBase58(),
keyBase58,
"should maintain equality when converting through base58",
);
});

test("should verify key bytes are correct length", () => {
const keyBytes = key.toBytes();
assert.equal(
keyBytes.length,
32,
"public key bytes should be exactly 32 bytes",
);
});

test("should maintain equality for same public key", () => {
const sameKey = new PublicKey(key.toString());
assert.ok(
key.equals(sameKey),
"same public key should be equal when compared",
);
});

test("should identify different public keys as not equal", () => {
const differentKey = new PublicKey(offCurveAddress.toString());
assert.equal(
key.equals(differentKey),
false,
"different public keys should not be equal",
);
});
});
Loading
Loading