Files
gaze-indexer/modules/runes/runes/rune_test.go
gazenw fcdecd4046 feat: v0.1.0 release (#13)
* fix: don't remove first block

* fix: make etching_terms nullable

* fix: fix panic if empty pkscript

* chore: change testnet starting block

* feat: more logs

* fix: extract tapscript bug

* feat: more logs

* fix: switch pk to block height

* chore: remove redundant log

* fix: repo

* fix: not found error

* fix: golangci-lint

* feat: add etching tx hash to rune entries

* feat: stop main if indexer failed

* fix: check balance after populating current balance

* fix: sql ambiguous column

* feat: add tx hash and out index in tx output

* fix: actually use transactions to write db

* fix: create rune entry states only during flushes

* fix: mint cap reached off by one

* fix: debug log unsafe

* feat: prevent processing of txs before activation height

* feat: add rune number to rune entry

* feat: include new rune entries in event hash and flushing

* refactor(config): separate init and get config func

Co-authored-by: Gaze <dev@gaze.network>

* feat: remove annoying log

Co-authored-by: Gaze <dev@gaze.network>

* feat: mod tidy

Co-authored-by: Gaze <dev@gaze.network>

* refactor: move main to root

Co-authored-by: Gaze <dev@gaze.network>

* feat(cli): create cli commands

Co-authored-by: Gaze <dev@gaze.network>

* refactor: move main logic to command

Co-authored-by: Gaze <dev@gaze.network>

* doc: remove unused desc

Co-authored-by: Gaze <dev@gaze.network>

* refactor: test structure in runestone_test.go

* fix: edict flaws were ignored

* feat: more tests

* refactor(cli): add local flag

Co-authored-by: Gaze <dev@gaze.network>

* feat: set symbol limit to utf8.MaxRune

* refactor(cli): flags for each module

Co-authored-by: Gaze <dev@gaze.network>

* feat(cli): support db selection

Co-authored-by: Gaze <dev@gaze.network>

* fix: remove temp code

Co-authored-by: Gaze <dev@gaze.network>

* fix: get data from cache in processor first, then dg

* feat(cli): add version command

Co-authored-by: Gaze <dev@gaze.network>

* doc(cli): add refactor plan

Co-authored-by: Gaze <dev@gaze.network>

* refactor(cli): rename files

Co-authored-by: Gaze <dev@gaze.network>

* feat: add main.go

Co-authored-by: Gaze <dev@gaze.network>

* feat: more tests

* feat: add overflow err

* feat: finish runestone tests

* refactor(cli): separate protocol config and cli flag

Co-authored-by: Gaze <dev@gaze.network>

* chore(btc): update example config

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add get block header to datasource interface

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): reorg handling

Co-authored-by: Gaze <dev@gaze.network>

* fix: interface

Co-authored-by: Gaze <dev@gaze.network>

* fix: rename postgres config key

* fix: migrated runes indexer integration to new cli

* fix: commit every block

* feat(btc): add revert data query

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add revert data to processor

Co-authored-by: Gaze <dev@gaze.network>

* feat: implement public errors

* fix: use errs in api

* refactor: move api and usecase outside of internal

* feat: add custom opcode check for datapush

* fix: break if input utxo is not P2TR

* fix: zero len destination case

* fix: get the rest of transaction data in GetTransaction

* refactor: create subscription utils tools

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add btc_database from datasource

Co-authored-by: Gaze <dev@gaze.network>

* doc(btc): add note

Co-authored-by: Gaze <dev@gaze.network>

* wip(btc): imple prepare range func

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add pg queries for datasource

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update queries

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): implement repo for get blocks

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update dg

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): return nil if errors

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update fetch async for db datasource

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add get block header from db for reorg handling

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add todo notes

Co-authored-by: Gaze <dev@gaze.network>

* feat: implement get tx by hash

* fix: rename func

* fix: rename func

* fix: rename func

* fix: fix get transaction by hash

* feat: integrate bitcoin client db to main

* fix: reduce chunk size

* fix: stop main if bitcoin indexer failed

* fix: stop main if runes indexer failed

* fix: move stop() inside goroutine

* chore: add log

* fix: duplicate rune entry number

* feat(btc): add witness utils

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): witness datamodel parsing

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): invalid table name

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): remove uniqte index for hash

Co-authored-by: Gaze <dev@gaze.network>

* doc: add todo

Co-authored-by: Gaze <dev@gaze.network>

* feat(logger): remove error verbose

Co-authored-by: Gaze <dev@gaze.network>

* feat: support postgresql db

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add err notfound

Co-authored-by: Gaze <dev@gaze.network>

