package oltp
import (
"strings"
"testing"
"time"
)
// A CHANGE MADE THROUGH A TOKEN CARRIES THE TOKEN'S ID IN THE SETTINGS
// LOG; A PERSON'S OWN CHANGE CARRIES ”.
func TestSettingsLogRecordsTheTokenAChangeCameInOn(t *testing.T) {
db := testDB(t)
if err := db.AddRepo("proj", t.TempDir(), "d", Public); err != nil {
t.Fatal(err)
}
if _, err := db.PatchRepo("proj", RepoPatch{Description: str("by token")}, "bot", "tok123"); err != nil {
t.Fatal(err)
}
if _, err := db.PatchRepo("proj", RepoPatch{Topics: str("a")}, "dmestas", ""); err != nil {
t.Fatal(err)
}
if err := db.SetProtectionBy("proj", "trunk", []string{"bot"}, "bot", "tok123"); err != nil {
t.Fatal(err)
}
rows, err := db.SettingsLog("proj", 10)
if err != nil {
t.Fatal(err)
}
got := map[string][2]string{}
for _, r := range rows {
got[r.Field] = [2]string{r.Actor, r.Via}
}
for field, want := range map[string][2]string{
"description": {"bot", "tok123"},
"topics": {"dmestas", ""},
"protect:trunk": {"bot", "tok123"},
} {
if got[field] != want {
t.Errorf("FAIL: %s logged (actor, via) = %v, want %v", field, got[field], want)
}
}
}
// THE THIRTY-FIRST SIGN-IN LINK IN AN HOUR IS REFUSED, PER TOKEN.
func TestTheThirtyFirstSigninLinkInAnHourIsRefused(t *testing.T) {
db, acct, tok := signinDB(t, ScopeWeb)
for i := 0; i < SigninLinksPerHour; i++ {
if _, _, err := db.MintSigninLink(acct, tok.ID, ""); err != nil {
t.Fatalf("FAIL: link %d refused: %v", i+1, err)
}
}
if _, _, err := db.MintSigninLink(acct, tok.ID, ""); err == nil {
t.Errorf("FAIL: link %d was minted", SigninLinksPerHour+1)
}
// Another token of the same account has its own hour.
_, other, err := db.MintToken(acct, TokenSpec{Name: "second", Scopes: []string{ScopeWeb}, Ceiling: "o"})
if err != nil {
t.Fatal(err)
}
if _, _, err := db.MintSigninLink(acct, other.ID, ""); err != nil {
t.Errorf("FAIL: another token's link was refused: %v", err)
}
// Links older than an hour do not count.
db.w.Exec(`UPDATE signin_link SET created = ? WHERE token_id = ?`, time.Now().Add(-2*time.Hour).UTC().Format(time.RFC3339), tok.ID)
if _, _, err := db.MintSigninLink(acct, tok.ID, ""); err != nil {
t.Errorf("FAIL: an hour on, a link was still refused: %v", err)
}
}
// A TOKEN ID RESOLVES TO ITS NAME; A RUN TOKEN'S TO ITS RUN AND STARTER.
func TestTokenLabelResolvesAnIdToWhatItIs(t *testing.T) {
db := runDB(t)
if err := db.AddAccount("bot", "pw-bot-12345", false); err != nil {
t.Fatal(err)
}
_, acct, err := db.MintToken("bot", TokenSpec{Name: "release-2026q4", Scopes: []string{ScopeAPI}})
if err != nil {
t.Fatal(err)
}
if got := db.TokenLabel(acct.ID); got != "release-2026q4" {
t.Errorf("FAIL: account token label = %q", got)
}
if err := db.RecordCIRunStart("r1", "push", "dmestas", nil); err != nil {
t.Fatal(err)
}
_, _, run := mintRun(t, db, "r1", "priv", "publish", "y")
if got := db.TokenLabel(run.ID); got != "workflow, run r1, started by dmestas" {
t.Errorf("FAIL: run token label = %q", got)
}
if got := db.TokenLabel("nosuchid"); got != "" {
t.Errorf("FAIL: unknown id label = %q, want empty", got)
}
}
// A TOKEN'S CI-SECRET WRITE STORES THE BARE ACCOUNT BESIDE THE SECRET
// AND THE TOKEN ID IN ITS OWN COLUMN OF THE SETTINGS LOG.
func TestASecretWrittenThroughATokenNamesTheBareAccount(t *testing.T) {
d := testDB(t)
if err := d.SetEnvironment("alpha", "production", nil, "bot", "T9"); err != nil {
t.Fatal(err)
}
if err := d.SetSecret("alpha", "production", "TOKEN", []byte("sealed"), "bot", "T9"); err != nil {
t.Fatal(err)
}
envs, err := d.Environments("alpha")
if err != nil {
t.Fatal(err)
}
var by string
for _, e := range envs {
for _, s := range e.Secrets {
by = s.UpdatedBy
}
}
if by != "bot" {
t.Errorf("FAIL: updatedBy = %q, want the bare account", by)
}
rows, err := d.SettingsLog("alpha", 10)
if err != nil {
t.Fatal(err)
}
seen := 0
for _, r := range rows {
if strings.Contains(r.Actor, "\x1f") || r.Actor != "bot" || r.Via != "T9" {
t.Errorf("FAIL: %s logged (actor, via) = (%q, %q), want (bot, T9)", r.Field, r.Actor, r.Via)
}
seen++
}
if seen == 0 {
t.Error("FAIL: the writes left no settings log row")
}
}