|
9 | 9 | package catalog_test |
10 | 10 |
|
11 | 11 | import ( |
| 12 | + "context" |
| 13 | + "net/http" |
| 14 | + "net/http/httptest" |
| 15 | + "strings" |
12 | 16 | "testing" |
13 | 17 |
|
14 | 18 | "github.com/raoptimus/db-migrator.go/internal/helper/dsn" |
@@ -88,6 +92,72 @@ func TestNew_DSNParsing(t *testing.T) { |
88 | 92 | } |
89 | 93 | } |
90 | 94 |
|
| 95 | +// brokenHeadCatalog emulates an Iceberg REST server (older apache/iceberg-rest builds on |
| 96 | +// JdbcCatalog) that does NOT implement HEAD /v1/namespaces/{ns} and rejects it with 400 for |
| 97 | +// any namespace, while GET works correctly. NamespaceExists must rely on GET only, so that a |
| 98 | +// missing namespace yields (false, nil) — not a bad-request error — even though HEAD returns 400. |
| 99 | +func brokenHeadCatalog(t *testing.T) *httptest.Server { |
| 100 | + t.Helper() |
| 101 | + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 102 | + w.Header().Set("Content-Type", "application/json") |
| 103 | + |
| 104 | + // HEAD on any namespace is unsupported and rejected with 400. |
| 105 | + if r.Method == http.MethodHead && strings.HasPrefix(r.URL.Path, "/v1/namespaces/") { |
| 106 | + w.WriteHeader(http.StatusBadRequest) |
| 107 | + return |
| 108 | + } |
| 109 | + |
| 110 | + switch r.URL.Path { |
| 111 | + case "/v1/config": |
| 112 | + w.WriteHeader(http.StatusOK) |
| 113 | + _, _ = w.Write([]byte(`{"defaults":{},"overrides":{}}`)) |
| 114 | + case "/v1/namespaces/exists": |
| 115 | + w.WriteHeader(http.StatusOK) |
| 116 | + _, _ = w.Write([]byte(`{"namespace":["exists"],"properties":{}}`)) |
| 117 | + case "/v1/namespaces/missing": |
| 118 | + w.WriteHeader(http.StatusNotFound) |
| 119 | + _, _ = w.Write([]byte(`{"error":{"message":"namespace does not exist",` + |
| 120 | + `"type":"NoSuchNamespaceException","code":404}}`)) |
| 121 | + case "/v1/namespaces/boom": |
| 122 | + w.WriteHeader(http.StatusInternalServerError) |
| 123 | + _, _ = w.Write([]byte(`{"error":{"message":"boom","type":"ServerError","code":500}}`)) |
| 124 | + default: |
| 125 | + w.WriteHeader(http.StatusNotFound) |
| 126 | + _, _ = w.Write([]byte(`{"error":{"message":"not found","type":"NotFound","code":404}}`)) |
| 127 | + } |
| 128 | + })) |
| 129 | +} |
| 130 | + |
| 131 | +// TestNamespaceExists_HeadUnsupported is a regression test for the dev bug where db-migrator up |
| 132 | +// died with "check namespace exists: REST error: bad request" against a REST server that rejects |
| 133 | +// HEAD /v1/namespaces/{ns} with 400. NamespaceExists must probe via GET, so it works despite HEAD |
| 134 | +// returning 400 — the passing test proves HEAD is no longer used. |
| 135 | +func TestNamespaceExists_HeadUnsupported(t *testing.T) { |
| 136 | + ts := brokenHeadCatalog(t) |
| 137 | + defer ts.Close() |
| 138 | + |
| 139 | + parsed, err := dsn.Parse("iceberg://" + strings.TrimPrefix(ts.URL, "http://") + "/warehouse") |
| 140 | + require.NoError(t, err) |
| 141 | + |
| 142 | + client, err := catalog.New(parsed) |
| 143 | + require.NoError(t, err) |
| 144 | + require.NotNil(t, client) |
| 145 | + |
| 146 | + ctx := context.Background() |
| 147 | + |
| 148 | + exists, err := client.NamespaceExists(ctx, []string{"exists"}) |
| 149 | + require.NoError(t, err) |
| 150 | + assert.True(t, exists, "existing namespace must be reported as existing") |
| 151 | + |
| 152 | + exists, err = client.NamespaceExists(ctx, []string{"missing"}) |
| 153 | + require.NoError(t, err, "missing namespace must not surface HEAD 400 as an error") |
| 154 | + assert.False(t, exists, "missing namespace must be reported as not existing") |
| 155 | + |
| 156 | + _, err = client.NamespaceExists(ctx, []string{"boom"}) |
| 157 | + require.Error(t, err, "a non-404 error on GET must be propagated") |
| 158 | + assert.Contains(t, err.Error(), "check namespace exists") |
| 159 | +} |
| 160 | + |
91 | 161 | // TestNew_InvalidOAuth2ServerURI verifies that a malformed oauth2_server_uri returns an error. |
92 | 162 | func TestNew_InvalidOAuth2ServerURI(t *testing.T) { |
93 | 163 | parsed, err := dsn.Parse("iceberg://localhost:8181/warehouse?credential=c:s&oauth2_server_uri=://bad") |
|
0 commit comments