package beads
import (
"context"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
)
// The two readings of the string "NULL", and the mask that tells them apart.
//
// browse renders a cell that holds no value as the text "NULL", which is what a
// row storing those four characters renders as too. Flattening both to "" — as
// this package did before the mask existed — is right for the first and wrong
// for the second: an issue titled "NULL" arrived with an empty title, and no
// projection could tell that it had one.
// nullMaskFixture is one issues table carrying both readings in the same
// columns: i-absent holds no value in title or closed_at, i-stored stores the
// text "NULL" in both.
func nullMaskFixture() *fakeSession {
issues := &browse.RowPage{
Columns: []string{"id", "title", "status", "priority", "issue_type", "assignee", "created_at", "closed_at", "is_blocked"},
Rows: [][]string{
{"i-absent", "NULL", "open", "1", "task", "alice", "2024-01-01", "NULL", "0"},
{"i-stored", "NULL", "open", "1", "task", "alice", "2024-01-02", "NULL", "0"},
},
Nulls: [][]bool{
// title and closed_at hold no value at all.
{false, true, false, false, false, false, false, true, false},
// every cell holds a value; the two "NULL"s are stored text.
{false, false, false, false, false, false, false, false, false},
},
Total: 2,
}
deps := &browse.RowPage{
Columns: []string{"id", "issue_id", "depends_on_issue_id", "type"},
Rows: [][]string{},
Nulls: [][]bool{},
}
return &fakeSession{rowsByTable: map[string]*browse.RowPage{
"issues": issues,
"dependencies": deps,
}}
}
// cell is where the distinction is actually made, so it is asked directly first:
// the same string, read two ways, decided by the mask beside it.
func TestCellReadsTheNullMaskAndNotTheString(t *testing.T) {
cols := indexCols([]string{"id", "title"})
absent := rowCells{values: []string{"i-absent", "NULL"}, nulls: []bool{false, true}}
stored := rowCells{values: []string{"i-stored", "NULL"}, nulls: []bool{false, false}}
assert.Equal(t, "", cell(cols, absent, "title"),
"a cell that holds no value reads as empty, which every projection depends on")
assert.Equal(t, "NULL", cell(cols, stored, "title"),
"a cell that stores the text \"NULL\" has a value, and it is that text")
assert.Equal(t, "", cell(cols, stored, "nosuchcolumn"), "an absent column is still empty")
}
// A page that carries no mask at all cannot answer the question, and the reading
// that predates the mask is all there is: "NULL" reads as absent. Every page
// browse returns carries one, so this is about a page built by hand.
func TestCellWithoutAMaskKeepsTheOlderReading(t *testing.T) {
cols := indexCols([]string{"id", "title"})
unmasked := rowCells{values: []string{"i-1", "NULL"}}
assert.Equal(t, "", cell(cols, unmasked, "title"))
assert.Equal(t, "i-1", cell(cols, unmasked, "id"))
}
// rowsOf pairs each row with its own mask, and a page with none hands out rows
// that answer "unknown" rather than rows that answer "not null".
func TestRowsOfPairsEveryRowWithItsMask(t *testing.T) {
page := &browse.RowPage{
Columns: []string{"id", "title"},
Rows: [][]string{{"a", "NULL"}, {"b", "NULL"}},
Nulls: [][]bool{{false, true}, {false, false}},
}
rows := rowsOf(page)
require.Len(t, rows, 2)
assert.Equal(t, []bool{false, true}, rows[0].nulls)
assert.Equal(t, []bool{false, false}, rows[1].nulls)
unmasked := rowsOf(&browse.RowPage{Columns: []string{"id"}, Rows: [][]string{{"a"}}})
require.Len(t, unmasked, 1)
assert.Nil(t, unmasked[0].nulls, "a page with no mask answers no mask, not an all-false one")
assert.Empty(t, rowsOf(nil), "a table that is absent has no rows to pair")
}
// The projections are where it is felt: a title that is literally "NULL" must
// survive to the detail pane, and a closed_at that holds no value must keep
// reading as empty.
func TestProjectionsKeepAStoredNullAndDropARealOne(t *testing.T) {
ctx := context.Background()
stored, err := Build(ctx, nullMaskFixture(), "main", url.Values{"issue": {"i-stored"}})
require.NoError(t, err)
require.NotNil(t, stored.Issue)
assert.Equal(t, "NULL", stored.Issue.Title,
"the row stores those four characters: an empty title would lose them")
assert.Equal(t, "NULL", stored.Issue.ClosedAt)
absent, err := Build(ctx, nullMaskFixture(), "main", url.Values{"issue": {"i-absent"}})
require.NoError(t, err)
require.NotNil(t, absent.Issue)
assert.Equal(t, "", absent.Issue.Title, "no value is no text, as every projection reads it")
assert.Equal(t, "", absent.Issue.ClosedAt)
}
// The board goes through the same rows, and the same two readings have to reach
// the cards.
func TestBoardCardsKeepAStoredNullTitle(t *testing.T) {
d, err := Build(context.Background(), nullMaskFixture(), "main", url.Values{})
require.NoError(t, err)
titles := map[string]string{}
for _, lane := range d.Lanes {
for _, c := range lane.Issues {
titles[c.ID] = c.Title
}
}
assert.Equal(t, "NULL", titles["i-stored"])
assert.Equal(t, "", titles["i-absent"])
}