Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

feat: add code testing #585

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
Prev Previous commit
Next Next commit
merge main
heyAyushh committed Jan 15, 2025
commit 36d3df4e0051932e7b7603ca10e7c837461d0c3a
4 changes: 2 additions & 2 deletions .github/label-actions.yml
Original file line number Diff line number Diff line change
@@ -13,6 +13,6 @@ solved:
# Close the discussion
close: true
# Set a close reason
close-reason: 'resolved'
close-reason: "resolved"
# Lock the discussion
lock: true
lock: true
2 changes: 0 additions & 2 deletions .github/workflows/contentlayer.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Contentlayer Markdown Formatting

on:
push:
branches: [main]
pull_request:
branches: [main]

71 changes: 7 additions & 64 deletions .github/workflows/label-actions.yml
Original file line number Diff line number Diff line change
@@ -1,73 +1,16 @@
name: "Label Actions"

# env:
# GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}

on:
pull_request_target:
issues:
types: [labeled, unlabeled]
pull_request:
types: [labeled, unlabeled]
# issues:
# types: [labeled, unlabeled]
# discussion:
# types: [labeled, unlabeled]

permissions:
contents: read
issues: write
pull-requests: write
discussions: write

jobs:
# run the local prettier config on the PR
prettier:
if: contains(github.event.pull_request.labels.*.name, 'prettier')
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20]

permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- 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:
node-version: ${{ matrix.node-version }}
cache: "pnpm"

- name: before_install
run:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
&& sudo sysctl -p

# perform steps to modify as desired
- name: pnpm install
run: pnpm install
- name: pnpm prettier:fix
run: pnpm prettier:fix

- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: prettier"

action:
label-actions:
runs-on: ubuntu-latest
steps:
- uses: dessant/label-actions@v4
- uses: dessant/label-actions@v3
with:
# github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
config-path: ".github/label-actions.yml"
github-token: ${{ secrets.GITHUB_TOKEN }}
config-path: .github/label-actions.yml
70 changes: 70 additions & 0 deletions .github/workflows/prettier-label-action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Prettier Formatting on Label

# Explicitly set permissions
permissions:
contents: write
pull-requests: write

on:
pull_request:
types: [labeled]

jobs:
prettier-format:
# Only run if the 'prettier' label is added
if: ${{ contains(github.event.pull_request.labels.*.name, 'prettier') }}
runs-on: ubuntu-latest
steps:
# Checkout the PR branch
- uses: actions/checkout@v4
with:
# Use the full PR head reference
ref: ${{ github.event.pull_request.head.ref }}
# Use the full repository name
repository: ${{ github.event.pull_request.head.repo.full_name }}
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0

# Setup Node.js (required for Prettier)
- uses: actions/setup-node@v4
with:
node-version: "lts/*"

# Install Prettier
- name: Install Prettier
run: npm install --save-dev --save-exact prettier

# Run Prettier using the repo's config
- name: Run Prettier
run: npx prettier --write .

# Check if there are any changes
- name: Check for changes
id: check-changes
run: |
git diff --quiet || echo "changes=true" >> $GITHUB_OUTPUT

# Commit and push changes if any
- name: Commit changes
if: steps.check-changes.outputs.changes == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
git commit -m "[chore] automatic prettier formatting"
git push origin HEAD:${{ github.event.pull_request.head.ref }}

# Remove the label after formatting
# todo: label removal is flaky, disabled for now
# - name: Remove formatting label
# if: always()
# uses: actions/github-script@v7
# with:
# github-token: ${{ secrets.GITHUB_TOKEN }}
# script: |
# github.rest.issues.removeLabel({
# owner: context.repo.owner,
# repo: context.repo.repo,
# issue_number: context.issue.number,
# name: 'prettier'
# })
37 changes: 37 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Mark Stale Issues and PRs

on:
schedule:
# Run daily at midnight
- cron: "0 0 * * *"

jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
# Issues configuration
stale-issue-message:
"This issue has been automatically marked as stale because it has
not had recent activity. Remove stale label or comment or this will
be closed in 5 days."
stale-issue-label: "no-issue-activity"
days-before-issue-stale: 30 # Mark as stale after 30 days of inactivity
days-before-issue-close: 5 # Close 5 days after being marked stale

# Pull Request configuration
stale-pr-message:
"This pull request has been automatically marked as stale because it
has not had recent activity. Remove stale label or comment or this
will be closed in 7 days."
stale-pr-label: "no-pr-activity"
days-before-pr-stale: 21 # Mark PRs as stale after 21 days
days-before-pr-close: 7 # Close PRs 7 days after being marked stale

# Exempt labels and other configurations
exempt-issue-labels: "bug,enhancement,in-progress,not-stale"
exempt-pr-labels: "work-in-progress,review-needed,not-stale"

# Optional: Dry run to test configuration
dry-run: true
4 changes: 2 additions & 2 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -3,10 +3,10 @@
# These global owners will be the default owners for everything in
# the repo. Unless a later match takes precedence, will be requested for
# review when someone opens a pull request.
* @nickfrosty
* @ZYJLiu

