|
| 1 | +import { Schema } from "effect" |
| 2 | +import { describe, expect, it } from "vitest" |
| 3 | + |
| 4 | +import { TxInfoSchema } from "../src/sdk/provider/internal/Koios.js" |
| 5 | + |
| 6 | +// Minimal valid TxInfoSchema payload — only fields needed to exercise the new structs |
| 7 | +const baseTxInfo = { |
| 8 | + tx_hash: "d10133964da9e443b303917fd0b7644ae3d01c133deff85b4f59416c2d00f530", |
| 9 | + block_hash: "f144a8264acf4bdfe2e1241170969c930d64ab6b0996a4a45237b623f1dd670e", |
| 10 | + block_height: 5385757, |
| 11 | + epoch_no: 250, |
| 12 | + epoch_slot: 142, |
| 13 | + absolute_slot: 22636942, |
| 14 | + tx_timestamp: 1614203233, |
| 15 | + tx_block_index: 0, |
| 16 | + tx_size: 512, |
| 17 | + total_output: "157832856", |
| 18 | + fee: "172101", |
| 19 | + treasury_donation: "0", |
| 20 | + deposit: "0", |
| 21 | + invalid_before: null, |
| 22 | + invalid_after: null, |
| 23 | + collateral_inputs: null, |
| 24 | + collateral_output: null, |
| 25 | + reference_inputs: null, |
| 26 | + inputs: [], |
| 27 | + outputs: [], |
| 28 | + withdrawals: null, |
| 29 | + assets_minted: null, |
| 30 | + metadata: null, |
| 31 | + certificates: null, |
| 32 | + native_scripts: null, |
| 33 | + plutus_contracts: null, |
| 34 | + voting_procedures: null, |
| 35 | + proposal_procedures: null |
| 36 | +} |
| 37 | + |
| 38 | +describe("Koios TxInfoSchema — voting_procedures & proposal_procedures", () => { |
| 39 | + describe("voting_procedures", () => { |
| 40 | + it("accepts null", () => { |
| 41 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ ...baseTxInfo, voting_procedures: null }) |
| 42 | + expect(result.voting_procedures).toBeNull() |
| 43 | + }) |
| 44 | + |
| 45 | + it("accepts a valid voting procedure array", () => { |
| 46 | + const votingProcedures = [ |
| 47 | + { |
| 48 | + proposal_tx_hash: "d10133964da9e443b303917fd0b7644ae3d01c133deff85b4f59416c2d00f530", |
| 49 | + proposal_index: 0, |
| 50 | + voter_role: "DRep" as const, |
| 51 | + voter: "drep1yfhyq6tztjksqqpd5lglc3zr2tn8vylgjh9xzz7n2p4l4lgk3qam3", |
| 52 | + voter_hex: "6e4069625cad00002da7d1fc444352e67613e895ca610bd3506bfafd", |
| 53 | + vote: "Yes" as const |
| 54 | + } |
| 55 | + ] |
| 56 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ ...baseTxInfo, voting_procedures: votingProcedures }) |
| 57 | + expect(result.voting_procedures).toHaveLength(1) |
| 58 | + expect(result.voting_procedures![0].voter_role).toBe("DRep") |
| 59 | + expect(result.voting_procedures![0].vote).toBe("Yes") |
| 60 | + }) |
| 61 | + |
| 62 | + it("accepts all valid voter_role values", () => { |
| 63 | + const roles = ["DRep", "SPO", "ConstitutionalCommittee"] as const |
| 64 | + for (const voter_role of roles) { |
| 65 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ |
| 66 | + ...baseTxInfo, |
| 67 | + voting_procedures: [ |
| 68 | + { |
| 69 | + proposal_tx_hash: "abc123", |
| 70 | + proposal_index: 0, |
| 71 | + voter_role, |
| 72 | + voter: "voter", |
| 73 | + voter_hex: "abcd", |
| 74 | + vote: "No" |
| 75 | + } |
| 76 | + ] |
| 77 | + }) |
| 78 | + expect(result.voting_procedures![0].voter_role).toBe(voter_role) |
| 79 | + } |
| 80 | + }) |
| 81 | + |
| 82 | + it("accepts all valid vote values", () => { |
| 83 | + const votes = ["Yes", "No", "Abstain"] as const |
| 84 | + for (const vote of votes) { |
| 85 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ |
| 86 | + ...baseTxInfo, |
| 87 | + voting_procedures: [ |
| 88 | + { |
| 89 | + proposal_tx_hash: "abc", |
| 90 | + proposal_index: 0, |
| 91 | + voter_role: "SPO", |
| 92 | + voter: "v", |
| 93 | + voter_hex: "ff", |
| 94 | + vote |
| 95 | + } |
| 96 | + ] |
| 97 | + }) |
| 98 | + expect(result.voting_procedures![0].vote).toBe(vote) |
| 99 | + } |
| 100 | + }) |
| 101 | + |
| 102 | + it("rejects an unknown voter_role", () => { |
| 103 | + expect(() => |
| 104 | + Schema.decodeUnknownSync(TxInfoSchema)({ |
| 105 | + ...baseTxInfo, |
| 106 | + voting_procedures: [ |
| 107 | + { |
| 108 | + proposal_tx_hash: "abc", |
| 109 | + proposal_index: 0, |
| 110 | + voter_role: "InvalidRole", // not in the literal union |
| 111 | + voter: "v", |
| 112 | + voter_hex: "ff", |
| 113 | + vote: "Yes" |
| 114 | + } |
| 115 | + ] |
| 116 | + }) |
| 117 | + ).toThrow() |
| 118 | + }) |
| 119 | + |
| 120 | + it("rejects an unknown vote value", () => { |
| 121 | + expect(() => |
| 122 | + Schema.decodeUnknownSync(TxInfoSchema)({ |
| 123 | + ...baseTxInfo, |
| 124 | + voting_procedures: [ |
| 125 | + { |
| 126 | + proposal_tx_hash: "abc", |
| 127 | + proposal_index: 0, |
| 128 | + voter_role: "DRep", |
| 129 | + voter: "v", |
| 130 | + voter_hex: "ff", |
| 131 | + vote: "Maybe" // not Yes/No/Abstain |
| 132 | + } |
| 133 | + ] |
| 134 | + }) |
| 135 | + ).toThrow() |
| 136 | + }) |
| 137 | + }) |
| 138 | + |
| 139 | + describe("proposal_procedures", () => { |
| 140 | + it("accepts null", () => { |
| 141 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ ...baseTxInfo, proposal_procedures: null }) |
| 142 | + expect(result.proposal_procedures).toBeNull() |
| 143 | + }) |
| 144 | + |
| 145 | + it("accepts a valid InfoAction proposal", () => { |
| 146 | + const proposals = [ |
| 147 | + { |
| 148 | + index: 0, |
| 149 | + type: "InfoAction" as const, |
| 150 | + description: { tag: "InfoAction" }, |
| 151 | + deposit: "100000000000", |
| 152 | + return_address: "stake1uy6yzwsxxc28lfms0qmpxvyz9a7y770rtcqx9y96m42cttqwvp4m5", |
| 153 | + expiration: 680, |
| 154 | + meta_url: "https://example.com/meta.json", |
| 155 | + meta_hash: "dc208474e195442d07a5b6d42af19bb2db02229427dfb53ab23122e6b0e2487d", |
| 156 | + withdrawal: null, |
| 157 | + param_proposal: null |
| 158 | + } |
| 159 | + ] |
| 160 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ ...baseTxInfo, proposal_procedures: proposals }) |
| 161 | + expect(result.proposal_procedures).toHaveLength(1) |
| 162 | + expect(result.proposal_procedures![0].type).toBe("InfoAction") |
| 163 | + expect(result.proposal_procedures![0].expiration).toBe(680) |
| 164 | + }) |
| 165 | + |
| 166 | + it("accepts a TreasuryWithdrawals proposal with withdrawal array", () => { |
| 167 | + const proposals = [ |
| 168 | + { |
| 169 | + index: 0, |
| 170 | + type: "TreasuryWithdrawals" as const, |
| 171 | + description: { tag: "TreasuryWithdrawals" }, |
| 172 | + deposit: "100000000000", |
| 173 | + return_address: "stake1uy6yzwsxxc28lfms0qmpxvyz9a7y770rtcqx9y96m42cttqwvp4m5", |
| 174 | + expiration: null, |
| 175 | + meta_url: null, |
| 176 | + meta_hash: null, |
| 177 | + withdrawal: [ |
| 178 | + { |
| 179 | + stake_address: "stake1uy6yzwsxxc28lfms0qmpxvyz9a7y770rtcqx9y96m42cttqwvp4m5", |
| 180 | + amount: "31235800000" |
| 181 | + } |
| 182 | + ], |
| 183 | + param_proposal: null |
| 184 | + } |
| 185 | + ] |
| 186 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ ...baseTxInfo, proposal_procedures: proposals }) |
| 187 | + expect(result.proposal_procedures![0].withdrawal).toHaveLength(1) |
| 188 | + expect(result.proposal_procedures![0].withdrawal![0].amount).toBe("31235800000") |
| 189 | + }) |
| 190 | + |
| 191 | + it("accepts all valid proposal types", () => { |
| 192 | + const types = [ |
| 193 | + "ParameterChange", |
| 194 | + "HardForkInitiation", |
| 195 | + "TreasuryWithdrawals", |
| 196 | + "NoConfidence", |
| 197 | + "NewCommittee", |
| 198 | + "NewConstitution", |
| 199 | + "InfoAction" |
| 200 | + ] as const |
| 201 | + |
| 202 | + for (const type of types) { |
| 203 | + const result = Schema.decodeUnknownSync(TxInfoSchema)({ |
| 204 | + ...baseTxInfo, |
| 205 | + proposal_procedures: [ |
| 206 | + { |
| 207 | + index: 0, |
| 208 | + type, |
| 209 | + description: {}, |
| 210 | + deposit: "100000000000", |
| 211 | + return_address: "stake1abc", |
| 212 | + expiration: null, |
| 213 | + meta_url: null, |
| 214 | + meta_hash: null, |
| 215 | + withdrawal: null, |
| 216 | + param_proposal: null |
| 217 | + } |
| 218 | + ] |
| 219 | + }) |
| 220 | + expect(result.proposal_procedures![0].type).toBe(type) |
| 221 | + } |
| 222 | + }) |
| 223 | + |
| 224 | + it("rejects an unknown proposal type", () => { |
| 225 | + expect(() => |
| 226 | + Schema.decodeUnknownSync(TxInfoSchema)({ |
| 227 | + ...baseTxInfo, |
| 228 | + proposal_procedures: [ |
| 229 | + { |
| 230 | + index: 0, |
| 231 | + type: "UnknownProposalType", // not in the literal union |
| 232 | + description: {}, |
| 233 | + deposit: "100000000000", |
| 234 | + return_address: "stake1abc", |
| 235 | + expiration: null, |
| 236 | + meta_url: null, |
| 237 | + meta_hash: null, |
| 238 | + withdrawal: null, |
| 239 | + param_proposal: null |
| 240 | + } |
| 241 | + ] |
| 242 | + }) |
| 243 | + ).toThrow() |
| 244 | + }) |
| 245 | + |
| 246 | + it("rejects missing required field (deposit)", () => { |
| 247 | + expect(() => |
| 248 | + Schema.decodeUnknownSync(TxInfoSchema)({ |
| 249 | + ...baseTxInfo, |
| 250 | + proposal_procedures: [ |
| 251 | + { |
| 252 | + index: 0, |
| 253 | + type: "InfoAction", |
| 254 | + description: {}, |
| 255 | + // deposit is missing |
| 256 | + return_address: "stake1abc", |
| 257 | + expiration: null, |
| 258 | + meta_url: null, |
| 259 | + meta_hash: null, |
| 260 | + withdrawal: null, |
| 261 | + param_proposal: null |
| 262 | + } |
| 263 | + ] |
| 264 | + }) |
| 265 | + ).toThrow() |
| 266 | + }) |
| 267 | + }) |
| 268 | +}) |
0 commit comments