mesh-delivery-assigner: the asks package (the keys of its asks and their value) is the module's own, a package artifact; the view's answer key and fit in viewstate (ADR 0634 §1, §4, ADR 0635 §5; issue 1966)
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
// Package asks holds the layout of the assigner's state `asks`: the keys of the asks the assigner puts to a person and the
|
||||
// value it keeps at each, which the assigner writes and the view and the teller read (novox/hq ADR 0634 §1, ADR 0635 §5). It
|
||||
// is the assigner's own published package. The gate's merge ask (`merge.<group>.<heads hash>`) needs proof of who answered
|
||||
// (ADR 0421 §1), so it is answered on the phone alone until issue 953 closes. The assigner's asks need no such proof, and the
|
||||
// view may answer them: the hold of a fingerprint whose health check failed (`hold.<module>.<fingerprint hex>`: release it or
|
||||
// keep it held), the restore of a data module set back (`restore.<node>.<module>.<fingerprint hex>`: restore and lose newer
|
||||
// writes, or leave it stopped, ADR 0429 §6), a backup that failed before an apply, a move a person must allow for a module of
|
||||
// policy record, and the authorising ask of a proposal of placement.
|
||||
package asks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/kept"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
const (
|
||||
// AskKindHold is the kind of the assigner's ask about a held fingerprint.
|
||||
AskKindHold = "hold"
|
||||
// AskKindRestore is the kind of the assigner's ask about a data module set back.
|
||||
AskKindRestore = "restore"
|
||||
// AskKindBackup is the kind of the assigner's ask about a backup before an apply that failed: back up again, or go on
|
||||
// without one (novox/hq ADR 0539 clause 16).
|
||||
AskKindBackup = "backup"
|
||||
// AskKindRecord is the kind of the assigner's ask about a move a person must allow, for a module of policy record: a
|
||||
// composition judged to recreate a container or move data, or one that cannot be judged (ADR 0539 clause 14).
|
||||
AskKindRecord = "record"
|
||||
// AskKindPlacement is the kind of the authorising ask about a proposal of placement (novox/hq ADR 0567 §5):
|
||||
// `placement.<proposal id>`. The kind needs no proof, as the bus's put does not: its yes is an Approve, which only a
|
||||
// channel proving who answered takes and the view never does (ViewAnswerFits).
|
||||
AskKindPlacement = "placement"
|
||||
)
|
||||
|
||||
// AskKey is an ask's key read whole: its Kind, and the parts its kind has: a merge's Group and heads Hash, a hold's Module
|
||||
// and fingerprint FP, a restore's and a backup's Node, Module and FP, a record's Node, Module and the digest Composed of the
|
||||
// composition it would allow, a placement's Proposal.
|
||||
type AskKey struct {
|
||||
Proposal string
|
||||
Kind string
|
||||
Group string
|
||||
Hash string
|
||||
Node string
|
||||
Module string
|
||||
FP string
|
||||
Composed string
|
||||
}
|
||||
|
||||
// AskNeedsProof says whether an ask of this kind must prove who answered it, so the view may not answer it: the merge's
|
||||
// Approve does (ADR 0421 §1, issue 953), the assigner's hold and restore do not. An unknown kind is refused.
|
||||
func AskNeedsProof(kind string) (bool, error) {
|
||||
switch kind {
|
||||
case meshdeliveryclient.AskKindMerge:
|
||||
return true, nil
|
||||
case AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement:
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("asks: %q is not a kind of ask, %s, %s, %s, %s, %s or %s", kind, meshdeliveryclient.AskKindMerge, AskKindHold,
|
||||
AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement)
|
||||
}
|
||||
|
||||
// HoldAskKey is the key of the ask about a module's held fingerprint: `hold.<module>.<hex>`.
|
||||
func HoldAskKey(module, fingerprint string) (string, error) {
|
||||
key, err := builds.BuilderBuildKey(module, fingerprint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return AskKindHold + "." + key, nil
|
||||
}
|
||||
|
||||
// RestoreAskKey is the key of the ask about a data module set back on a node from a fingerprint:
|
||||
// `restore.<node>.<module>.<hex>`.
|
||||
func RestoreAskKey(node, module, fingerprint string) (string, error) {
|
||||
if err := keys.CheckName("node", node); err != nil {
|
||||
return "", err
|
||||
}
|
||||
key, err := builds.BuilderBuildKey(module, fingerprint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return AskKindRestore + "." + node + "." + key, nil
|
||||
}
|
||||
|
||||
// BackupAskKey is the key of the ask about a module's failed backup on a node before it applies a fingerprint:
|
||||
// `backup.<node>.<module>.<hex>`.
|
||||
func BackupAskKey(node, module, fingerprint string) (string, error) {
|
||||
part, err := meshcatalogclient.KeyPart(fingerprint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
key, err := nodeengineclient.ComposedKey(node, module, part)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return AskKindBackup + "." + key, nil
|
||||
}
|
||||
|
||||
// PlacementAskKey is the key of the authorising ask about a proposal of placement: `placement.<proposal id>` (ADR 0567 §5).
|
||||
func PlacementAskKey(proposal string) (string, error) {
|
||||
if err := meshmoduleassignerclient.CheckProposalID(proposal); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return AskKindPlacement + "." + proposal, nil
|
||||
}
|
||||
|
||||
// RecordAskKey is the key of the ask about a move of a module on a node to the composition whose digest it names:
|
||||
// `record.<node>.<module>.<digest hex>`, so a new revision at the same fingerprint is asked again (ADR 0539 clause 14).
|
||||
func RecordAskKey(node, module, composed string) (string, error) {
|
||||
have, err := nodeengineclient.HaveKey(node, module)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := kept.CheckDigest(composed, "a record ask's composition digest"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return AskKindRecord + "." + have + "." + strings.TrimPrefix(composed, "sha256:"), nil
|
||||
}
|
||||
|
||||
// ParseAnyAskKey reads an ask key of any kind (a merge, hold, restore, backup, record or placement ask); it refuses an unknown
|
||||
// kind and a key whose parts do not fit its kind, naming the key.
|
||||
func ParseAnyAskKey(key string) (AskKey, error) {
|
||||
kind, rest, _ := strings.Cut(key, ".")
|
||||
refused := func(err error) (AskKey, error) {
|
||||
return AskKey{}, fmt.Errorf("asks: %q is not an ask key: %w", key, err)
|
||||
}
|
||||
switch kind {
|
||||
case meshdeliveryclient.AskKindMerge:
|
||||
_, group, hash, err := meshdeliveryclient.ParseAskKey(key)
|
||||
if err != nil {
|
||||
return AskKey{}, err
|
||||
}
|
||||
return AskKey{Kind: kind, Group: group, Hash: hash}, nil
|
||||
case AskKindHold:
|
||||
module, fp, err := builds.ParseBuilderBuildKey(rest)
|
||||
if err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
return AskKey{Kind: kind, Module: module, FP: fp}, nil
|
||||
case AskKindRestore:
|
||||
node, build, ok := strings.Cut(rest, ".")
|
||||
if !ok {
|
||||
return refused(fmt.Errorf("a restore ask is restore.<node>.<module>.<fingerprint hex>"))
|
||||
}
|
||||
if err := keys.CheckName("node", node); err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
module, fp, err := builds.ParseBuilderBuildKey(build)
|
||||
if err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil
|
||||
case AskKindBackup:
|
||||
node, module, part, isNode, err := nodeengineclient.ParseComposedKey(rest)
|
||||
if err != nil || isNode {
|
||||
return refused(fmt.Errorf("a backup ask is backup.<node>.<module>.<fingerprint hex>"))
|
||||
}
|
||||
fp, err := meshcatalogclient.ParseKeyPart(part)
|
||||
if err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil
|
||||
case AskKindPlacement:
|
||||
if err := meshmoduleassignerclient.CheckProposalID(rest); err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
return AskKey{Kind: kind, Proposal: rest}, nil
|
||||
case AskKindRecord:
|
||||
parts := strings.Split(rest, ".")
|
||||
if len(parts) != 3 {
|
||||
return refused(fmt.Errorf("a record ask is record.<node>.<module>.<digest hex>"))
|
||||
}
|
||||
node, module, isNode, err := nodeengineclient.ParseHaveKey(parts[0] + "." + parts[1])
|
||||
if err != nil || isNode {
|
||||
return refused(fmt.Errorf("a record ask is record.<node>.<module>.<digest hex>"))
|
||||
}
|
||||
composed := "sha256:" + parts[2]
|
||||
if err := kept.CheckDigest(composed, "a record ask's composition digest"); err != nil {
|
||||
return refused(err)
|
||||
}
|
||||
return AskKey{Kind: kind, Node: node, Module: module, Composed: composed}, nil
|
||||
}
|
||||
_, err := AskNeedsProof(kind)
|
||||
return refused(err)
|
||||
}
|
||||
|
||||
// Ask is the value of a key in the assigner's state asks: the router's ask whole, as the assigner composes it, so the teller
|
||||
// publishes it unchanged. Asking counts the times the assigner wrote the ask at this key, as the gate's ask does: 0 or 1 for
|
||||
// the first, n for the n-th, and the ask's id is meshdeliveryclient.AskingID(key, Asking), so each asking has its own id and
|
||||
// an answer to an earlier asking is never taken for a later one. The ask is the operator-channel seat's as its JSON: its id,
|
||||
// its options and its digest are checked by the assigner and the teller through operator-channel's client (novox/hq ADR
|
||||
// 0627 §4).
|
||||
type Ask struct {
|
||||
V int `json:"v"`
|
||||
Ask json.RawMessage `json:"ask"`
|
||||
Asking int `json:"asking,omitempty"`
|
||||
}
|
||||
|
||||
func (a Ask) check(key string) error {
|
||||
ask, err := ParseAnyAskKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if proof, _ := AskNeedsProof(ask.Kind); proof {
|
||||
return fmt.Errorf("asks: the assigner asks no %s ask: it needs proof of who answered", ask.Kind)
|
||||
}
|
||||
if a.Asking < 0 {
|
||||
return fmt.Errorf("asks: the ask's asking %d is negative", a.Asking)
|
||||
}
|
||||
var fields map[string]json.RawMessage
|
||||
if len(bytes.TrimSpace(a.Ask)) == 0 || json.Unmarshal(a.Ask, &fields) != nil || fields == nil {
|
||||
return fmt.Errorf("asks: the value carries no router's ask")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeAsk is a, the ask at key, as JSON at keys.Version; it refuses a key of a kind that needs proof or of no kind, a
|
||||
// negative asking, and a value carrying no router's ask.
|
||||
func EncodeAsk(key string, a Ask) ([]byte, error) {
|
||||
if err := a.check(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.V = keys.Version
|
||||
return json.Marshal(a)
|
||||
}
|
||||
|
||||
// DecodeAsk reads the ask at key; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeAsk refuses.
|
||||
func DecodeAsk(key string, data []byte) (Ask, error) {
|
||||
var a Ask
|
||||
if err := kept.Decode(data, &a); err != nil {
|
||||
return Ask{}, err
|
||||
}
|
||||
if err := a.check(key); err != nil {
|
||||
return Ask{}, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// SameAsk says whether two asks are the same value: the same version, asking and router's ask, its JSON compared compacted.
|
||||
func SameAsk(a, b Ask) bool {
|
||||
return a.V == b.V && a.Asking == b.Asking && bytes.Equal(compacted(a.Ask), compacted(b.Ask))
|
||||
}
|
||||
|
||||
func compacted(raw json.RawMessage) []byte {
|
||||
var b bytes.Buffer
|
||||
if json.Compact(&b, raw) != nil {
|
||||
return raw
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package asks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
// The keys equal what the assigner has always written (the expected strings are the trunk's own output for the same input),
|
||||
// and each reads back to its parts.
|
||||
func TestTheAssignersAskKeysKeepTheirFormsAndTheValueItsRules(t *testing.T) {
|
||||
fp := "src2:" + strings.Repeat("ab", 32)
|
||||
hex := strings.Repeat("ab", 32)
|
||||
digest := "sha256:" + strings.Repeat("cd", 32)
|
||||
hold, _ := HoldAskKey("gitea", fp)
|
||||
restore, _ := RestoreAskKey("n1", "gitea", fp)
|
||||
backup, _ := BackupAskKey("n1", "novox.be", fp)
|
||||
placement, _ := PlacementAskKey("p-20261028t183000-1a2b3c")
|
||||
record, _ := RecordAskKey("n1", "gitea", digest)
|
||||
for key, want := range map[string]struct {
|
||||
got string
|
||||
back AskKey
|
||||
}{
|
||||
"hold": {hold, AskKey{Kind: "hold", Module: "gitea", FP: fp}},
|
||||
"restore": {restore, AskKey{Kind: "restore", Node: "n1", Module: "gitea", FP: fp}},
|
||||
"backup": {backup, AskKey{Kind: "backup", Node: "n1", Module: "novox.be", FP: fp}},
|
||||
"placement": {placement, AskKey{Kind: "placement", Proposal: "p-20261028t183000-1a2b3c"}},
|
||||
"record": {record, AskKey{Kind: "record", Node: "n1", Module: "gitea", Composed: digest}},
|
||||
} {
|
||||
back, err := ParseAnyAskKey(want.got)
|
||||
if err != nil || back != want.back {
|
||||
t.Errorf("%s: ParseAnyAskKey(%q) = %+v, %v; want %+v", key, want.got, back, err, want.back)
|
||||
}
|
||||
}
|
||||
for got, want := range map[string]string{hold: "hold.gitea." + hex, restore: "restore.n1.gitea." + hex, backup: "backup.n1.novox=2ebe." + hex,
|
||||
placement: "placement.p-20261028t183000-1a2b3c", record: "record.n1.gitea." + strings.Repeat("cd", 32)} {
|
||||
if got != want {
|
||||
t.Errorf("key %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
merge, err := ParseAnyAskKey("merge.fix/1=2e2.d106c7950d28")
|
||||
if err != nil || merge.Kind != "merge" || merge.Group != "fix/1.2" || merge.Hash != "d106c7950d28" {
|
||||
t.Errorf("a merge ask key: %+v, %v", merge, err)
|
||||
}
|
||||
for _, bad := range []string{"", "other.x", "hold.gitea", "hold.gitea.src2:" + hex, "restore.n1", "backup.n1.node", "backup.n1.gitea." + hex[:60],
|
||||
"placement.AB", "record.n1.gitea." + hex[:10], "record.n1.node." + hex, "merge.g"} {
|
||||
if _, err := ParseAnyAskKey(bad); err == nil {
|
||||
t.Errorf("ParseAnyAskKey(%q) reads a wrong key", bad)
|
||||
}
|
||||
}
|
||||
if proof, err := AskNeedsProof("merge"); err != nil || !proof {
|
||||
t.Error("a merge ask needs proof")
|
||||
}
|
||||
if proof, err := AskNeedsProof("hold"); err != nil || proof {
|
||||
t.Error("a hold ask needs no proof")
|
||||
}
|
||||
if _, err := AskNeedsProof("view"); err == nil {
|
||||
t.Error("an unknown kind of ask is taken")
|
||||
}
|
||||
|
||||
// The value: the router's ask whole; an ask of a kind that needs proof, a negative asking and no ask are refused.
|
||||
good := Ask{Ask: []byte(`{"id":"x"}`), Asking: 2}
|
||||
data, err := EncodeAsk(hold, good)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeAsk: %v", err)
|
||||
}
|
||||
back, err := DecodeAsk(hold, data)
|
||||
if err != nil || !SameAsk(back, Ask{V: keys.Version, Ask: []byte(` {"id": "x"} `), Asking: 2}) {
|
||||
t.Errorf("DecodeAsk = %+v, %v", back, err)
|
||||
}
|
||||
for name, c := range map[string]struct {
|
||||
key string
|
||||
a Ask
|
||||
}{"a merge ask": {"merge.g.d106c7950d28", good}, "a negative asking": {hold, Ask{Ask: good.Ask, Asking: -1}}, "no ask": {hold, Ask{}},
|
||||
"a null ask": {hold, Ask{Ask: []byte("null")}}, "an unknown kind": {"other.x", good}} {
|
||||
if _, err := EncodeAsk(c.key, c.a); err == nil {
|
||||
t.Errorf("EncodeAsk takes %s", name)
|
||||
}
|
||||
}
|
||||
if _, err := DecodeAsk(hold, []byte(`{"v":2,"ask":{}}`)); !errors.Is(err, keys.ErrUnknownVersion) {
|
||||
t.Errorf("DecodeAsk of another version = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0
|
||||
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
)
|
||||
@@ -158,6 +158,15 @@
|
||||
"MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools",
|
||||
"MESH_VIEW_CALLERS_WANTS": "mesh-grants"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "asks",
|
||||
"kind": "package",
|
||||
"language": "go",
|
||||
"paths": [
|
||||
"asks"
|
||||
],
|
||||
"series": "0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package viewstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||
)
|
||||
|
||||
const viewWord = "view"
|
||||
|
||||
// ViewAnswerKey is the key, in the state answers, of the view's answer to the ask at askKey: `<ask key>.view`. An ask that needs
|
||||
// proof of who answered is refused with its reason: it is answered on the phone (issue 953).
|
||||
func ViewAnswerKey(askKey string) (string, error) {
|
||||
ask, err := asks.ParseAnyAskKey(askKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if proof, _ := asks.AskNeedsProof(ask.Kind); proof {
|
||||
return "", fmt.Errorf("viewstate: the ask %s needs proof of who answered: answer it on the phone (issue 953)", askKey)
|
||||
}
|
||||
return askKey + "." + viewWord, nil
|
||||
}
|
||||
|
||||
// ParseViewAnswerKey is the inverse of ViewAnswerKey: the ask's key. It refuses a key without `.view`, an ask key of a kind that
|
||||
// needs proof, and a key of another state, naming it.
|
||||
func ParseViewAnswerKey(key string) (askKey string, err error) {
|
||||
askKey, ok := strings.CutSuffix(key, "."+viewWord)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("viewstate: %q is not a view answer key, <ask key>.view", key)
|
||||
}
|
||||
if _, err := ViewAnswerKey(askKey); err != nil {
|
||||
return "", fmt.Errorf("viewstate: %q is not a view answer key: %w", key, err)
|
||||
}
|
||||
return askKey, nil
|
||||
}
|
||||
|
||||
// ViewAnswerFits says whether the view's answer a answers the ask at key as it stands now: it refuses an answer to another
|
||||
// asking of the ask (a stale answer to an earlier writing of the same key), naming both. Whether the ask offers the option
|
||||
// chosen, and at the level acknowledge only (an option that authorises is answered on a channel that proves who answered,
|
||||
// never in the view, which proves nobody), the teller reads from the ask through operator-channel's client.
|
||||
func ViewAnswerFits(a ViewAnswer, key string, ask asks.Ask) error {
|
||||
if want := meshdeliveryclient.AskingID(key, ask.Asking); a.Asking != want {
|
||||
return fmt.Errorf("viewstate: the view's answer is to the asking %s, and the ask stands at the asking %s", a.Asking, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,8 @@ module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks v0.0.0
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f
|
||||
)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
@@ -72,6 +74,32 @@ func TestReachValuesDifferingInAnyPartAreNotTheSame(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestViewAnswerRoundTrip(t *testing.T) {
|
||||
t.Run("the key and the asking", func(t *testing.T) {
|
||||
fp := "src2:" + strings.Repeat("ab", 32)
|
||||
hold, _ := asks.HoldAskKey("gitea", fp)
|
||||
key, err := ViewAnswerKey(hold)
|
||||
if err != nil || key != hold+".view" {
|
||||
t.Fatalf("ViewAnswerKey = %q, %v", key, err)
|
||||
}
|
||||
if back, err := ParseViewAnswerKey(key); err != nil || back != hold {
|
||||
t.Errorf("ParseViewAnswerKey = %q, %v", back, err)
|
||||
}
|
||||
for _, bad := range []string{hold, "merge.g.d106c7950d28.view", "other.x.view"} {
|
||||
if _, err := ParseViewAnswerKey(bad); err == nil {
|
||||
t.Errorf("ParseViewAnswerKey(%q) reads a wrong key", bad)
|
||||
}
|
||||
}
|
||||
if _, err := ViewAnswerKey("merge.g.d106c7950d28"); err == nil || !strings.Contains(err.Error(), "proof") {
|
||||
t.Errorf("a merge ask is answered by the view: %v", err)
|
||||
}
|
||||
first := ViewAnswer{Asking: meshdeliveryclient.AskingID(hold, 0), Option: "release"}
|
||||
if err := ViewAnswerFits(first, hold, asks.Ask{}); err != nil {
|
||||
t.Errorf("an answer to the first asking is refused: %v", err)
|
||||
}
|
||||
if err := ViewAnswerFits(first, hold, asks.Ask{Asking: 2}); err == nil {
|
||||
t.Error("an answer to an earlier asking is taken for the later one")
|
||||
}
|
||||
})
|
||||
a := ViewAnswer{Asking: "hold-mesh-mail-aaaa-0123456789ab", Option: "release", Words: "fixed by #12"}
|
||||
data, err := EncodeViewAnswer(a)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user