* fix: invalid pgx version

Co-authored-by: Gaze <dev@gaze.network>

* fix: invalid indexer flow

Co-authored-by: Gaze <dev@gaze.network>

* feat: refactor runes api

* feat: implement http server

* fix: mount runes api

* fix: error handler

* fix: first empty state error

Co-authored-by: Gaze <dev@gaze.network>

* fix: off by one confirmation

* ci: ignore RollBack error

* fix: change WithPublicMessage to be prefix

* feat: bump cstream version

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): nullable pkscript

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): change rollback style

Co-authored-by: Gaze <dev@gaze.network>

* refactor: move runes out of internal

* feat: rename id field to runeId in rune transaction

* feat(btc): update index

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add default current block

Co-authored-by: Gaze <dev@gaze.network>

* doc: add note

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): use int64 to store sequence

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): upgrade data type for numbers

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc):  upgrade data type for idx

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): get indexed block impl

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): add common.ZeroHash

Co-authored-by: Gaze <dev@gaze.network>

* feat: add chainparam

* feat: implement get transactions

* fix: wrong condition for non-OP_RETURN output

* feat(btc): add verify indexer states

Co-authored-by: Gaze <dev@gaze.network>

* refactor: sorting code

Co-authored-by: Gaze <dev@gaze.network>

* feat: fix interface

* feat(btc): update chuunk size

Co-authored-by: Gaze <dev@gaze.network>

* feat: add rune_etched column in rune transaction

* fix: missing field in create

* feat: add runeEtched in get transactions

* feat: implement get token info

* feat: add holders count in token info

* feat: implement get holders

* fix: return a new repository when beginning a new tx

* fix: rename type

* feat: add pkscript to outpoint balance

* feat: implement get utxos by address api

* fix: spend outpoint bug

* feat: implement get balances by address batch

* feat: sort balances result by amount

* ci: create Dockerfile

Co-authored-by: Gaze <dev@gaze.network>

* ci: add arg run

Co-authored-by: Gaze <dev@gaze.network>

* perf: add automaxprocs

Co-authored-by: Gaze <dev@gaze.network>

* chore: add performance logging

Co-authored-by: Gaze <dev@gaze.network>

* chore: add performance logger for debyug

Co-authored-by: Gaze <dev@gaze.network>

* fix: empty etched at

* fix: revert data sequentially

* fix: remove unused funcs

* fix: main.go

* feat: add flag --api-only to run cmd

* fix: create index

* fix: don't add zero mint to unallocated

* fix: ignore zero burn amount

* feat(reorg): add reorg detail

Co-authored-by: Gaze <dev@gaze.network>

* fix: wrong index type

* feat: implement reporting client to report runes blocks

* feat: implement report node

* feat(runes): add latest block api

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): use logger warn

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): txout aren't revert if it's have to revert spent

Co-authored-by: Gaze <dev@gaze.network>

* fix: annoying error when unsubscribe fetcher

Co-authored-by: Gaze <dev@gaze.network>

* refactor(btc): readable code

Co-authored-by: Gaze <dev@gaze.network>

* fix(indexer): fix subscription closed before process when success fetch

Co-authored-by: Gaze <dev@gaze.network>

* fix: remove module enum

* fix: increase max reorg limit

* feat: add starting height for runes mainnet

* fix(btc): fix `with` modified same row twice

Co-authored-by: Gaze <dev@gaze.network>

* fix(runes): handling latest block not found

Co-authored-by: Gaze <dev@gaze.network>

* feat: add decimals in get transactions

* fix: wrong condition

* feat: add more index

* feat: implement get transactions by pkscript

* feat: allow query by rune id too

* feat: more comments

* perf(btc): bitcoin indexer performance optimization (#4)

* feat(btc): not null to witness

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): add batch insert txin

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): batch insert txout

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): batch insert transaction

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): remove old queries

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): typo

Co-authored-by: Gaze <dev@gaze.network>

* perf(btc): batch insert blocks (#5)

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat(btc): Duplicate coinbase transaction handling (#7)

* feat(btc): tx_hash can duplicated in block v1

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): duplicate tx  will use same txin/txout from previous tx

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): prevent revert block v1 data

if you really want to revert the data before the block version 2, you should reset the database and reindex the data instead.

Co-authored-by: Gaze <dev@gaze.network>

* doc(btc): update list duplicate tx hash

Co-authored-by: Gaze <dev@gaze.network>

* doc(btc): update docs

Co-authored-by: Gaze <dev@gaze.network>

* fix(btc): use last v1 block instead

Co-authored-by: Gaze <dev@gaze.network>

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat: add ping handler

* fix: type

