mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
AgentBBS becomes a trusted Ed25519 issuer for qrypt.chat anonymous accounts. Members mint a signed, single-use qci1 token (per the shared invite contract) that qrypt.chat verifies and redeems. - internal/qryptinvite: Mint / GenerateIssuerKey / ParsePrivateKey + Config (AGENTBBS_QRYPT_* env) with unit tests (independent verify, payload assertions, jti uniqueness, tamper rejection, seed/full key). - store: qrypt_invites table + QryptInviteCount / RecordQryptInvite (per-member quota, enforced in a tx; ErrQuotaExceeded) + test. - plugins/qryptinvite: hub plugin (members only) — checks quota, mints, records, prints token + redeem URL. - cmd/agentbbs: `qrypt-invite <user>` and `qrypt-issuer-keygen` subcommands wired into dispatch. - setup.sh env template + docs/qrypt-invites.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.4 KiB
Go
52 lines
1.4 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestQryptInviteQuota(t *testing.T) {
|
|
st, err := Open(filepath.Join(t.TempDir(), "t.db"))
|
|
if err != nil {
|
|
t.Fatalf("open: %v", err)
|
|
}
|
|
defer st.Close()
|
|
|
|
const quota = 3
|
|
|
|
if n, err := st.QryptInviteCount("alice"); err != nil || n != 0 {
|
|
t.Fatalf("initial count = %d, %v; want 0, nil", n, err)
|
|
}
|
|
|
|
for i := 0; i < quota; i++ {
|
|
jti := "jti-alice-" + string(rune('a'+i))
|
|
if err := st.RecordQryptInvite("alice", jti, quota); err != nil {
|
|
t.Fatalf("record %d: %v", i, err)
|
|
}
|
|
}
|
|
if n, err := st.QryptInviteCount("alice"); err != nil || n != quota {
|
|
t.Fatalf("count after fill = %d, %v; want %d", n, err, quota)
|
|
}
|
|
|
|
// One over the cap must be rejected with ErrQuotaExceeded and not stored.
|
|
if err := st.RecordQryptInvite("alice", "jti-over", quota); !errors.Is(err, ErrQuotaExceeded) {
|
|
t.Fatalf("over-quota err = %v; want ErrQuotaExceeded", err)
|
|
}
|
|
if n, _ := st.QryptInviteCount("alice"); n != quota {
|
|
t.Fatalf("count after rejected insert = %d; want %d", n, quota)
|
|
}
|
|
|
|
// Quotas are per-member: bob is unaffected.
|
|
if err := st.RecordQryptInvite("bob", "jti-bob", quota); err != nil {
|
|
t.Fatalf("bob record: %v", err)
|
|
}
|
|
if n, _ := st.QryptInviteCount("bob"); n != 1 {
|
|
t.Fatalf("bob count = %d; want 1", n)
|
|
}
|
|
|
|
// quota <= 0 means unlimited.
|
|
if err := st.RecordQryptInvite("carol", "jti-carol-1", 0); err != nil {
|
|
t.Fatalf("unlimited record: %v", err)
|
|
}
|
|
}
|