> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Ge0frey/nullgraph/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Comprehensive testing guide for the NullGraph Anchor program

## Overview

NullGraph uses **Anchor's testing framework** with TypeScript and Mocha to verify the full protocol lifecycle. The test suite covers protocol initialization, NKA submission, bounty creation, submission matching, approval, and closure.

## Running Tests

### Local Validator (Recommended)

Run tests against a local Solana validator that starts and stops automatically:

```bash theme={null}
anchor test
```

This command:

1. Starts a local Solana validator
2. Deploys the program
3. Runs all tests in `tests/nullgraph.ts`
4. Stops the validator after completion

<Note>
  The local validator provides a clean, isolated testing environment and is the recommended approach for development.
</Note>

### Existing Devnet Deployment

Run tests against an existing devnet deployment (skips local validator):

```bash theme={null}
anchor test --skip-local-validator --provider.cluster devnet
```

<Warning>
  Testing against devnet requires devnet SOL and will execute real transactions on the devnet cluster.
</Warning>

## Test Suite Coverage

The test suite in `tests/nullgraph.ts` covers the complete protocol lifecycle:

### Phase 1: Protocol Initialization

<CodeGroup>
  ```typescript Protocol Initialization theme={null}
  it("initialize_protocol creates ProtocolState with correct values", async () => {
    const tx = await program.methods
      .initializeProtocol(250) // 2.5% fee
      .accounts({
        authority: authority.publicKey,
        protocolState: protocolStatePDA,
        treasury: treasuryKeypair.publicKey,
        systemProgram: SystemProgram.programId,
      })
      .rpc();

    const state = await program.account.protocolState.fetch(protocolStatePDA);
    assert.ok(state.authority.equals(authority.publicKey));
    assert.equal(state.nkaCounter.toNumber(), 0);
    assert.equal(state.bountyCounter.toNumber(), 0);
    assert.equal(state.feeBasisPoints, 250);
  });
  ```

  ```typescript Double-Init Rejection theme={null}
  it("initialize_protocol fails on second call (PDA exists)", async () => {
    try {
      await program.methods
        .initializeProtocol(500)
        .accounts({ /* ... */ })
        .rpc();
      assert.fail("Should have thrown");
    } catch (err: any) {
      expect(err.toString()).to.include("already in use");
    }
  });
  ```
</CodeGroup>

**Verifies:**

* ProtocolState singleton created with correct authority
* Counters initialized at zero
* Fee rate set to 250 basis points (2.5%)
* Treasury address configured
* Duplicate initialization rejected (PDA already exists)

### Phase 2: Null Knowledge Asset Submission

```typescript theme={null}
it("submit_null_result creates NKA with correct fields", async () => {
  const hypothesis = encodeString("Compound X inhibits enzyme Y", 128);
  const methodology = encodeString("Double-blind RCT, n=200", 128);
  const pValue = 8700; // 0.8700
  const sampleSize = 200;

  await program.methods
    .submitNullResult(
      hypothesis,
      methodology,
      expectedOutcome,
      actualOutcome,
      pValue,
      sampleSize,
      dataHash
    )
    .accounts({ /* ... */ })
    .rpc();

  const nr = await program.account.nullResult.fetch(firstNullResultPDA);
  assert.equal(nr.specimenNumber.toNumber(), 1);
  assert.equal(nr.pValue, 8700);
  assert.equal(nr.status, 0); // Pending
});
```

**Verifies:**

* NullResult PDA created with all metadata fields
* Auto-incrementing specimen number (NKA-0001, NKA-0002, etc.)
* Global `nka_counter` increments correctly
* Multiple researchers can submit independently
* Data hash stored correctly (SHA-256 fingerprint)

### Phase 3: Bounty Marketplace