Co-authored-by: Gaze <dev@gaze.network>

* doc: add refactor note

Co-authored-by: Gaze <dev@gaze.network>

* ci: add golang linter and test runner gh action

* ci: use go-test-action@v0

* ci: annotate test result

* ci: update running flag

* fix: try to fix malformed import path

* feat: add mock test

* ci: remove annotation ci

* ci: add annotate test result

* chore: remove unused

* feat: try testify

* feat: remove test

* ci: add go test on macos, windows and go latest version

* ci: test building

* feat: remove mock code

* ci: add sqlc diff checker action (#10)

* feat: Graceful shutdown (#8)

* feat: add shutdown function for indexer

Co-authored-by: Gaze <dev@gaze.network>

* feat: add force shutdown

Co-authored-by: Gaze <dev@gaze.network>

* revert

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): remove unused

Co-authored-by: Gaze <dev@gaze.network>

* style: go fmt

Co-authored-by: Gaze <dev@gaze.network>

* feat: separate context for worker and application

* feat: increase force shutdown timeout

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update logging

Co-authored-by: Gaze <dev@gaze.network>

* feat(btc): update shutdown function

Co-authored-by: Gaze <dev@gaze.network>

* feat: remove wg for shutdown

Co-authored-by: Gaze <dev@gaze.network>

* feat: refactor shutdown flow

Co-authored-by: Gaze <dev@gaze.network>

* feat: update shutdown flow

Co-authored-by: Gaze <dev@gaze.network>

* feat: update maming

Co-authored-by: Gaze <dev@gaze.network>

* feat: update force shutdown logic

Co-authored-by: Gaze <dev@gaze.network>

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat: check reporting config name

* fix: use db config in bitcoin module for runes datasource

* Add migrate commands (#2)

* feat: add migrate up

* feat: add down migration

* fix: example

* feat: change description

* fix: hardcode migration source directory

* Update README.md for public release. (#11)

* feat: initial draft for README.md

* fix: remove some sections

* feat: add block reporting to first description

* fix: reduce redundancy

* feat: update README.md

* Update README.md

* feat: update README.md

* fix: update config.yaml in README

* fix: remove redundant words

* fix: change default datasource

* fix: config.yaml comments

* feat: update README.md

* refactor(logger): format logging (#12)

* feat(logger): format main logger

* feat(logger): use duration ms for gcp output

* refactor(logger): bitcoin node logger

* refactor(logger): indexer logger

* refactor(logger): fix cmd logger

* refactor(logger): logger in config pacakge

* refactor(logger): set pgx error log level debug

* refactor(logger): btcclient datasource

* refactor: processor name

* refactor(logger): runese logger

* refactor(logger): update logger

* fix(runes): wrong btc db datasource

* refactor(logger): remove unnecessary debug log

* refactor: update logger in indexer

* fix(logger): deadlock in load()

* fix: remove unused

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>

* feat(btc): remove unused func

* fix: fix golangci-lint error

* fix(pg): update logger level

* doc: update config example

* feat: go mod tidy

* doc: update readme

* fix: panic cause didn't handle error

* doc: update example config

* doc: update example config in readme

* feat(logger): only log error stacktrace when debug mode is on

* feat(reporting): handling invalid config error

* feat(pg): handling invalid config error

* fix: panic in get_token_info

---------

Co-authored-by: Gaze <gazenw@users.noreply.github.com>
Co-authored-by: Planxnx <thanee@cleverse.com>
Co-authored-by: Thanee Charattrakool <37617738+Planxnx@users.noreply.github.com>
2024-04-29 15:16:10 +07:00

273 lines
7.6 KiB
Go

package runes
import (
"fmt"
"math"
"strings"
"testing"
"github.com/Cleverse/go-utilities/utils"
"github.com/gaze-network/indexer-network/common"
"github.com/gaze-network/uint128"
"github.com/stretchr/testify/assert"
)
func TestRuneString(t *testing.T) {
test := func(rune Rune, encoded string) {
t.Run(encoded, func(t *testing.T) {
t.Parallel()
actualEncoded := rune.String()
assert.Equal(t, encoded, actualEncoded)
actualRune, err := NewRuneFromString(encoded)
assert.NoError(t, err)
assert.Equal(t, rune, actualRune)
})
}
test(NewRune(0), "A")
test(NewRune(1), "B")
test(NewRune(2), "C")
test(NewRune(3), "D")
test(NewRune(4), "E")
test(NewRune(5), "F")
test(NewRune(6), "G")
test(NewRune(7), "H")
test(NewRune(8), "I")
test(NewRune(9), "J")
test(NewRune(10), "K")
test(NewRune(11), "L")
test(NewRune(12), "M")
test(NewRune(13), "N")
test(NewRune(14), "O")
test(NewRune(15), "P")
test(NewRune(16), "Q")
test(NewRune(17), "R")
test(NewRune(18), "S")
test(NewRune(19), "T")
test(NewRune(20), "U")
test(NewRune(21), "V")
test(NewRune(22), "W")
test(NewRune(23), "X")
test(NewRune(24), "Y")
test(NewRune(25), "Z")
test(NewRune(26), "AA")
test(NewRune(27), "AB")
test(NewRune(51), "AZ")
test(NewRune(52), "BA")
test(NewRune(53), "BB")
test(NewRuneFromUint128(utils.Must(uint128.FromString("2055900680524219742"))), "UNCOMMONGOODS")
test(NewRuneFromUint128(uint128.Max.Sub64(2)), "BCGDENLQRQWDSLRUGSNLBTMFIJAT")
test(NewRuneFromUint128(uint128.Max.Sub64(1)), "BCGDENLQRQWDSLRUGSNLBTMFIJAU")
test(NewRuneFromUint128(uint128.Max), "BCGDENLQRQWDSLRUGSNLBTMFIJAV")
}
func TestNewRuneFromBase26Error(t *testing.T) {
_, err := NewRuneFromString("?")
assert.ErrorIs(t, err, ErrInvalidBase26)
}
func TestFirstRuneHeight(t *testing.T) {
test := func(network common.Network, expected uint64) {
t.Run(network.String(), func(t *testing.T) {
t.Parallel()
actual := FirstRuneHeight(network)
assert.Equal(t, expected, actual)
})
}
test(common.NetworkMainnet, 840_000)
test(common.NetworkTestnet, 2_520_000)
}
func TestMinimumRuneAtHeightMainnet(t *testing.T) {
test := func(height uint64, encoded string) {
t.Run(fmt.Sprintf("%d", height), func(t *testing.T) {
t.Parallel()
rune, err := NewRuneFromString(encoded)
assert.NoError(t, err)
actual := MinimumRuneAtHeight(common.NetworkMainnet, height)
assert.Equal(t, rune, actual)
})
}
start := FirstRuneHeight(common.NetworkMainnet)
end := start + common.HalvingInterval
interval := uint64(common.HalvingInterval / 12)
test(0, "AAAAAAAAAAAAA")
test(start/2, "AAAAAAAAAAAAA")
test(start, "ZZYZXBRKWXVA")
test(start+1, "ZZXZUDIVTVQA")
test(end-1, "A")
test(end, "A")
test(end+1, "A")
test(math.MaxUint32, "A")
test(start+interval*0-1, "AAAAAAAAAAAAA")
test(start+interval*0, "ZZYZXBRKWXVA")
test(start+interval*0+1, "ZZXZUDIVTVQA")
test(start+interval*1-1, "AAAAAAAAAAAA")
test(start+interval*1, "ZZYZXBRKWXV")
test(start+interval*1+1, "ZZXZUDIVTVQ")
test(start+interval*2-1, "AAAAAAAAAAA")
test(start+interval*2, "ZZYZXBRKWY")
test(start+interval*2+1, "ZZXZUDIVTW")
test(start+interval*3-1, "AAAAAAAAAA")
test(start+interval*3, "ZZYZXBRKX")
test(start+interval*3+1, "ZZXZUDIVU")
test(start+interval*4-1, "AAAAAAAAA")
test(start+interval*4, "ZZYZXBRL")
test(start+interval*4+1, "ZZXZUDIW")
test(start+interval*5-1, "AAAAAAAA")
test(start+interval*5, "ZZYZXBS")
test(start+interval*5+1, "ZZXZUDJ")
test(start+interval*6-1, "AAAAAAA")
test(start+interval*6, "ZZYZXC")
test(start+interval*6+1, "ZZXZUE")
test(start+interval*7-1, "AAAAAA")
test(start+interval*7, "ZZYZY")
test(start+interval*7+1, "ZZXZV")
test(start+interval*8-1, "AAAAA")
test(start+interval*8, "ZZZA")
test(start+interval*8+1, "ZZYA")
test(start+interval*9-1, "AAAA")
test(start+interval*9, "ZZZ")
test(start+interval*9+1, "ZZY")
test(start+interval*10-2, "AAC")
test(start+interval*10-1, "AAA")
test(start+interval*10, "AAA")
test(start+interval*10+1, "AAA")
test(start+interval*10+interval/2, "NA")
test(start+interval*11-2, "AB")
test(start+interval*11-1, "AA")
test(start+interval*11, "AA")
test(start+interval*11+1, "AA")
test(start+interval*11+interval/2, "N")
test(start+interval*12-2, "B")
test(start+interval*12-1, "A")
test(start+interval*12, "A")
test(start+interval*12+1, "A")
}
func TestMinimumRuneAtHeightTestnet(t *testing.T) {
test := func(height uint64, runeStr string) {
t.Run(fmt.Sprintf("%d", height), func(t *testing.T) {
t.Parallel()
rune, err := NewRuneFromString(runeStr)
assert.NoError(t, err)
actual := MinimumRuneAtHeight(common.NetworkTestnet, height)
assert.Equal(t, rune, actual)
})
}
start := FirstRuneHeight(common.NetworkTestnet)
test(start-1, "AAAAAAAAAAAAA")
test(start, "ZZYZXBRKWXVA")
test(start+1, "ZZXZUDIVTVQA")
}
func TestIsReserved(t *testing.T) {
test := func(runeStr string, expected bool) {
t.Run(runeStr, func(t *testing.T) {
t.Parallel()
rune, err := NewRuneFromString(runeStr)
assert.NoError(t, err)
actual := rune.IsReserved()
assert.Equal(t, expected, actual)
})
}
test("A", false)
test("B", false)
test("ZZZZZZZZZZZZZZZZZZZZZZZZZZ", false)
test("AAAAAAAAAAAAAAAAAAAAAAAAAAA", true)
test("AAAAAAAAAAAAAAAAAAAAAAAAAAB", true)
test("BCGDENLQRQWDSLRUGSNLBTMFIJAV", true)
}
func TestGetReservedRune(t *testing.T) {
test := func(blockHeight uint64, txIndex uint32, expected Rune) {
t.Run(fmt.Sprintf("blockHeight_%d_txIndex_%d", blockHeight, txIndex), func(t *testing.T) {
t.Parallel()
rune := GetReservedRune(blockHeight, txIndex)
assert.Equal(t, expected.String(), rune.String())
})
}
test(0, 0, firstReservedRune)
test(0, 1, Rune(firstReservedRune.Uint128().Add(uint128.From64(1))))
test(0, 2, Rune(firstReservedRune.Uint128().Add(uint128.From64(2))))
test(1, 0, Rune(firstReservedRune.Uint128().Add(uint128.From64(1).Lsh(32))))
test(1, 1, Rune(firstReservedRune.Uint128().Add(uint128.From64(1).Lsh(32).Add(uint128.From64(1)))))
test(1, 2, Rune(firstReservedRune.Uint128().Add(uint128.From64(1).Lsh(32).Add(uint128.From64(2)))))
test(2, 0, Rune(firstReservedRune.Uint128().Add(uint128.From64(2).Lsh(32))))
test(2, 1, Rune(firstReservedRune.Uint128().Add(uint128.From64(2).Lsh(32).Add(uint128.From64(1)))))
test(2, 2, Rune(firstReservedRune.Uint128().Add(uint128.From64(2).Lsh(32).Add(uint128.From64(2)))))
test(math.MaxUint64, math.MaxUint32, Rune(firstReservedRune.Uint128().Add(uint128.From64(math.MaxUint64).Lsh(32).Add(uint128.From64(math.MaxUint32)))))
}
func TestUnlockSteps(t *testing.T) {
for i := 0; i < len(unlockSteps); i++ {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
t.Parallel()
encoded := Rune(unlockSteps[i]).String()
expected := strings.Repeat("A", i+1)
assert.Equal(t, expected, encoded)
})
}
}
func TestCommitment(t *testing.T) {
test := func(rune Rune, expected []byte) {
t.Run(rune.String(), func(t *testing.T) {
t.Parallel()
actual := rune.Commitment()
assert.Equal(t, expected, actual)
})
}
test(NewRune(0), []byte{})
test(NewRune(1), []byte{1})
test(NewRune(2), []byte{2})
test(NewRune(255), []byte{255})
test(NewRune(256), []byte{0, 1})
test(NewRune(257), []byte{1, 1})
test(NewRune(65535), []byte{255, 255})
test(NewRune(65536), []byte{0, 0, 1})
}
func TestRuneMarshal(t *testing.T) {
rune := NewRune(5)
bytes, err := rune.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, []byte(`"F"`), bytes)
}
func TestRuneUnmarshal(t *testing.T) {
str := `"F"`
var rune Rune
err := rune.UnmarshalJSON([]byte(str))
assert.NoError(t, err)
assert.Equal(t, NewRune(5), rune)
str = `1`
err = rune.UnmarshalJSON([]byte(str))
assert.Error(t, err)
}