# This list owns any file in the `/docs` directory in the root of
# the repository and any of its subdirectories.
# /docs/ @nickfrosty

/content/courses/ @mikemaccana
/docs/ @ZYJLiu
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1013,6 +1013,10 @@ pnpm dev

> Note: The developer content API normally runs locally on port `3001`

4. Installing other packages, since the repo is a pnpm workspace.

`pnpm i -D remark-mdx -w solana-developer-content-api` `remarkmdx`

## Testing Code Snippets

The repository uses Node's native test runner for testing code examples. This
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -53,7 +53,7 @@ For specific validator clients, consult their respective documentation:

| Validator Client | Repo | Website |
| ------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------ |
| Anza's Agave | [Repo](https://github.com/anza-xyz/agave/blob/master/docs) | [Website](https://docs.solanalabs.com/) |
| Anza's Agave | [Repo](https://github.com/anza-xyz/agave/blob/master/docs) | [Website](https://docs.anza.xyz/) |
| Jump Crypto's Firedancer | [Repo](https://github.com/firedancer-io/firedancer/tree/main/book) | [Website](https://firedancer-io.github.io/firedancer/) |

### Developer Guides
16 changes: 16 additions & 0 deletions code/content/web3jsv1/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "web3jsv1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "node --import tsx --test '**/*.test.ts' --trace-deprecation",
"test:coverage": "node --import tsx --experimental-test-coverage --test '**/*.test.ts'"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@solana/web3.js": "^1.98.0"
}
}
24 changes: 24 additions & 0 deletions code/content/web3jsv2/cookbook/wallets/check-publickey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { isAddress, isProgramDerivedAddress } from "@solana/web3.js";

// Note that generateKeyPair() will always give a public key that is valid for users

// Valid public key
const key = "5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY";

// Lies on the ed25519 curve and is suitable for users
console.log("Valid Address: ", isAddress(key));

// Valid public key
const offCurveAddress = "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e";

// Not on the ed25519 curve, therefore not suitable for users, but suitable for programs
console.log(
"Valid Off Curve Address: ",
isProgramDerivedAddress(offCurveAddress),
);

// Not a valid public key
const errorPubkey = "testPubkey";
console.log("Invalid Address: ", isAddress(errorPubkey));

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

(async () => {
const signer = await generateKeyPairSigner();
console.log("address: ", signer.address);
})();

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

export async function restoreKeypair() {
const keypairBytes = new Uint8Array([
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,
]);

return await createKeyPairFromBytes(keypairBytes, true);
}
28 changes: 28 additions & 0 deletions code/content/web3jsv2/cookbook/wallets/sign-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
signBytes,
verifySignature,
getUtf8Encoder,
getBase58Decoder,
Address,
} from "@solana/web3.js";

export async function signMessage(
keys: CryptoKeyPair,
message: string = "Hello, World!",
) {
const encodedMessage = getUtf8Encoder().encode(message);
const signedBytes = await signBytes(keys.privateKey, encodedMessage);

const decoded = getBase58Decoder().decode(signedBytes);
const verified = await verifySignature(
keys.publicKey,
signedBytes,
encodedMessage,
);

return {
signature: signedBytes,
decodedSignature: decoded,
verified,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import test from "node:test";
import assert from "node:assert";
import { isAddress, isProgramDerivedAddress } from "@solana/web3.js";
import { errorPubkey, offCurveAddress, key } from "../check-publickey";

test("check isAddress function", async t => {
await t.test("works with valid address", () => {
assert.equal(isAddress(key), true);
});

await t.test("works with valid off-curve address", () => {
assert.equal(isAddress(offCurveAddress), true);
});

await t.test("fails with invalid address", () => {
assert.equal(isAddress(errorPubkey), false);
});

await t.test("fails with empty string", () => {
assert.equal(isAddress(""), false);
});

await t.test("fails with wrong format", () => {
assert.equal(isAddress("not-base-58!"), false);
});
});

test("check isProgramDerivedAddress function", async t => {
await t.test("returns correct value for off-curve address", () => {
assert.equal(isProgramDerivedAddress(offCurveAddress), false);
});

await t.test("fails with regular address", () => {
assert.equal(isProgramDerivedAddress(key), false);
});

await t.test("fails with invalid address", () => {
assert.equal(isProgramDerivedAddress(errorPubkey), false);
});

await t.test("fails with empty string", () => {
assert.equal(isProgramDerivedAddress(""), false);
});
});

test("check edge cases", async t => {
await t.test("handles null values", () => {
try {
isAddress(null as any);
isProgramDerivedAddress(null as any);
} catch (error) {
assert.ok(error instanceof Error);
}
});

await t.test("handles undefined values", () => {
try {
isAddress(undefined as any);
isProgramDerivedAddress(undefined as any);
} catch (error) {
assert.ok(error instanceof Error);
}
});
});
Loading