<Steps>
  <Step title="Bounty Creation + Escrow">
    ```typescript theme={null}
    it("create_bounty creates bounty + vault, escrows USDC", async () => {
      const rewardAmount = 1_000_000; // 1 USDC (6 decimals)
      
      await program.methods
        .createBounty(
          encodeString("Looking for CRISPR efficiency null result", 256),
          new anchor.BN(rewardAmount),
          new anchor.BN(deadline)
        )
        .accounts({ /* ... */ })
        .rpc();

      // Verify bounty
      const bounty = await program.account.nullBounty.fetch(bountyPDA);
      assert.equal(bounty.status, 0); // Open
      
      // Verify vault holds USDC
      const vaultAccount = await getAccount(provider.connection, vaultPDA);
      assert.equal(Number(vaultAccount.amount), rewardAmount);
    });
    ```

    **Verifies:** Bounty PDA and vault created, BIO tokens escrowed, counter increments
  </Step>

  <Step title="Bounty Submission">
    ```typescript theme={null}
    it("submit_to_bounty creates submission and sets bounty to Matched", async () => {
      await program.methods
        .submitToBounty()
        .accounts({
          researcher: authority.publicKey,
          nullResult: firstNullResultPDA,
          bounty: bountyPDA,
          submission: submissionPDA,
        })
        .rpc();

      const submission = await program.account.bountySubmission.fetch(submissionPDA);
      assert.equal(submission.status, 0); // Pending
      
      const bounty = await program.account.nullBounty.fetch(bountyPDA);
      assert.equal(bounty.status, 1); // Matched
    });
    ```

    **Verifies:** BountySubmission PDA created, bounty transitions to Matched
  </Step>

  <Step title="Invalid Submission Rejection">
    ```typescript theme={null}
    it("submit_to_bounty fails if bounty not Open", async () => {
      try {
        await program.methods
          .submitToBounty()
          .accounts({ /* second submission */ })
          .rpc();
        assert.fail("Should have thrown");
      } catch (err: any) {
        expect(err.toString()).to.include("InvalidBountyStatus");
      }
    });
    ```

    **Verifies:** Cannot submit to already-matched bounty (status guard)
  </Step>

  <Step title="Approval + Payout">
    ```typescript theme={null}
    it("approve_bounty_submission transfers correct payout + fee", async () => {
      await program.methods
        .approveBountySubmission()
        .accounts({ /* ... */ })
        .rpc();

      // Verify 2.5% fee = 25,000; payout = 975,000
      const fee = Math.floor((rewardAmount * 250) / 10000);
      const payout = rewardAmount - fee;
      
      const treasuryAccount = await getAccount(provider.connection, treasuryUsdcAta);
      assert.equal(Number(treasuryAccount.amount), fee);
      
      const vaultAccount = await getAccount(provider.connection, vaultPDA);
      assert.equal(Number(vaultAccount.amount), 0); // Vault emptied
    });
    ```

    **Verifies:** 97.5% to researcher, 2.5% to treasury, vault emptied, statuses updated
  </Step>

  <Step title="Bounty Closure + Refund">
    ```typescript theme={null}
    it("close_bounty refunds full amount to creator", async () => {
      await program.methods
        .closeBounty()
        .accounts({ /* ... */ })
        .rpc();

      const bounty = await program.account.nullBounty.fetch(bounty2PDA);
      assert.equal(bounty.status, 3); // Closed
      
      // Verify full refund
      assert.equal(
        Number(creatorAfter.amount) - Number(creatorBefore.amount),
        500_000
      );
    });
    ```

    **Verifies:** Full vault balance returned to creator, bounty status becomes Closed
  </Step>

  <Step title="Close Fulfilled Bounty Rejection">
    ```typescript theme={null}
    it("close_bounty fails if already Fulfilled", async () => {
      try {
        await program.methods
          .closeBounty()
          .accounts({ bounty: bountyPDA /* Fulfilled */ })
          .rpc();
        assert.fail("Should have thrown");
      } catch (err: any) {
        expect(err.toString()).to.include("InvalidBountyStatus");
      }
    });
    ```

    **Verifies:** Cannot close already-fulfilled bounty (status guard)
  </Step>
</Steps>

## Test Coverage Summary

| Test Category           | Tests  | Coverage                                               |
| ----------------------- | ------ | ------------------------------------------------------ |
| Protocol Initialization | 2      | Singleton creation, duplicate init rejection           |
| NKA Submission          | 2      | PDA creation, multiple researchers, counter increments |
| Bounty Creation         | 1      | Bounty + vault creation, BIO escrow                    |
| Bounty Submission       | 2      | Submission PDA creation, invalid status rejection      |
| Bounty Approval         | 1      | Payout calculation, fee distribution, vault CPI        |
| Bounty Closure          | 2      | Refund mechanics, fulfilled bounty rejection           |
| **Total**               | **10** | **Full protocol lifecycle**                            |

## Test Data Setup

The test suite uses **mock SPL tokens** to simulate BIO token transfers:

```typescript theme={null}
// Create a mock USDC/BIO mint with 6 decimals
usdcMint = await createMint(
  provider.connection,
  (authority as any).payer,
  authority.publicKey,
  null,
  6 // 6 decimals like USDC/BIO
);

// Mint tokens to creator's ATA
await mintTo(
  provider.connection,
  (authority as any).payer,
  usdcMint,
  creatorUsdcAta,
  authority.publicKey,
  10_000_000 // 10 USDC
);
```

<Note>
  All token operations use the **SPL Token Interface** (`@solana/spl-token`), ensuring compatibility with any SPL-compliant token including BIO.
</Note>

## Running Specific Tests

To run a specific test by name:

```bash theme={null}
npm test -- --grep "protocol initialization"
```

To run tests with verbose output:

```bash theme={null}
npm test -- --reporter spec
```

## Test Scripts

The root `package.json` defines test scripts:

```json theme={null}
{
  "scripts": {
    "test": "ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts",
    "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check",
    "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w"
  }
}
```

* `npm test` - Run all tests with 1000-second timeout
* `npm run lint` - Check code formatting
* `npm run lint:fix` - Auto-fix formatting issues

## Next Steps

* Deploy your program to [devnet](/development/deployment)
* Explore the [program architecture](/protocol/architecture)
* Learn about [accounts and instructions](/protocol/instructions)
