diff --git a/build/openapi3gen/convert_test.go b/build/openapi3gen/convert_test.go index 1cf73bd1f0e..28d5413421d 100644 --- a/build/openapi3gen/convert_test.go +++ b/build/openapi3gen/convert_test.go @@ -42,27 +42,19 @@ func TestDeriveEnumName_miss(t *testing.T) { func TestExtractSharedEnums_usesASTMap(t *testing.T) { doc := &openapi3.T{ Components: &openapi3.Components{ - Schemas: openapi3.Schemas{ - "A": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green", "blue"}, - }}, - }, - }}, - "B": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green", "blue"}, - }}, - }, + Schemas: openapi3.Schemas{}, + }, + } + for _, name := range []string{"A", "B"} { + doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "color": {Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"red", "green", "blue"}, }}, }, - }, + }} } astMap := map[string][]string{EnumKey([]any{"red", "green", "blue"}): {"Color"}} if err := extractSharedEnums(doc, astMap); err != nil { @@ -190,27 +182,19 @@ func TestExtractEnumTypeName_rejectsIncidentalPrefix(t *testing.T) { func TestExtractSharedEnums_missReturnsError(t *testing.T) { doc := &openapi3.T{ Components: &openapi3.Components{ - Schemas: openapi3.Schemas{ - "A": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green"}, - }}, - }, - }}, - "B": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"object"}, - Properties: openapi3.Schemas{ - "color": {Value: &openapi3.Schema{ - Type: &openapi3.Types{"string"}, - Enum: []any{"red", "green"}, - }}, - }, + Schemas: openapi3.Schemas{}, + }, + } + for _, name := range []string{"A", "B"} { + doc.Components.Schemas[name] = &openapi3.SchemaRef{Value: &openapi3.Schema{ + Type: &openapi3.Types{"object"}, + Properties: openapi3.Schemas{ + "color": {Value: &openapi3.Schema{ + Type: &openapi3.Types{"string"}, + Enum: []any{"red", "green"}, }}, }, - }, + }} } if err := extractSharedEnums(doc, map[string][]string{}); err == nil { t.Fatal("expected miss error") diff --git a/build/openapi3gen/enumscan_test.go b/build/openapi3gen/enumscan_test.go index 8cb16b721d1..439a3251746 100644 --- a/build/openapi3gen/enumscan_test.go +++ b/build/openapi3gen/enumscan_test.go @@ -33,9 +33,17 @@ func TestEnumKey_handlesNonStringValues(t *testing.T) { } } -func TestScanSwaggerEnumTypes_basic(t *testing.T) { - dir := t.TempDir() - src := `package fixture +func TestScanSwaggerEnumTypes(t *testing.T) { + scan := func(t *testing.T, filename, src string) (map[string][]string, error) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, filename), []byte(src), 0o644); err != nil { + t.Fatal(err) + } + return ScanSwaggerEnumTypes([]string{dir}) + } + + t.Run("basic", func(t *testing.T) { + got, err := scan(t, "color.go", `package fixture // Color is a primary color. // swagger:enum Color @@ -46,24 +54,18 @@ const ( ColorGreen Color = "green" ColorBlue Color = "blue" ) -` - if err := os.WriteFile(filepath.Join(dir, "color.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } +`) + if err != nil { + t.Fatalf("ScanSwaggerEnumTypes: %v", err) + } + wantKey := EnumKey([]any{"red", "green", "blue"}) + if single(got, wantKey) != "Color" { + t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color") + } + }) - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - wantKey := EnumKey([]any{"red", "green", "blue"}) - if single(got, wantKey) != "Color" { - t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Color") - } -} - -func TestScanSwaggerEnumTypes_orphanAnnotation(t *testing.T) { - dir := t.TempDir() - src := `package fixture + t.Run("orphanAnnotation", func(t *testing.T) { + _, err := scan(t, "typo.go", `package fixture // swagger:enum Sttype type StateType string @@ -71,23 +73,17 @@ type StateType string const ( StateOpen StateType = "open" ) -` - if err := os.WriteFile(filepath.Join(dir, "typo.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } +`) + if err == nil { + t.Fatal("expected error for annotation referencing a non-matching type name") + } + if !strings.Contains(err.Error(), "Sttype") { + t.Fatalf("error %q should mention the typo'd name Sttype", err.Error()) + } + }) - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected error for annotation referencing a non-matching type name") - } - if !strings.Contains(err.Error(), "Sttype") { - t.Fatalf("error %q should mention the typo'd name Sttype", err.Error()) - } -} - -func TestScanSwaggerEnumTypes_collision(t *testing.T) { - dir := t.TempDir() - src := `package fixture + t.Run("collision", func(t *testing.T) { + got, err := scan(t, "dup.go", `package fixture // swagger:enum Alpha type Alpha string @@ -102,52 +98,90 @@ const ( BetaX Beta = "x" BetaY Beta = "y" ) -` - if err := os.WriteFile(filepath.Join(dir, "dup.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } +`) + if err != nil { + t.Fatalf("ScanSwaggerEnumTypes: %v", err) + } + key := EnumKey([]any{"x", "y"}) + names := got[key] + if !slices.Equal(names, []string{"Alpha", "Beta"}) { + t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names) + } + }) - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - key := EnumKey([]any{"x", "y"}) - names := got[key] - if !slices.Equal(names, []string{"Alpha", "Beta"}) { - t.Fatalf("map[%q] = %v, want [Alpha Beta]", key, names) - } -} + t.Run("parseFailure", func(t *testing.T) { + _, err := scan(t, "bad.go", "package fixture\nfunc Foo() {") + if err == nil { + t.Fatal("expected parse error, got nil") + } + }) -func TestScanSwaggerEnumTypes_parseFailure(t *testing.T) { - dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "bad.go"), []byte("package fixture\nfunc Foo() {"), 0o644); err != nil { - t.Fatal(err) - } - - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected parse error, got nil") - } -} - -func TestScanSwaggerEnumTypes_annotationWithoutConsts(t *testing.T) { - dir := t.TempDir() - src := `package fixture + t.Run("annotationWithoutConsts", func(t *testing.T) { + _, err := scan(t, "lonely.go", `package fixture // swagger:enum Lonely type Lonely string -` - if err := os.WriteFile(filepath.Join(dir, "lonely.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } +`) + if err == nil { + t.Fatal("expected error for annotation without consts") + } + if !strings.Contains(err.Error(), "Lonely") { + t.Fatalf("error %q should mention Lonely", err.Error()) + } + }) - _, err := ScanSwaggerEnumTypes([]string{dir}) - if err == nil { - t.Fatal("expected error for annotation without consts") - } - if !strings.Contains(err.Error(), "Lonely") { - t.Fatalf("error %q should mention Lonely", err.Error()) - } + t.Run("constsBeforeType", func(t *testing.T) { + got, err := scan(t, "shade.go", `package fixture + +const ( + ShadeDark Shade = "dark" + ShadeLight Shade = "light" +) + +// swagger:enum Shade +type Shade string +`) + if err != nil { + t.Fatalf("ScanSwaggerEnumTypes: %v", err) + } + wantKey := EnumKey([]any{"dark", "light"}) + if single(got, wantKey) != "Shade" { + t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade") + } + }) + + t.Run("groupedTypeDecl", func(t *testing.T) { + got, err := scan(t, "grouped.go", `package fixture + +type ( + // swagger:enum Color + Color string + // swagger:enum Shade + Shade string +) + +const ( + ColorRed Color = "red" + ColorBlue Color = "blue" +) + +const ( + ShadeDark Shade = "dark" + ShadeLight Shade = "light" +) +`) + if err != nil { + t.Fatalf("ScanSwaggerEnumTypes: %v", err) + } + colorKey := EnumKey([]any{"red", "blue"}) + shadeKey := EnumKey([]any{"dark", "light"}) + if single(got, colorKey) != "Color" { + t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color") + } + if single(got, shadeKey) != "Shade" { + t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade") + } + }) } func TestScanSwaggerEnumTypes_constsAndTypeInDifferentFiles(t *testing.T) { @@ -182,68 +216,3 @@ type Hue string t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Hue") } } - -func TestScanSwaggerEnumTypes_constsBeforeType(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -const ( - ShadeDark Shade = "dark" - ShadeLight Shade = "light" -) - -// swagger:enum Shade -type Shade string -` - if err := os.WriteFile(filepath.Join(dir, "shade.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - wantKey := EnumKey([]any{"dark", "light"}) - if single(got, wantKey) != "Shade" { - t.Fatalf("map[%q] = %q, want %q", wantKey, got[wantKey], "Shade") - } -} - -func TestScanSwaggerEnumTypes_groupedTypeDecl(t *testing.T) { - dir := t.TempDir() - src := `package fixture - -type ( - // swagger:enum Color - Color string - // swagger:enum Shade - Shade string -) - -const ( - ColorRed Color = "red" - ColorBlue Color = "blue" -) - -const ( - ShadeDark Shade = "dark" - ShadeLight Shade = "light" -) -` - if err := os.WriteFile(filepath.Join(dir, "grouped.go"), []byte(src), 0o644); err != nil { - t.Fatal(err) - } - - got, err := ScanSwaggerEnumTypes([]string{dir}) - if err != nil { - t.Fatalf("ScanSwaggerEnumTypes: %v", err) - } - colorKey := EnumKey([]any{"red", "blue"}) - shadeKey := EnumKey([]any{"dark", "light"}) - if single(got, colorKey) != "Color" { - t.Fatalf("Color: map[%q] = %q, want %q", colorKey, got[colorKey], "Color") - } - if single(got, shadeKey) != "Shade" { - t.Fatalf("Shade: map[%q] = %q, want %q", shadeKey, got[shadeKey], "Shade") - } -} diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index f9b5338ba0e..6614502eab7 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -489,13 +489,20 @@ func TestUpdateLdapBindDn(t *testing.T) { defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() // Test cases - cases := []struct { + type testCase struct { args []string id int64 existingAuthSource *auth.Source authSource *auth.Source errMsg string - }{ + } + withConfig := func(config *ldap.Source, args ...string) testCase { + return testCase{ + args: append([]string{"ldap-test", "--id", "1"}, args...), + authSource: &auth.Source{Type: auth.LDAP, Cfg: config}, + } + } + cases := []testCase{ // case 0 { args: []string{ @@ -574,16 +581,7 @@ func TestUpdateLdapBindDn(t *testing.T) { }, }, // case 1 - { - args: []string{ - "ldap-test", - "--id", "1", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{}, - }, - }, + withConfig(&ldap.Source{}), // case 2 { args: []string{ @@ -618,215 +616,44 @@ func TestUpdateLdapBindDn(t *testing.T) { }, }, // case 4 - { - args: []string{ - "ldap-test", - "--id", "1", - "--security-protocol", "LDAPS", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - SecurityProtocol: ldap.SecurityProtocol(1), - }, - }, - }, + withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(1)}, "--security-protocol", "LDAPS"), // case 5 - { - args: []string{ - "ldap-test", - "--id", "1", - "--skip-tls-verify", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - SkipVerify: true, - }, - }, - }, + withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"), // case 6 - { - args: []string{ - "ldap-test", - "--id", "1", - "--host", "ldap-server", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - Host: "ldap-server", - }, - }, - }, + withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"), // case 7 - { - args: []string{ - "ldap-test", - "--id", "1", - "--port", "389", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - Port: 389, - }, - }, - }, + withConfig(&ldap.Source{Port: 389}, "--port", "389"), // case 8 - { - args: []string{ - "ldap-test", - "--id", "1", - "--user-search-base", "ou=Users,dc=domain,dc=org", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - UserBase: "ou=Users,dc=domain,dc=org", - }, - }, - }, + withConfig( + &ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"}, + "--user-search-base", "ou=Users,dc=domain,dc=org", + ), // case 9 - { - args: []string{ - "ldap-test", - "--id", "1", - "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", - }, - }, - }, + withConfig( + &ldap.Source{Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)"}, + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + ), // case 10 - { - args: []string{ - "ldap-test", - "--id", "1", - "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", - }, - }, - }, + withConfig( + &ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"}, + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + ), // case 11 - { - args: []string{ - "ldap-test", - "--id", "1", - "--username-attribute", "uid", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AttributeUsername: "uid", - }, - }, - }, + withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"), // case 12 - { - args: []string{ - "ldap-test", - "--id", "1", - "--firstname-attribute", "givenName", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AttributeName: "givenName", - }, - }, - }, + withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"), // case 13 - { - args: []string{ - "ldap-test", - "--id", "1", - "--surname-attribute", "sn", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AttributeSurname: "sn", - }, - }, - }, + withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"), // case 14 - { - args: []string{ - "ldap-test", - "--id", "1", - "--email-attribute", "mail", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AttributeMail: "mail", - }, - }, - }, + withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"), // case 15 - { - args: []string{ - "ldap-test", - "--id", "1", - "--attributes-in-bind", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AttributesInBind: true, - }, - }, - }, + withConfig(&ldap.Source{AttributesInBind: true}, "--attributes-in-bind"), // case 16 - { - args: []string{ - "ldap-test", - "--id", "1", - "--public-ssh-key-attribute", "publickey", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - AttributeSSHPublicKey: "publickey", - }, - }, - }, + withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"), // case 17 - { - args: []string{ - "ldap-test", - "--id", "1", - "--bind-dn", "cn=readonly,dc=domain,dc=org", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - BindDN: "cn=readonly,dc=domain,dc=org", - }, - }, - }, + withConfig(&ldap.Source{BindDN: "cn=readonly,dc=domain,dc=org"}, "--bind-dn", "cn=readonly,dc=domain,dc=org"), // case 18 - { - args: []string{ - "ldap-test", - "--id", "1", - "--bind-password", "secret", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - BindPassword: "secret", - }, - }, - }, + withConfig(&ldap.Source{BindPassword: "secret"}, "--bind-password", "secret"), // case 19 { args: []string{ @@ -841,19 +668,7 @@ func TestUpdateLdapBindDn(t *testing.T) { }, }, // case 20 - { - args: []string{ - "ldap-test", - "--id", "1", - "--page-size", "12", - }, - authSource: &auth.Source{ - Type: auth.LDAP, - Cfg: &ldap.Source{ - SearchPageSize: 12, - }, - }, - }, + withConfig(&ldap.Source{SearchPageSize: 12}, "--page-size", "12"), // case 21 { args: []string{ @@ -964,13 +779,20 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { defer test.MockVariableValue(&cli.OsExiter, func(code int) {})() // Test cases - cases := []struct { + type testCase struct { args []string id int64 existingAuthSource *auth.Source authSource *auth.Source errMsg string - }{ + } + withConfig := func(config *ldap.Source, args ...string) testCase { + return testCase{ + args: append([]string{"ldap-test", "--id", "1"}, args...), + authSource: &auth.Source{Type: auth.DLDAP, Cfg: config}, + } + } + cases := []testCase{ // case 0 { args: []string{ @@ -1020,16 +842,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { }, }, // case 1 - { - args: []string{ - "ldap-test", - "--id", "1", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{}, - }, - }, + withConfig(&ldap.Source{}), // case 2 { args: []string{ @@ -1064,187 +877,43 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { }, }, // case 4 - { - args: []string{ - "ldap-test", - "--id", "1", - "--security-protocol", "starttls", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - SecurityProtocol: ldap.SecurityProtocol(2), - }, - }, - }, + withConfig(&ldap.Source{SecurityProtocol: ldap.SecurityProtocol(2)}, "--security-protocol", "starttls"), // case 5 - { - args: []string{ - "ldap-test", - "--id", "1", - "--skip-tls-verify", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - SkipVerify: true, - }, - }, - }, + withConfig(&ldap.Source{SkipVerify: true}, "--skip-tls-verify"), // case 6 - { - args: []string{ - "ldap-test", - "--id", "1", - "--host", "ldap-server", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - Host: "ldap-server", - }, - }, - }, + withConfig(&ldap.Source{Host: "ldap-server"}, "--host", "ldap-server"), // case 7 - { - args: []string{ - "ldap-test", - "--id", "1", - "--port", "987", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - Port: 987, - }, - }, - }, + withConfig(&ldap.Source{Port: 987}, "--port", "987"), // case 8 - { - args: []string{ - "ldap-test", - "--id", "1", - "--user-search-base", "ou=Users,dc=domain,dc=org", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - UserBase: "ou=Users,dc=domain,dc=org", - }, - }, - }, + withConfig( + &ldap.Source{UserBase: "ou=Users,dc=domain,dc=org"}, + "--user-search-base", "ou=Users,dc=domain,dc=org", + ), // case 9 - { - args: []string{ - "ldap-test", - "--id", "1", - "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - Filter: "(&(objectClass=posixAccount)(cn=%s))", - }, - }, - }, + withConfig( + &ldap.Source{Filter: "(&(objectClass=posixAccount)(cn=%s))"}, + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + ), // case 10 - { - args: []string{ - "ldap-test", - "--id", "1", - "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", - }, - }, - }, + withConfig( + &ldap.Source{AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)"}, + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + ), // case 11 - { - args: []string{ - "ldap-test", - "--id", "1", - "--username-attribute", "uid", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - AttributeUsername: "uid", - }, - }, - }, + withConfig(&ldap.Source{AttributeUsername: "uid"}, "--username-attribute", "uid"), // case 12 - { - args: []string{ - "ldap-test", - "--id", "1", - "--firstname-attribute", "givenName", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - AttributeName: "givenName", - }, - }, - }, + withConfig(&ldap.Source{AttributeName: "givenName"}, "--firstname-attribute", "givenName"), // case 13 - { - args: []string{ - "ldap-test", - "--id", "1", - "--surname-attribute", "sn", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - AttributeSurname: "sn", - }, - }, - }, + withConfig(&ldap.Source{AttributeSurname: "sn"}, "--surname-attribute", "sn"), // case 14 - { - args: []string{ - "ldap-test", - "--id", "1", - "--email-attribute", "mail", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - AttributeMail: "mail", - }, - }, - }, + withConfig(&ldap.Source{AttributeMail: "mail"}, "--email-attribute", "mail"), // case 15 - { - args: []string{ - "ldap-test", - "--id", "1", - "--public-ssh-key-attribute", "publickey", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - AttributeSSHPublicKey: "publickey", - }, - }, - }, + withConfig(&ldap.Source{AttributeSSHPublicKey: "publickey"}, "--public-ssh-key-attribute", "publickey"), // case 16 - { - args: []string{ - "ldap-test", - "--id", "1", - "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", - }, - authSource: &auth.Source{ - Type: auth.DLDAP, - Cfg: &ldap.Source{ - UserDN: "cn=%s,ou=Users,dc=domain,dc=org", - }, - }, - }, + withConfig( + &ldap.Source{UserDN: "cn=%s,ou=Users,dc=domain,dc=org"}, + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + ), // case 17 { args: []string{ diff --git a/cmd/admin_auth_oauth_test.go b/cmd/admin_auth_oauth_test.go index c16dd96d565..332e30243f0 100644 --- a/cmd/admin_auth_oauth_test.go +++ b/cmd/admin_auth_oauth_test.go @@ -16,10 +16,11 @@ import ( func TestAddOauth(t *testing.T) { testCases := []struct { - name string - args []string - source *auth_model.Source - errMsg string + name string + args []string + config *oauth2.Source + twoFactorPolicy string + errMsg string }{ { name: "valid config", @@ -29,17 +30,11 @@ func TestAddOauth(t *testing.T) { "--key", "some_key", "--secret", "some_secret", }, - source: &auth_model.Source{ - Type: auth_model.OAuth2, - Name: "test", - IsActive: true, - Cfg: &oauth2.Source{ - Scopes: []string{}, - Provider: "github", - ClientID: "some_key", - ClientSecret: "some_secret", - }, - TwoFactorPolicy: "", + config: &oauth2.Source{ + Scopes: []string{}, + Provider: "github", + ClientID: "some_key", + ClientSecret: "some_secret", }, }, { @@ -51,18 +46,12 @@ func TestAddOauth(t *testing.T) { "--secret", "some_secret", "--auto-discover-url", "https://example.com", }, - source: &auth_model.Source{ - Type: auth_model.OAuth2, - Name: "test", - IsActive: true, - Cfg: &oauth2.Source{ - Scopes: []string{}, - Provider: "openidConnect", - ClientID: "some_key", - ClientSecret: "some_secret", - OpenIDConnectAutoDiscoveryURL: "https://example.com", - }, - TwoFactorPolicy: "", + config: &oauth2.Source{ + Scopes: []string{}, + Provider: "openidConnect", + ClientID: "some_key", + ClientSecret: "some_secret", + OpenIDConnectAutoDiscoveryURL: "https://example.com", }, }, { @@ -91,35 +80,30 @@ func TestAddOauth(t *testing.T) { "--ssh-public-key-claim-name", "attr_ssh_pub_key", "--full-name-claim-name", "attr_full_name", }, - source: &auth_model.Source{ - Type: auth_model.OAuth2, - Name: "test", - IsActive: true, - Cfg: &oauth2.Source{ - Provider: "gitlab", - ClientID: "some_key", - ClientSecret: "some_secret", - CustomURLMapping: &oauth2.CustomURLMapping{ - TokenURL: "https://example.com/token", - AuthURL: "https://example.com/auth", - ProfileURL: "https://example.com/profile", - EmailURL: "https://example.com/email", - Tenant: "some_tenant", - }, - IconURL: "https://example.com/icon", - Scopes: []string{"scope1", "scope2"}, - RequiredClaimName: "claim_name", - RequiredClaimValue: "claim_value", - GroupClaimName: "group_name", - AdminGroup: "admin", - RestrictedGroup: "restricted", - GroupTeamMap: `{"group1": [1,2]}`, - GroupTeamMapRemoval: true, - SSHPublicKeyClaimName: "attr_ssh_pub_key", - FullNameClaimName: "attr_full_name", + config: &oauth2.Source{ + Provider: "gitlab", + ClientID: "some_key", + ClientSecret: "some_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://example.com/token", + AuthURL: "https://example.com/auth", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "some_tenant", }, - TwoFactorPolicy: "skip", + IconURL: "https://example.com/icon", + Scopes: []string{"scope1", "scope2"}, + RequiredClaimName: "claim_name", + RequiredClaimValue: "claim_value", + GroupClaimName: "group_name", + AdminGroup: "admin", + RestrictedGroup: "restricted", + GroupTeamMap: `{"group1": [1,2]}`, + GroupTeamMapRemoval: true, + SSHPublicKeyClaimName: "attr_ssh_pub_key", + FullNameClaimName: "attr_full_name", }, + twoFactorPolicy: "skip", }, } @@ -150,7 +134,13 @@ func TestAddOauth(t *testing.T) { assert.EqualError(t, err, tc.errMsg) } else { assert.NoError(t, err) - assert.Equal(t, tc.source, createdSource) + assert.Equal(t, &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: tc.config, + TwoFactorPolicy: tc.twoFactorPolicy, + }, createdSource) } }) } @@ -158,12 +148,10 @@ func TestAddOauth(t *testing.T) { func TestUpdateOauth(t *testing.T) { testCases := []struct { - name string - args []string - id int64 - existingAuthSource *auth_model.Source - authSource *auth_model.Source - errMsg string + name string + args []string + authSource *auth_model.Source + errMsg string }{ { name: "missing id", @@ -174,19 +162,6 @@ func TestUpdateOauth(t *testing.T) { }, { name: "valid config", - id: 1, - existingAuthSource: &auth_model.Source{ - ID: 1, - Type: auth_model.OAuth2, - Name: "old name", - IsActive: true, - Cfg: &oauth2.Source{ - Provider: "github", - ClientID: "old_key", - ClientSecret: "old_secret", - }, - TwoFactorPolicy: "", - }, args: []string{ "--id", "1", "--name", "test", @@ -210,37 +185,6 @@ func TestUpdateOauth(t *testing.T) { }, { name: "valid config with options", - id: 1, - existingAuthSource: &auth_model.Source{ - ID: 1, - Type: auth_model.OAuth2, - Name: "old name", - IsActive: true, - Cfg: &oauth2.Source{ - Provider: "gitlab", - ClientID: "old_key", - ClientSecret: "old_secret", - CustomURLMapping: &oauth2.CustomURLMapping{ - TokenURL: "https://old.example.com/token", - AuthURL: "https://old.example.com/auth", - ProfileURL: "https://old.example.com/profile", - EmailURL: "https://old.example.com/email", - Tenant: "old_tenant", - }, - IconURL: "https://old.example.com/icon", - Scopes: []string{"old_scope1", "old_scope2"}, - RequiredClaimName: "old_claim_name", - RequiredClaimValue: "old_claim_value", - GroupClaimName: "old_group_name", - AdminGroup: "old_admin", - RestrictedGroup: "old_restricted", - GroupTeamMap: `{"old_group1": [1,2]}`, - GroupTeamMapRemoval: true, - SSHPublicKeyClaimName: "old_ssh_pub_key", - FullNameClaimName: "old_full_name", - }, - TwoFactorPolicy: "", - }, args: []string{ "--id", "1", "--name", "test", diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go index 2b890f154aa..a1cb79e69aa 100644 --- a/cmd/admin_auth_smtp_test.go +++ b/cmd/admin_auth_smtp_test.go @@ -132,11 +132,10 @@ func TestAddSMTP(t *testing.T) { func TestUpdateSMTP(t *testing.T) { testCases := []struct { - name string - args []string - existingAuthSource *auth_model.Source - authSource *auth_model.Source - errMsg string + name string + args []string + authSource *auth_model.Source + errMsg string }{ { name: "missing id", @@ -149,17 +148,6 @@ func TestUpdateSMTP(t *testing.T) { }, { name: "valid config", - existingAuthSource: &auth_model.Source{ - ID: 1, - Type: auth_model.SMTP, - Name: "old name", - IsActive: true, - Cfg: &smtp.Source{ - Auth: "PLAIN", - Host: "old host", - Port: 26, - }, - }, args: []string{ "--id", "1", "--name", "test", @@ -180,20 +168,6 @@ func TestUpdateSMTP(t *testing.T) { }, { name: "valid config with options", - existingAuthSource: &auth_model.Source{ - ID: 1, - Type: auth_model.SMTP, - Name: "old name", - IsActive: true, - Cfg: &smtp.Source{ - Auth: "PLAIN", - Host: "old host", - Port: 26, - HeloHostname: "old.example.com", - AllowedDomains: "old.example.com", - }, - TwoFactorPolicy: "", - }, args: []string{ "--id", "1", "--name", "test", diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go index 4fbd0435c51..6d61b010ff1 100644 --- a/cmd/admin_user_delete_test.go +++ b/cmd/admin_user_delete_test.go @@ -24,42 +24,29 @@ func TestAdminUserDelete(t *testing.T) { require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{})) }() - setupTestUser := func(t *testing.T) { - unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) - err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) - require.NoError(t, err) + for _, tc := range []struct { + name string + args []string + }{ + {"delete user by id", []string{"delete-test", "--id", ""}}, + {"delete user by username", []string{"delete-test", "--username", "testuser"}}, + {"delete user by email", []string{"delete-test", "--email", "testuser@gitea.local"}}, + {"delete user by all 3 attributes", []string{"delete", "--id", "", "--username", "testuser", "--email", "testuser@gitea.local"}}, + } { + t.Run(tc.name, func(t *testing.T) { + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + + if tc.args[1] == "--id" { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + tc.args[2] = strconv.FormatInt(user.ID, 10) + } + err = microcmdUserDelete().Run(ctx, tc.args) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) } - - t.Run("delete user by id", func(t *testing.T) { - setupTestUser(t) - - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)}) - require.NoError(t, err) - unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) - }) - t.Run("delete user by username", func(t *testing.T) { - setupTestUser(t) - - err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"}) - require.NoError(t, err) - unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) - }) - t.Run("delete user by email", func(t *testing.T) { - setupTestUser(t) - - err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"}) - require.NoError(t, err) - unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) - }) - t.Run("delete user by all 3 attributes", func(t *testing.T) { - setupTestUser(t) - - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"}) - require.NoError(t, err) - unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) - }) } func TestAdminUserDeleteFailure(t *testing.T) { diff --git a/cmd/admin_user_must_change_password_test.go b/cmd/admin_user_must_change_password_test.go index cf81fbe0ba7..f5544dca0fa 100644 --- a/cmd/admin_user_must_change_password_test.go +++ b/cmd/admin_user_must_change_password_test.go @@ -18,61 +18,28 @@ func TestMustChangePassword(t *testing.T) { defer func() { require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{})) }() - err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) - require.NoError(t, err) - err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"}) - require.NoError(t, err) - // Reset password change flag - err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"}) - require.NoError(t, err) - - testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - assert.False(t, testUser.MustChangePassword) - testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) - assert.False(t, testUserExclude.MustChangePassword) - - // Make all users change password - err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"}) - require.NoError(t, err) - - testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - assert.True(t, testUser.MustChangePassword) - testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) - assert.True(t, testUserExclude.MustChangePassword) - - // Reset password change flag but exclude all tested users - err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}) - require.NoError(t, err) - - testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - assert.True(t, testUser.MustChangePassword) - testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) - assert.True(t, testUserExclude.MustChangePassword) - - // Reset password change flag by listing multiple users - err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"}) - require.NoError(t, err) - - testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - assert.False(t, testUser.MustChangePassword) - testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) - assert.False(t, testUserExclude.MustChangePassword) - - // Exclude a user from all user - err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"}) - require.NoError(t, err) - - testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - assert.True(t, testUser.MustChangePassword) - testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) - assert.False(t, testUserExclude.MustChangePassword) - - // Unset a flag for single user - err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"}) - require.NoError(t, err) - - testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - assert.False(t, testUser.MustChangePassword) - testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) - assert.False(t, testUserExclude.MustChangePassword) + for _, name := range []string{"testuser", "testuserexclude"} { + require.NoError(t, microcmdUserCreate().Run(t.Context(), []string{"create", "--username", name, "--email", name + "@gitea.local", "--random-password"})) + } + for _, tc := range []struct { + args []string + assertUser, assertExcludedUser assert.BoolAssertionFunc + }{ + // Reset password change flag + {[]string{"change-test", "--all", "--unset"}, assert.False, assert.False}, + // Make all users change password + {[]string{"change-test", "--all"}, assert.True, assert.True}, + // Reset password change flag but exclude all tested users + {[]string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}, assert.True, assert.True}, + // Reset password change flag by listing multiple users + {[]string{"change-test", "--unset", "testuser", "testuserexclude"}, assert.False, assert.False}, + // Exclude a user from all user + {[]string{"change-test", "--all", "--exclude", "testuserexclude"}, assert.True, assert.False}, + // Unset a flag for single user + {[]string{"change-test", "--unset", "testuser"}, assert.False, assert.False}, + } { + require.NoError(t, microcmdUserMustChangePassword().Run(t.Context(), tc.args)) + tc.assertUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}).MustChangePassword) + tc.assertExcludedUser(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}).MustChangePassword) + } } diff --git a/cmd/cmdtest/cmd_test.go b/cmd/cmdtest/cmd_test.go index f41793c8e44..557eef2aa1e 100644 --- a/cmd/cmdtest/cmd_test.go +++ b/cmd/cmdtest/cmd_test.go @@ -76,110 +76,82 @@ func TestCliCmd(t *testing.T) { cli.SubcommandHelpTemplate = "(subcommand help template)" cases := []struct { - env map[string]string - cmd string - exp string + env map[string]string + cmds []string + exp string }{ // help commands { - cmd: "./gitea -h", - exp: "DEFAULT CONFIGURATION:", + cmds: []string{"./gitea -h", "./gitea help"}, + exp: "DEFAULT CONFIGURATION:", }, { - cmd: "./gitea help", - exp: "DEFAULT CONFIGURATION:", - }, - - { - cmd: "./gitea -c /dev/null -h", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea help -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null test-cmd -h", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -c /dev/null -h", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -h -c /dev/null", - exp: "ConfigFile: /dev/null", - }, - - { - cmd: "./gitea -c /dev/null test-cmd help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd -c /dev/null help", - exp: "ConfigFile: /dev/null", - }, - { - cmd: "./gitea test-cmd help -c /dev/null", + cmds: []string{ + "./gitea -c /dev/null -h", + "./gitea -c /dev/null help", + "./gitea help -c /dev/null", + "./gitea -c /dev/null test-cmd -h", + "./gitea test-cmd -c /dev/null -h", + "./gitea test-cmd -h -c /dev/null", + "./gitea -c /dev/null test-cmd help", + "./gitea test-cmd -c /dev/null help", + "./gitea test-cmd help -c /dev/null", + }, exp: "ConfigFile: /dev/null", }, // parse paths { - cmd: "./gitea test-cmd", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf), + cmds: []string{"./gitea test-cmd"}, + exp: makePathOutput(defaultWorkPath, defaultCustomPath, defaultCustomConf), }, { - cmd: "./gitea -c /tmp/app.ini test-cmd", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), + cmds: []string{"./gitea -c /tmp/app.ini test-cmd"}, + exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), }, { - cmd: "./gitea test-cmd -c /tmp/app.ini", - exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), + cmds: []string{"./gitea test-cmd -c /tmp/app.ini"}, + exp: makePathOutput(defaultWorkPath, defaultCustomPath, "/tmp/app.ini"), }, { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd", - exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"), + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmds: []string{"./gitea test-cmd"}, + exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/custom/conf/app.ini"), }, { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd --work-path /tmp/other", - exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"), + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmds: []string{"./gitea test-cmd --work-path /tmp/other"}, + exp: makePathOutput("/tmp/other", "/tmp/other/custom", "/tmp/other/custom/conf/app.ini"), }, { - env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, - cmd: "./gitea test-cmd --config /tmp/app-other.ini", - exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"), + env: map[string]string{"GITEA_WORK_DIR": "/tmp"}, + cmds: []string{"./gitea test-cmd --config /tmp/app-other.ini"}, + exp: makePathOutput("/tmp", "/tmp/custom", "/tmp/app-other.ini"), }, } for _, c := range cases { - t.Run(c.cmd, func(t *testing.T) { - app := newTestApp(cli.Command{ - Action: func(ctx context.Context, cmd *cli.Command) error { - _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) - return nil - }, + for _, command := range c.cmds { + t.Run(command, func(t *testing.T) { + app := newTestApp(cli.Command{ + Action: func(ctx context.Context, cmd *cli.Command) error { + _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + return nil + }, + }) + for k, v := range c.env { + t.Setenv(k, v) + } + args := strings.Split(command, " ") // for test only, "split" is good enough + r, err := runTestApp(app, args...) + assert.NoError(t, err, command) + assert.NotEmpty(t, c.exp, command) + if !assert.Contains(t, r.Stdout, c.exp, command) { + t.Log("Full output:\n" + r.Stdout) + t.Log("Expected:\n" + c.exp) + } }) - for k, v := range c.env { - t.Setenv(k, v) - } - args := strings.Split(c.cmd, " ") // for test only, "split" is good enough - r, err := runTestApp(app, args...) - assert.NoError(t, err, c.cmd) - assert.NotEmpty(t, c.exp, c.cmd) - if !assert.Contains(t, r.Stdout, c.exp, c.cmd) { - t.Log("Full output:\n" + r.Stdout) - t.Log("Expected:\n" + c.exp) - } - }) + } } } diff --git a/modelmigration/base/db_test.go b/modelmigration/base/db_test.go index eb5a212a76c..5a8a0984f92 100644 --- a/modelmigration/base/db_test.go +++ b/modelmigration/base/db_test.go @@ -45,38 +45,15 @@ func Test_DropTableColumns(t *testing.T) { x.SetMapper(names.GonicMapper{}) for i := range columns { - if err := x.Sync(new(DropTest)); err != nil { - t.Errorf("unable to create DropTest table: %v", err) - return - } - - sess := x.NewSession() - if err := sess.Begin(); err != nil { - sess.Close() - t.Errorf("unable to begin transaction: %v", err) - return - } - if err := base.DropTableColumns(sess, "drop_test", columns[i:]...); err != nil { - sess.Close() - t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err) - return - } - if err := sess.Commit(); err != nil { - sess.Close() - t.Errorf("unable to commit transaction: %v", err) - return - } - sess.Close() - if err := x.DropTables(new(DropTest)); err != nil { - t.Errorf("unable to drop table: %v", err) - return - } - for j := range columns[i+1:] { + for j := i; j < len(columns); j++ { if err := x.Sync(new(DropTest)); err != nil { t.Errorf("unable to create DropTest table: %v", err) return } - dropcols := append([]string{columns[i]}, columns[j+i+1:]...) + dropcols := columns[i:] + if j > i { + dropcols = append([]string{columns[i]}, columns[j:]...) + } sess := x.NewSession() if err := sess.Begin(); err != nil { sess.Close() @@ -85,7 +62,11 @@ func Test_DropTableColumns(t *testing.T) { } if err := base.DropTableColumns(sess, "drop_test", dropcols...); err != nil { sess.Close() - t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err) + if j == i { + t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err) + } else { + t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err) + } return } if err := sess.Commit(); err != nil { diff --git a/modelmigration/v1_20/v259_test.go b/modelmigration/v1_20/v259_test.go index a3f3ee78c19..b657c51fdd5 100644 --- a/modelmigration/v1_20/v259_test.go +++ b/modelmigration/v1_20/v259_test.go @@ -78,12 +78,17 @@ func Test_ConvertScopedAccessTokens(t *testing.T) { assert.NoError(t, err) assert.Equal(t, int64(0), count) + session := x.NewSession() + defer session.Close() + assert.NoError(t, session.Begin()) + for _, tc := range tests { - _, err = x.Insert(&AccessToken{ + _, err = session.Insert(&AccessToken{ Scope: string(tc.Old), }) assert.NoError(t, err) } + assert.NoError(t, session.Commit()) // migrate the scopes err = ConvertScopedAccessTokens(t.Context(), x) diff --git a/modelmigration/v1_26/v329_test.go b/modelmigration/v1_26/v329_test.go index d627030ebe2..0083c9ab5c7 100644 --- a/modelmigration/v1_26/v329_test.go +++ b/modelmigration/v1_26/v329_test.go @@ -43,13 +43,11 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) { } // check that we have duplicates - count, err := x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{}) - assert.NoError(t, err) - assert.Equal(t, int64(2), count) - - count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{}) - assert.NoError(t, err) - assert.Equal(t, int64(2), count) + for _, userID := range []int{1, 3} { + count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(2), count) + } totalCount, err := x.Count(&UserBadgeBefore{}) assert.NoError(t, err) @@ -62,13 +60,11 @@ func Test_AddUniqueIndexForUserBadge(t *testing.T) { } // verify the duplicates were removed - count, err = x.Where("user_id = ? AND badge_id = ?", 1, 1).Count(&UserBadgeBefore{}) - assert.NoError(t, err) - assert.Equal(t, int64(1), count) - - count, err = x.Where("user_id = ? AND badge_id = ?", 3, 3).Count(&UserBadgeBefore{}) - assert.NoError(t, err) - assert.Equal(t, int64(1), count) + for _, userID := range []int{1, 3} { + count, err := x.Where("user_id = ? AND badge_id = ?", userID, userID).Count(&UserBadgeBefore{}) + assert.NoError(t, err) + assert.Equal(t, int64(1), count) + } // check total count totalCount, err = x.Count(&UserBadgeBefore{}) diff --git a/models/actions/run_job_status_test.go b/models/actions/run_job_status_test.go index 90cb4afd436..738fe300a0b 100644 --- a/models/actions/run_job_status_test.go +++ b/models/actions/run_job_status_test.go @@ -10,22 +10,6 @@ import ( ) func TestAggregateJobStatus(t *testing.T) { - testStatuses := func(expected Status, statuses []Status) { - t.Helper() - var jobs []*ActionRunJob - for _, v := range statuses { - jobs = append(jobs, &ActionRunJob{Status: v}) - } - actual := AggregateJobStatus(jobs) - if !assert.Equal(t, expected, actual) { - var statusStrings []string - for _, s := range statuses { - statusStrings = append(statusStrings, s.String()) - } - t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[expected]) - } - } - cases := []struct { statuses []Status expected Status @@ -92,6 +76,17 @@ func TestAggregateJobStatus(t *testing.T) { } for _, c := range cases { - testStatuses(c.expected, c.statuses) + var jobs []*ActionRunJob + for _, status := range c.statuses { + jobs = append(jobs, &ActionRunJob{Status: status}) + } + actual := AggregateJobStatus(jobs) + if !assert.Equal(t, c.expected, actual) { + var statusStrings []string + for _, status := range c.statuses { + statusStrings = append(statusStrings, status.String()) + } + t.Errorf("AggregateJobStatus(%v) = %v, want %v", statusStrings, statusNames[actual], statusNames[c.expected]) + } } } diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go index 9ab0babee24..743d7de479e 100644 --- a/models/actions/run_job_test.go +++ b/models/actions/run_job_test.go @@ -16,189 +16,267 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetPriorAttemptChildrenByParent(t *testing.T) { +func TestRunJobDatabase(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() - // 3 attempts of one run: - // 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary". - // 2: caller skipped, no children rows. - // 3: placeholder "current" attempt for the walkback subtest. + t.Run("prior attempt children by parent", func(t *testing.T) { + ctx := t.Context() - run := &ActionRun{ - Title: "prior-children-test", - RepoID: 4, - Index: 9501, - OwnerID: 1, - WorkflowID: "matrix.yaml", - TriggerUserID: 1, - Ref: "refs/heads/master", - CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", - Event: "push", - TriggerEvent: "push", - EventPayload: "{}", - Status: StatusSuccess, - } - require.NoError(t, db.Insert(ctx, run)) + // 3 attempts of one run: + // 1: caller expanded with 3 matrix instances of "work" + non-matrix sibling "summary". + // 2: caller skipped, no children rows. + // 3: placeholder "current" attempt for the walkback subtest. - const callerAttemptJobID int64 = 9001 - insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt { - t.Helper() - a := &ActionRunAttempt{ - RepoID: run.RepoID, - RunID: run.ID, - Attempt: num, + run := &ActionRun{ + Title: "prior-children-test", + RepoID: 4, + Index: 9501, + OwnerID: 1, + WorkflowID: "matrix.yaml", TriggerUserID: 1, - Status: status, + Ref: "refs/heads/master", + CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", + Event: "push", + TriggerEvent: "push", + EventPayload: "{}", + Status: StatusSuccess, } - require.NoError(t, db.Insert(ctx, a)) - return a - } - insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob { - t.Helper() - caller := &ActionRunJob{ - RunID: run.ID, - RunAttemptID: attemptID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: "caller", - JobID: "caller", - Attempt: 1, - Status: status, - AttemptJobID: callerAttemptJobID, - IsReusableCaller: true, - IsExpanded: expanded, + require.NoError(t, db.Insert(ctx, run)) + + const callerAttemptJobID int64 = 9001 + insertAttempt := func(t *testing.T, num int64, status Status) *ActionRunAttempt { + t.Helper() + a := &ActionRunAttempt{ + RepoID: run.RepoID, + RunID: run.ID, + Attempt: num, + TriggerUserID: 1, + Status: status, + } + require.NoError(t, db.Insert(ctx, a)) + return a + } + insertCaller := func(t *testing.T, attemptID int64, status Status, expanded bool) *ActionRunJob { + t.Helper() + caller := &ActionRunJob{ + RunID: run.ID, + RunAttemptID: attemptID, + RepoID: run.RepoID, + OwnerID: run.OwnerID, + CommitSHA: run.CommitSHA, + Name: "caller", + JobID: "caller", + Attempt: 1, + Status: status, + AttemptJobID: callerAttemptJobID, + IsReusableCaller: true, + IsExpanded: expanded, + } + require.NoError(t, db.Insert(ctx, caller)) + return caller + } + insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) { + t.Helper() + require.NoError(t, db.Insert(ctx, &ActionRunJob{ + RunID: run.ID, + RunAttemptID: attemptID, + RepoID: run.RepoID, + OwnerID: run.OwnerID, + CommitSHA: run.CommitSHA, + Name: name, + JobID: jobID, + Attempt: 1, + Status: StatusSuccess, + AttemptJobID: attemptJobID, + ParentJobID: parentID, + })) } - require.NoError(t, db.Insert(ctx, caller)) - return caller - } - insertChild := func(t *testing.T, attemptID, parentID, attemptJobID int64, name, jobID string) { - t.Helper() - require.NoError(t, db.Insert(ctx, &ActionRunJob{ - RunID: run.ID, - RunAttemptID: attemptID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: name, - JobID: jobID, - Attempt: 1, - Status: StatusSuccess, - AttemptJobID: attemptJobID, - ParentJobID: parentID, - })) - } - attempt1 := insertAttempt(t, 1, StatusSuccess) - caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true) - insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work") - insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work") - insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work") - insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary") + attempt1 := insertAttempt(t, 1, StatusSuccess) + caller1 := insertCaller(t, attempt1.ID, StatusSuccess, true) + insertChild(t, attempt1.ID, caller1.ID, 101, "work (alpha)", "work") + insertChild(t, attempt1.ID, caller1.ID, 102, "work (beta)", "work") + insertChild(t, attempt1.ID, caller1.ID, 103, "work (gamma)", "work") + insertChild(t, attempt1.ID, caller1.ID, 104, "summary", "summary") - attempt2 := insertAttempt(t, 2, StatusSkipped) - insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally + attempt2 := insertAttempt(t, 2, StatusSkipped) + insertCaller(t, attempt2.ID, StatusSkipped, false) // no children intentionally - // both subtests expect attempt 1's expansion, differing only in the "current" attempt id - assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) { - t.Helper() - // outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1 - assert.Len(t, out, 2) - assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry") - assert.Len(t, out["summary"], 1) + // both subtests expect attempt 1's expansion, differing only in the "current" attempt id + assertAttempt1Children := func(t *testing.T, out map[string]map[string]*ActionRunJob) { + t.Helper() + // outer map keyed by JobID: "work" has 3 matrix instances, "summary" 1 + assert.Len(t, out, 2) + assert.Len(t, out["work"], 3, "matrix instances must each get their own inner-map entry") + assert.Len(t, out["summary"], 1) - require.NotNil(t, out["work"]["work (alpha)"]) - require.NotNil(t, out["work"]["work (beta)"]) - require.NotNil(t, out["work"]["work (gamma)"]) - require.NotNil(t, out["summary"]["summary"]) + require.NotNil(t, out["work"]["work (alpha)"]) + require.NotNil(t, out["work"]["work (beta)"]) + require.NotNil(t, out["work"]["work (gamma)"]) + require.NotNil(t, out["summary"]["summary"]) - assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID) - assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID) - assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID) - assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID) - } + assert.Equal(t, int64(101), out["work"]["work (alpha)"].AttemptJobID) + assert.Equal(t, int64(102), out["work"]["work (beta)"].AttemptJobID) + assert.Equal(t, int64(103), out["work"]["work (gamma)"].AttemptJobID) + assert.Equal(t, int64(104), out["summary"]["summary"].AttemptJobID) + } - t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) { - // "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt. - out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID) - require.NoError(t, err) - assertAttempt1Children(t, out) + t.Run("matrix instances and non-matrix sibling are indexed by (JobID, Name)", func(t *testing.T) { + // "current" = attempt 2; prior = attempt 1, which is the immediately preceding attempt. + out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt2.ID, callerAttemptJobID) + require.NoError(t, err) + assertAttempt1Children(t, out) + }) + + t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) { + attempt3 := insertAttempt(t, 3, StatusRunning) + // "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1. + out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID) + require.NoError(t, err) + assertAttempt1Children(t, out) + }) }) - t.Run("walkback past an attempt where the caller had no children", func(t *testing.T) { - attempt3 := insertAttempt(t, 3, StatusRunning) - // "current" = attempt 3; the immediately preceding attempt 2 has no children, so the lookup must walk further back to attempt 1. - out, err := GetPriorAttemptChildrenByParent(ctx, run.ID, attempt3.ID, callerAttemptJobID) - require.NoError(t, err) - assertAttempt1Children(t, out) - }) -} + t.Run("cancel reusable callers", func(t *testing.T) { + for _, tc := range []struct { + name, title, workflowID string + index int64 + status Status + }{ + {"NestedBlockedReusableCaller", "cancel-nested-caller", "caller.yaml", 9701, StatusBlocked}, + {"CallerWaitsForCancellingChild", "caller-cancelling-child", "test.yaml", 9811, StatusRunning}, + } { + t.Run(tc.name, func(t *testing.T) { + ctx := t.Context() -// A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled. -func TestCancelJobs_NestedBlockedReusableCaller(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() + run := &ActionRun{ + Title: tc.title, + RepoID: 4, + Index: tc.index, + OwnerID: 1, + WorkflowID: tc.workflowID, + TriggerUserID: 1, + Ref: "refs/heads/master", + CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", + Event: "push", + TriggerEvent: "push", + EventPayload: "{}", + Status: tc.status, + } + require.NoError(t, db.Insert(ctx, run)) - run := &ActionRun{ - Title: "cancel-nested-caller", - RepoID: 4, - Index: 9701, - OwnerID: 1, - WorkflowID: "caller.yaml", - TriggerUserID: 1, - Ref: "refs/heads/master", - CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", - Event: "push", - TriggerEvent: "push", - EventPayload: "{}", - Status: StatusBlocked, - } - require.NoError(t, db.Insert(ctx, run)) + attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: tc.status} + require.NoError(t, db.Insert(ctx, attempt)) + run.LatestAttemptID = attempt.ID + require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id")) - attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusBlocked} - require.NoError(t, db.Insert(ctx, attempt)) - run.LatestAttemptID = attempt.ID - require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id")) + newJob := func(job *ActionRunJob) *ActionRunJob { + job.RunID = run.ID + job.RunAttemptID = attempt.ID + job.RepoID = run.RepoID + job.OwnerID = run.OwnerID + job.CommitSHA = run.CommitSHA + job.JobID = job.Name + job.Attempt = 1 + job.Status = tc.status + require.NoError(t, db.Insert(ctx, job)) + return job + } - newJob := func(name string, attemptJobID, parentID int64, callUses string) *ActionRunJob { - job := &ActionRunJob{ - RunID: run.ID, - RunAttemptID: attempt.ID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: name, - JobID: name, - Attempt: 1, - Status: StatusBlocked, - AttemptJobID: attemptJobID, - IsReusableCaller: true, - CallUses: callUses, - ParentJobID: parentID, + // A reusable caller subtree with a Blocked descendant (e.g. a nested caller stuck on an invalid `uses:`) must aggregate to Cancelled, when the run is cancelled. + if tc.status == StatusBlocked { + // outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands). + outer := newJob(&ActionRunJob{Name: "outer", AttemptJobID: 1, IsReusableCaller: true, CallUses: "./.gitea/workflows/lib.yml"}) + inner := newJob(&ActionRunJob{Name: "inner", AttemptJobID: 2, ParentJobID: outer.ID, IsReusableCaller: true, CallUses: "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1"}) + + // Cancel all jobs of the attempt, ordered by id (parent before child). + jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID) + require.NoError(t, err) + _, err = CancelJobs(ctx, jobs, false) + require.NoError(t, err) + + for _, j := range []*ActionRunJob{outer, inner} { + got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID}) + assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID) + } + gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID}) + assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled") + gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}) + assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked") + } else { + outer := newJob(&ActionRunJob{Name: "outer", IsReusableCaller: true, IsExpanded: true}) + inner := newJob(&ActionRunJob{Name: "inner", ParentJobID: outer.ID, IsReusableCaller: true, IsExpanded: true}) + child := newJob(&ActionRunJob{Name: "child", ParentJobID: inner.ID}) + + runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true} + require.NoError(t, db.Insert(ctx, runner)) + task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA} + require.NoError(t, db.Insert(ctx, task)) + child.TaskID = task.ID + _, err := UpdateRunJob(ctx, child, nil, "task_id") + require.NoError(t, err) + + cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false) + require.NoError(t, err) + assert.Len(t, cancelled, 3) + for _, job := range []*ActionRunJob{outer, inner, child} { + assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name) + } + + _, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED}) + require.NoError(t, err) + for _, job := range []*ActionRunJob{outer, inner, child} { + assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name) + } + assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status) + } + }) } - require.NoError(t, db.Insert(ctx, job)) - return job - } + }) - // outer: a valid top-level caller that expanded; inner: a nested caller stuck Blocked (invalid uses, never expands). - outer := newJob("outer", 1, 0, "./.gitea/workflows/lib.yml") - inner := newJob("inner", 2, outer.ID, "https://other.example.com/o/r/.gitea/workflows/ci.yml@v1") + t.Run("batch load and cancel run jobs", func(t *testing.T) { + ctx := t.Context() - // Cancel all jobs of the attempt, ordered by id (parent before child). - jobs, err := GetRunJobsByRunAndAttemptID(ctx, run.ID, attempt.ID) - require.NoError(t, err) - _, err = CancelJobs(ctx, jobs, false) - require.NoError(t, err) + legacyRun := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9600, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning} + run := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9601, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning} + require.NoError(t, db.Insert(ctx, legacyRun, run)) + require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: legacyRun.ID, RepoID: legacyRun.RepoID, OwnerID: legacyRun.OwnerID})) + oldAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1} + latestAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 2, TriggerUserID: 2} + require.NoError(t, db.Insert(ctx, oldAttempt, latestAttempt)) + for _, attemptID := range []int64{oldAttempt.ID, latestAttempt.ID, latestAttempt.ID} { + require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: run.ID, RunAttemptID: attemptID, RepoID: run.RepoID, OwnerID: run.OwnerID})) + } + run.LatestAttemptID = latestAttempt.ID + require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id")) - for _, j := range []*ActionRunJob{outer, inner} { - got := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: j.ID}) - assert.Equal(t, StatusCancelled, got.Status, "job %q should be cancelled", j.JobID) - } - gotAttempt := unittest.AssertExistsAndLoadBean(t, &ActionRunAttempt{ID: attempt.ID}) - assert.Equal(t, StatusCancelled, gotAttempt.Status, "attempt must aggregate to Cancelled") - gotRun := unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}) - assert.Equal(t, StatusCancelled, gotRun.Status, "run must aggregate to Cancelled, not stay Blocked") + runs := RunList{legacyRun, run} + jobsByRun, err := GetLatestAttemptJobsByRuns(ctx, runs) + require.NoError(t, err) + for _, listedRun := range runs { + expected, err := GetLatestAttemptJobsByRun(ctx, listedRun) + require.NoError(t, err) + require.NotEmpty(t, expected) + assert.Equal(t, expected, jobsByRun[listedRun.ID]) + } + + attempts, err := runs.GetLatestAttempts(ctx) + require.NoError(t, err) + require.Len(t, attempts, 1) + assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID) + + chunkedAttempts := []*ActionRunAttempt{oldAttempt} + for i := range db.DefaultMaxInSize { + chunkedAttempts = append(chunkedAttempts, &ActionRunAttempt{ID: -int64(i + 1)}) + } + attemptJobs, err := getRunJobsByAttempts(ctx, append(chunkedAttempts, latestAttempt)) + require.NoError(t, err) + assert.Len(t, attemptJobs, 3) + + cancelled, err := CancelPreviousJobs(ctx, run.RepoID, run.Ref, "", "") + require.NoError(t, err) + assert.Len(t, cancelled, 4) + }) } func TestSettleRunAfterCancel(t *testing.T) { @@ -398,117 +476,3 @@ func TestForceCancelJobs(t *testing.T) { assert.Equal(t, StatusCancelled, callerAfter.Status) }) } - -func TestCancelJobs_CallerWaitsForCancellingChild(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() - - run := &ActionRun{ - Title: "caller-cancelling-child", - RepoID: 4, - Index: 9811, - OwnerID: 1, - WorkflowID: "test.yaml", - TriggerUserID: 1, - Ref: "refs/heads/master", - CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", - Event: "push", - TriggerEvent: "push", - EventPayload: "{}", - Status: StatusRunning, - } - require.NoError(t, db.Insert(ctx, run)) - attempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1, Status: StatusRunning} - require.NoError(t, db.Insert(ctx, attempt)) - run.LatestAttemptID = attempt.ID - require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id")) - - newJob := func(name string, parentID int64, isCaller bool) *ActionRunJob { - job := &ActionRunJob{ - RunID: run.ID, - RunAttemptID: attempt.ID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: name, - JobID: name, - Attempt: 1, - Status: StatusRunning, - ParentJobID: parentID, - IsReusableCaller: isCaller, - IsExpanded: isCaller, - } - require.NoError(t, db.Insert(ctx, job)) - return job - } - outer := newJob("outer", 0, true) - inner := newJob("inner", outer.ID, true) - child := newJob("child", inner.ID, false) - - runner := &ActionRunner{UUID: "caller-cancelling-child", Name: "caller-cancelling-child", HasCancellingSupport: true} - require.NoError(t, db.Insert(ctx, runner)) - task := &ActionTask{JobID: child.ID, Attempt: 1, RunnerID: runner.ID, Status: StatusRunning, Started: timeutil.TimeStampNow(), RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: run.CommitSHA} - require.NoError(t, db.Insert(ctx, task)) - child.TaskID = task.ID - _, err := UpdateRunJob(ctx, child, nil, "task_id") - require.NoError(t, err) - - cancelled, err := CancelJobs(ctx, []*ActionRunJob{outer}, false) - require.NoError(t, err) - assert.Len(t, cancelled, 3) - for _, job := range []*ActionRunJob{outer, inner, child} { - assert.Equal(t, StatusCancelling, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name) - } - - _, err = UpdateTaskByState(ctx, runner.ID, &runnerv1.TaskState{Id: task.ID, Result: runnerv1.Result_RESULT_CANCELLED}) - require.NoError(t, err) - for _, job := range []*ActionRunJob{outer, inner, child} { - assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}).Status, job.Name) - } - assert.Equal(t, StatusCancelled, unittest.AssertExistsAndLoadBean(t, &ActionRun{ID: run.ID}).Status) -} - -func TestBatchLoadAndCancelRunJobs(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() - - legacyRun := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9600, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning} - run := &ActionRun{RepoID: 4, OwnerID: 1, Index: 9601, TriggerUserID: 1, Ref: "refs/heads/batch", Status: StatusRunning} - require.NoError(t, db.Insert(ctx, legacyRun, run)) - require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: legacyRun.ID, RepoID: legacyRun.RepoID, OwnerID: legacyRun.OwnerID})) - oldAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: 1} - latestAttempt := &ActionRunAttempt{RepoID: run.RepoID, RunID: run.ID, Attempt: 2, TriggerUserID: 2} - require.NoError(t, db.Insert(ctx, oldAttempt, latestAttempt)) - for _, attemptID := range []int64{oldAttempt.ID, latestAttempt.ID, latestAttempt.ID} { - require.NoError(t, db.Insert(ctx, &ActionRunJob{RunID: run.ID, RunAttemptID: attemptID, RepoID: run.RepoID, OwnerID: run.OwnerID})) - } - run.LatestAttemptID = latestAttempt.ID - require.NoError(t, UpdateRun(ctx, run, "latest_attempt_id")) - - runs := RunList{legacyRun, run} - jobsByRun, err := GetLatestAttemptJobsByRuns(ctx, runs) - require.NoError(t, err) - for _, listedRun := range runs { - expected, err := GetLatestAttemptJobsByRun(ctx, listedRun) - require.NoError(t, err) - require.NotEmpty(t, expected) - assert.Equal(t, expected, jobsByRun[listedRun.ID]) - } - - attempts, err := runs.GetLatestAttempts(ctx) - require.NoError(t, err) - require.Len(t, attempts, 1) - assert.EqualValues(t, 2, attempts[latestAttempt.ID].TriggerUser.ID) - - chunkedAttempts := []*ActionRunAttempt{oldAttempt} - for i := range db.DefaultMaxInSize { - chunkedAttempts = append(chunkedAttempts, &ActionRunAttempt{ID: -int64(i + 1)}) - } - attemptJobs, err := getRunJobsByAttempts(ctx, append(chunkedAttempts, latestAttempt)) - require.NoError(t, err) - assert.Len(t, attemptJobs, 3) - - cancelled, err := CancelPreviousJobs(ctx, run.RepoID, run.Ref, "", "") - require.NoError(t, err) - assert.Len(t, cancelled, 4) -} diff --git a/models/actions/runner_test.go b/models/actions/runner_test.go index 3b5d0ea05ea..2a7ede74fce 100644 --- a/models/actions/runner_test.go +++ b/models/actions/runner_test.go @@ -12,72 +12,32 @@ import ( "github.com/stretchr/testify/assert" ) -func TestShouldPersistLastOnline(t *testing.T) { - now := time.Now() - tests := []struct { - name string - last timeutil.TimeStamp - want bool +func TestShouldPersistRunnerTimestamps(t *testing.T) { + for _, scenario := range []struct { + name string + persist func(timeutil.TimeStamp, time.Time) bool + interval time.Duration + freshAge time.Duration }{ - { - name: "fresh, skip write", - last: timeutil.TimeStamp(now.Add(-5 * time.Second).Unix()), - want: false, - }, - { - name: "exactly at interval, write", - last: timeutil.TimeStamp(now.Add(-RunnerHeartbeatInterval).Unix()), - want: true, - }, - { - name: "stale, write", - last: timeutil.TimeStamp(now.Add(-2 * RunnerHeartbeatInterval).Unix()), - want: true, - }, - { - name: "zero (never seen), write", - last: 0, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, ShouldPersistLastOnline(tt.last, now)) - }) - } -} - -func TestShouldPersistLastActive(t *testing.T) { - now := time.Now() - tests := []struct { - name string - last timeutil.TimeStamp - want bool - }{ - { - name: "fresh, skip write", - last: timeutil.TimeStamp(now.Add(-1 * time.Second).Unix()), - want: false, - }, - { - name: "exactly at interval, write", - last: timeutil.TimeStamp(now.Add(-RunnerActiveInterval).Unix()), - want: true, - }, - { - name: "stale, write", - last: timeutil.TimeStamp(now.Add(-2 * RunnerActiveInterval).Unix()), - want: true, - }, - { - name: "zero (never seen), write", - last: 0, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, ShouldPersistLastActive(tt.last, now)) + {"online", ShouldPersistLastOnline, RunnerHeartbeatInterval, 5 * time.Second}, + {"active", ShouldPersistLastActive, RunnerActiveInterval, time.Second}, + } { + t.Run(scenario.name, func(t *testing.T) { + now := time.Now() + for _, tt := range []struct { + name string + last timeutil.TimeStamp + want bool + }{ + {"fresh, skip write", timeutil.TimeStamp(now.Add(-scenario.freshAge).Unix()), false}, + {"exactly at interval, write", timeutil.TimeStamp(now.Add(-scenario.interval).Unix()), true}, + {"stale, write", timeutil.TimeStamp(now.Add(-2 * scenario.interval).Unix()), true}, + {"zero (never seen), write", 0, true}, + } { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, scenario.persist(tt.last, now)) + }) + } }) } } diff --git a/models/actions/status_test.go b/models/actions/status_test.go index 12e1b61e85f..8b2410d68a3 100644 --- a/models/actions/status_test.go +++ b/models/actions/status_test.go @@ -11,41 +11,28 @@ import ( "github.com/stretchr/testify/assert" ) -func TestStatusAsResult(t *testing.T) { +func TestStatusResultConversions(t *testing.T) { cases := []struct { - status Status - want runnerv1.Result + status Status + result runnerv1.Result + fromResult bool }{ - {StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED}, - {StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED}, - {StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED}, - {StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED}, - {StatusSuccess, runnerv1.Result_RESULT_SUCCESS}, - {StatusFailure, runnerv1.Result_RESULT_FAILURE}, - {StatusCancelled, runnerv1.Result_RESULT_CANCELLED}, - {StatusCancelling, runnerv1.Result_RESULT_CANCELLED}, - {StatusSkipped, runnerv1.Result_RESULT_SKIPPED}, + {StatusUnknown, runnerv1.Result_RESULT_UNSPECIFIED, true}, + {StatusWaiting, runnerv1.Result_RESULT_UNSPECIFIED, false}, + {StatusRunning, runnerv1.Result_RESULT_UNSPECIFIED, false}, + {StatusBlocked, runnerv1.Result_RESULT_UNSPECIFIED, false}, + {StatusSuccess, runnerv1.Result_RESULT_SUCCESS, true}, + {StatusFailure, runnerv1.Result_RESULT_FAILURE, true}, + {StatusCancelled, runnerv1.Result_RESULT_CANCELLED, true}, + {StatusCancelling, runnerv1.Result_RESULT_CANCELLED, false}, + {StatusSkipped, runnerv1.Result_RESULT_SKIPPED, true}, } for _, tt := range cases { - assert.Equal(t, tt.want, tt.status.AsResult(), "status=%s", tt.status) - } -} - -func TestStatusFromResult(t *testing.T) { - cases := []struct { - result runnerv1.Result - want Status - }{ - {runnerv1.Result_RESULT_UNSPECIFIED, StatusUnknown}, - {runnerv1.Result_RESULT_SUCCESS, StatusSuccess}, - {runnerv1.Result_RESULT_FAILURE, StatusFailure}, - {runnerv1.Result_RESULT_CANCELLED, StatusCancelled}, - {runnerv1.Result_RESULT_SKIPPED, StatusSkipped}, - } - - for _, tt := range cases { - assert.Equal(t, tt.want, StatusFromResult(tt.result), "result=%s", tt.result) + assert.Equal(t, tt.result, tt.status.AsResult(), "status=%s", tt.status) + if tt.fromResult { + assert.Equal(t, tt.status, StatusFromResult(tt.result), "result=%s", tt.result) + } } } diff --git a/models/actions/task_test.go b/models/actions/task_test.go index 12fa5468777..b8b27a7a58e 100644 --- a/models/actions/task_test.go +++ b/models/actions/task_test.go @@ -241,136 +241,106 @@ func TestStopTaskCancellingKeepsReportTime(t *testing.T) { // its job to the waiting queue and deletes the task and its steps, so a failure // while assembling the runner response cannot strand the job in running state. func TestReleaseTaskForRunner(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) + for _, tt := range []struct { + name string + runTitle string + runIndex int64 + jobName string + batchSize int + }{ + {"release", "release-task-test-run", 9902, "release-job", 0}, + {"pagination", "pagination-test-run", 9903, "target-job", 2}, + } { + t.Run(tt.name, func(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + if tt.batchSize != 0 { + defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize) + pickTaskBatchSize = tt.batchSize + } - run := &ActionRun{ - Title: "release-task-test-run", - RepoID: 1, - OwnerID: 2, - WorkflowID: "test.yaml", - Index: 9902, - TriggerUserID: 2, - Ref: "refs/heads/main", - CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", - Event: "push", - TriggerEvent: "push", - Status: StatusWaiting, + run := &ActionRun{ + Title: tt.runTitle, + RepoID: 1, + OwnerID: 2, + WorkflowID: "test.yaml", + Index: tt.runIndex, + TriggerUserID: 2, + Ref: "refs/heads/main", + CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", + Event: "push", + TriggerEvent: "push", + Status: StatusWaiting, + } + require.NoError(t, db.Insert(t.Context(), run)) + + if tt.batchSize != 0 { + // Five waiting jobs the runner cannot run, then one it can. + // With a page size of 2 the matching job only appears on the third page. + for i := range 5 { + mismatch := &ActionRunJob{ + RunID: run.ID, + RepoID: run.RepoID, + OwnerID: run.OwnerID, + CommitSHA: run.CommitSHA, + Name: "mismatch-" + string(rune('a'+i)), + Attempt: 1, + JobID: "mismatch-" + string(rune('a'+i)), + Status: StatusWaiting, + RunsOn: []string{"windows-latest"}, + } + require.NoError(t, db.Insert(t.Context(), mismatch)) + } + } + + job := &ActionRunJob{ + RunID: run.ID, + RepoID: run.RepoID, + OwnerID: run.OwnerID, + CommitSHA: run.CommitSHA, + Name: tt.jobName, + Attempt: 1, + JobID: tt.jobName, + Status: StatusWaiting, + RunsOn: []string{"ubuntu-latest"}, + WorkflowPayload: []byte("on: push\njobs:\n " + tt.jobName + ":\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"), + } + require.NoError(t, db.Insert(t.Context(), job)) + + runner := &ActionRunner{ + UUID: tt.name + "-runner-uuid", + Name: tt.name + "-runner", + AgentLabels: []string{"ubuntu-latest"}, + } + runner.GenerateAndFillToken() + require.NoError(t, db.Insert(t.Context(), runner)) + + task, ok, err := CreateTaskForRunner(t.Context(), runner) + require.NoError(t, err) + require.True(t, ok) + require.NotNil(t, task) + + claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}) + if tt.batchSize != 0 { + assert.Equal(t, StatusRunning, claimed.Status) + assert.Equal(t, task.ID, claimed.TaskID) + } else { + require.Equal(t, StatusRunning, claimed.Status) + require.Equal(t, task.ID, claimed.TaskID) + + require.NoError(t, ReleaseTaskForRunner(t.Context(), task)) + + // Job is back in the waiting queue with no task assigned. + released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}) + assert.Equal(t, StatusWaiting, released.Status) + assert.Zero(t, released.TaskID) + assert.Zero(t, released.Started) + + // The task and its steps are gone. + unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID}) + unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID}) + } + }) } - require.NoError(t, db.Insert(t.Context(), run)) - - job := &ActionRunJob{ - RunID: run.ID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: "release-job", - Attempt: 1, - JobID: "release-job", - Status: StatusWaiting, - RunsOn: []string{"ubuntu-latest"}, - WorkflowPayload: []byte("on: push\njobs:\n release-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"), - } - require.NoError(t, db.Insert(t.Context(), job)) - - runner := &ActionRunner{ - UUID: "release-runner-uuid", - Name: "release-runner", - AgentLabels: []string{"ubuntu-latest"}, - } - runner.GenerateAndFillToken() - require.NoError(t, db.Insert(t.Context(), runner)) - - task, ok, err := CreateTaskForRunner(t.Context(), runner) - require.NoError(t, err) - require.True(t, ok) - require.NotNil(t, task) - - claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}) - require.Equal(t, StatusRunning, claimed.Status) - require.Equal(t, task.ID, claimed.TaskID) - - require.NoError(t, ReleaseTaskForRunner(t.Context(), task)) - - // Job is back in the waiting queue with no task assigned. - released := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: job.ID}) - assert.Equal(t, StatusWaiting, released.Status) - assert.Zero(t, released.TaskID) - assert.Zero(t, released.Started) - - // The task and its steps are gone. - unittest.AssertNotExistsBean(t, &ActionTask{ID: task.ID}) - unittest.AssertNotExistsBean(t, &ActionTaskStep{TaskID: task.ID}) -} - -// TestCreateTaskForRunnerPagination verifies that a job sitting beyond the first page is still claimed -func TestCreateTaskForRunnerPagination(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - defer func(orig int) { pickTaskBatchSize = orig }(pickTaskBatchSize) - pickTaskBatchSize = 2 - - run := &ActionRun{ - Title: "pagination-test-run", - RepoID: 1, - OwnerID: 2, - WorkflowID: "test.yaml", - Index: 9903, - TriggerUserID: 2, - Ref: "refs/heads/main", - CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", - Event: "push", - TriggerEvent: "push", - Status: StatusWaiting, - } - require.NoError(t, db.Insert(t.Context(), run)) - - // Five waiting jobs the runner cannot run, then one it can. - // With a page size of 2 the matching job only appears on the third page. - for i := range 5 { - mismatch := &ActionRunJob{ - RunID: run.ID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: "mismatch-" + string(rune('a'+i)), - Attempt: 1, - JobID: "mismatch-" + string(rune('a'+i)), - Status: StatusWaiting, - RunsOn: []string{"windows-latest"}, - } - require.NoError(t, db.Insert(t.Context(), mismatch)) - } - - target := &ActionRunJob{ - RunID: run.ID, - RepoID: run.RepoID, - OwnerID: run.OwnerID, - CommitSHA: run.CommitSHA, - Name: "target-job", - Attempt: 1, - JobID: "target-job", - Status: StatusWaiting, - RunsOn: []string{"ubuntu-latest"}, - WorkflowPayload: []byte("on: push\njobs:\n target-job:\n runs-on: ubuntu-latest\n steps:\n - run: echo hi\n"), - } - require.NoError(t, db.Insert(t.Context(), target)) - - runner := &ActionRunner{ - UUID: "pagination-runner-uuid", - Name: "pagination-runner", - AgentLabels: []string{"ubuntu-latest"}, - } - runner.GenerateAndFillToken() - require.NoError(t, db.Insert(t.Context(), runner)) - - task, ok, err := CreateTaskForRunner(t.Context(), runner) - require.NoError(t, err) - require.True(t, ok) - require.NotNil(t, task) - - claimed := unittest.AssertExistsAndLoadBean(t, &ActionRunJob{ID: target.ID}) - assert.Equal(t, StatusRunning, claimed.Status) - assert.Equal(t, task.ID, claimed.TaskID) } type failFirstStepWrite struct{ fired atomic.Bool } diff --git a/models/actions/utils_test.go b/models/actions/utils_test.go index 4ccffa4c0c0..f091628b452 100644 --- a/models/actions/utils_test.go +++ b/models/actions/utils_test.go @@ -15,24 +15,14 @@ import ( ) func TestLogIndexes_ToDB(t *testing.T) { - tests := []struct { - indexes LogIndexes - }{ - { - indexes: []int64{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64}, - }, - } - for _, tt := range tests { - t.Run("", func(t *testing.T) { - got, err := tt.indexes.ToDB() - require.NoError(t, err) + inputIndexes := LogIndexes{1, 2, 0, -1, -2, math.MaxInt64, math.MinInt64} + got, err := inputIndexes.ToDB() + require.NoError(t, err) - indexes := LogIndexes{} - require.NoError(t, indexes.FromDB(got)) + indexes := LogIndexes{} + require.NoError(t, indexes.FromDB(got)) - assert.Equal(t, tt.indexes, indexes) - }) - } + assert.Equal(t, inputIndexes, indexes) } func Test_calculateDuration(t *testing.T) { diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 0a60cd9c781..256b696e891 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -104,19 +104,14 @@ func TestConsistencyUpdateAction(t *testing.T) { // // Get rid of incorrectly set created_unix // - count, err := activities_model.CountActionCreatedUnixString(t.Context()) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - count, err = activities_model.FixActionCreatedUnixString(t.Context()) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - count, err = activities_model.CountActionCreatedUnixString(t.Context()) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) - count, err = activities_model.FixActionCreatedUnixString(t.Context()) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) + for _, wantCount := range []int{1, 0} { + count, err := activities_model.CountActionCreatedUnixString(t.Context()) + assert.NoError(t, err) + assert.EqualValues(t, wantCount, count) + count, err = activities_model.FixActionCreatedUnixString(t.Context()) + assert.NoError(t, err) + assert.EqualValues(t, wantCount, count) + } // // XORM must be happy now diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go index beb404d0351..1273f9c5136 100644 --- a/models/activities/notification_test.go +++ b/models/activities/notification_test.go @@ -196,23 +196,17 @@ func TestNotification_GetIssue(t *testing.T) { func TestGetNotificationCount(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{ - UserID: user.ID, - Status: []activities_model.NotificationStatus{ - activities_model.NotificationStatusRead, - }, - }) - assert.NoError(t, err) - assert.EqualValues(t, 0, cnt) - - cnt, err = db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{ - UserID: user.ID, - Status: []activities_model.NotificationStatus{ - activities_model.NotificationStatusUnread, - }, - }) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) + for count, status := range []activities_model.NotificationStatus{ + activities_model.NotificationStatusRead, + activities_model.NotificationStatusUnread, + } { + cnt, err := db.Count[activities_model.Notification](t.Context(), activities_model.FindNotificationOptions{ + UserID: user.ID, + Status: []activities_model.NotificationStatus{status}, + }) + assert.NoError(t, err) + assert.EqualValues(t, count, cnt) + } } func TestSetNotificationStatus(t *testing.T) { diff --git a/models/asymkey/gpg_key_test.go b/models/asymkey/gpg_key_test.go index 5cce164a78f..5b722d2c6d7 100644 --- a/models/asymkey/gpg_key_test.go +++ b/models/asymkey/gpg_key_test.go @@ -18,43 +18,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestCheckArmoredGPGKeyString(t *testing.T) { - testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK----- - -mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv -z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m -/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1 -vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN -0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac -mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE -IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF -Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY -KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa -MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ -ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+ -sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo -T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i -iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE -QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT -pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU -JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN -/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx -ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02 -cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF -CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH -6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk -lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo -RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP -Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR -MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== -=i9b7 ------END PGP PUBLIC KEY BLOCK-----` - - key, err := CheckArmoredGPGKeyString(testGPGArmor) - assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", key) - // TODO verify value of key -} - func TestCheckArmoredbrainpoolP256r1GPGKeyString(t *testing.T) { testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 @@ -108,6 +71,10 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg== =i9b7 -----END PGP PUBLIC KEY BLOCK-----` keys, err := CheckArmoredGPGKeyString(testGPGArmor) + assert.NoError(t, err, "Could not parse a valid GPG public armored rsa key", keys) + // TODO verify value of key + + keys, err = CheckArmoredGPGKeyString(testGPGArmor) require.NotEmpty(t, keys) ekey := keys[0] diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go index a90bcad246a..88c5256eff1 100644 --- a/models/asymkey/ssh_key_test.go +++ b/models/asymkey/ssh_key_test.go @@ -23,18 +23,18 @@ import ( func Test_SSHParsePublicKey(t *testing.T) { testCases := []struct { - name string - skipSSHKeygen bool - keyType string - length int - content string + name string + keyType string + length int + fp string + content string }{ - {"rsa-1024", false, "rsa", 1024, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"}, - {"rsa-2048", false, "rsa", 2048, "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"}, - {"ecdsa-256", false, "ecdsa", 256, "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"}, - {"ecdsa-384", false, "ecdsa", 384, "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"}, - {"ecdsa-sk", true, "ecdsa-sk", 256, "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"}, - {"ed25519-sk", true, "ed25519-sk", 256, "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"}, + {"rsa-1024", "rsa", 1024, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"}, + {"rsa-2048", "rsa", 2048, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"}, + {"ecdsa-256", "ecdsa", 256, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"}, + {"ecdsa-384", "ecdsa", 384, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"}, + {"ecdsa-sk", "ecdsa-sk", 256, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"}, + {"ed25519-sk", "ed25519-sk", 256, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"}, } for _, tc := range testCases { @@ -44,6 +44,10 @@ func Test_SSHParsePublicKey(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tc.keyType, keyTypeN) assert.Equal(t, tc.length, lengthN) + + fpN, err := calcFingerprintNative(tc.content) + assert.NoError(t, err) + assert.Equal(t, tc.fp, fpN) }) }) } @@ -143,32 +147,6 @@ AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf } } -func Test_calcFingerprint(t *testing.T) { - testCases := []struct { - name string - skipSSHKeygen bool - fp string - content string - }{ - {"rsa-1024", false, "SHA256:vSnDkvRh/xM6kMxPidLgrUhq3mCN7CDaronCEm2joyQ", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\n"}, - {"rsa-2048", false, "SHA256:ZHD//a1b9VuTq9XSunAeYjKeU1xDa2tBFZYrFr2Okkg", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMZXh+1OBUwSH9D45wTaxErQIN9IoC9xl7MKJkqvTvv6O5RR9YW/IK9FbfjXgXsppYGhsCZo1hFOOsXHMnfOORqu/xMDx4yPuyvKpw4LePEcg4TDipaDFuxbWOqc/BUZRZcXu41QAWfDLrInwsltWZHSeG7hjhpacl4FrVv9V1pS6Oc5Q1NxxEzTzuNLS/8diZrTm/YAQQ/+B+mzWI3zEtF4miZjjAljWd1LTBPvU23d29DcBmmFahcZ441XZsTeAwGxG/Q6j8NgNXj9WxMeWwxXV2jeAX/EBSpZrCVlCQ1yJswT6xCp8TuBnTiGWYMBNTbOZvPC4e0WI2/yZW/s5F nocomment"}, - {"ecdsa-256", false, "SHA256:Bqx/xgWqRKLtkZ0Lr4iZpgb+5lYsFpSwXwVZbPwuTRw", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFQacN3PrOll7PXmN5B/ZNVahiUIqI05nbBlZk1KXsO3d06ktAWqbNflv2vEmA38bTFTfJ2sbn2B5ksT52cDDbA= nocomment"}, - {"ecdsa-384", false, "SHA256:4qfJOgJDtUd8BrEjyVNdI8IgjiZKouztVde43aDhe1E", "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBINmioV+XRX1Fm9Qk2ehHXJ2tfVxW30ypUWZw670Zyq5GQfBAH6xjygRsJ5wWsHXBsGYgFUXIHvMKVAG1tpw7s6ax9oA+dJOJ7tj+vhn8joFqT+sg3LYHgZkHrfqryRasQ== nocomment"}, - {"ecdsa-sk", true, "SHA256:4wcIu4z+53gHc+db85OPfy8IydyNzPLCr6kHIs625LQ", "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment"}, - {"ed25519-sk", true, "SHA256:RB4ku1OeWKN7fLMrjxz38DK0mp1BnOPBx4BItjTvJ0g", "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIE7kM1R02+4ertDKGKEDcKG0s+2vyDDcIvceJ0Gqv5f1AAAABHNzaDo= nocomment"}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Run("Native", func(t *testing.T) { - fpN, err := calcFingerprintNative(tc.content) - assert.NoError(t, err) - assert.Equal(t, tc.fp, fpN) - }) - }) - } -} - var ( // Generated with "ssh-keygen -C test@rekor.dev -f id_rsa" sshPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY----- diff --git a/models/audit/audit_event_test.go b/models/audit/audit_event_test.go index 46bc470363e..b3e183faa20 100644 --- a/models/audit/audit_event_test.go +++ b/models/audit/audit_event_test.go @@ -17,35 +17,29 @@ import ( func TestFindEventsScopeFilters(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) - events := []*Event{ + for _, event := range []*Event{ {Action: UserCreate, ScopeType: ScopeUser, ScopeID: 5, Origin: OriginUI, TimestampUnix: timeutil.TimeStamp(1)}, {Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 5, Origin: OriginAPI, TimestampUnix: timeutil.TimeStamp(1)}, {Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 6, Origin: OriginCLI, TimestampUnix: timeutil.TimeStamp(1)}, {Action: RepositoryCreate, ScopeType: ScopeRepository, ScopeID: 7, Origin: OriginSystem, TimestampUnix: timeutil.TimeStamp(1)}, - } - for _, event := range events { + } { require.NoError(t, InsertEvent(t.Context(), event)) } - byType, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository}) - require.NoError(t, err) - assert.Len(t, byType, 3) - - byID, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeID: 5}) - require.NoError(t, err) - assert.Len(t, byID, 2) - - byScope, _, err := FindEvents(t.Context(), &EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5}) - require.NoError(t, err) - assert.Len(t, byScope, 1) - - byOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginAPI}) - require.NoError(t, err) - assert.Len(t, byOrigin, 1) - - bySystemOrigin, _, err := FindEvents(t.Context(), &EventSearchOptions{Origin: OriginSystem}) - require.NoError(t, err) - assert.Len(t, bySystemOrigin, 1) + for _, test := range []struct { + options EventSearchOptions + count int + }{ + {EventSearchOptions{ScopeType: ScopeRepository}, 3}, + {EventSearchOptions{ScopeID: 5}, 2}, + {EventSearchOptions{ScopeType: ScopeRepository, ScopeID: 5}, 1}, + {EventSearchOptions{Origin: OriginAPI}, 1}, + {EventSearchOptions{Origin: OriginSystem}, 1}, + } { + events, _, err := FindEvents(t.Context(), &test.options) + require.NoError(t, err) + assert.Len(t, events, test.count) + } } func TestFindEventsActionPrefixFilter(t *testing.T) { diff --git a/models/auth/access_token_test.go b/models/auth/access_token_test.go index f2645aab118..053c4bb8cb2 100644 --- a/models/auth/access_token_test.go +++ b/models/auth/access_token_test.go @@ -14,97 +14,148 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNewAccessToken(t *testing.T) { +func TestAccessTokenLifecycle(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - token := &auth_model.AccessToken{ - UID: 3, - Name: "Token C", - } - assert.NoError(t, auth_model.NewAccessToken(t.Context(), token)) - unittest.AssertExistsAndLoadBean(t, token) - invalidToken := &auth_model.AccessToken{ - ID: token.ID, // duplicate - UID: 2, - Name: "Token F", - } - assert.Error(t, auth_model.NewAccessToken(t.Context(), invalidToken)) -} + t.Run("GetAccessTokenBySHA", func(t *testing.T) { + token, err := auth_model.GetAccessTokenBySHA(t.Context(), "d2c6c1ba3890b309189a8e618c72a162e4efbf36") + assert.NoError(t, err) + assert.Equal(t, int64(1), token.UID) + assert.Equal(t, "Token A", token.Name) + assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash) + assert.Equal(t, "e4efbf36", token.TokenLastEight) -func TestAccessTokenByNameExists(t *testing.T) { - name := "Token Gitea" + _, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash") + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) - assert.NoError(t, unittest.PrepareTestDatabase()) - token := &auth_model.AccessToken{ - UID: 3, - Name: name, - } + _, err = auth_model.GetAccessTokenBySHA(t.Context(), "") + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) + }) - // Check to make sure it doesn't exists already - exist, err := auth_model.AccessTokenByNameExists(t.Context(), token) - assert.NoError(t, err) - assert.False(t, exist) + t.Run("ListAccessTokens", func(t *testing.T) { + tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 1}) + assert.NoError(t, err) + if assert.Len(t, tokens, 2) { + assert.Equal(t, int64(1), tokens[0].UID) + assert.Equal(t, int64(1), tokens[1].UID) + assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token A") + assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B") + } - // Save it to the database - assert.NoError(t, auth_model.NewAccessToken(t.Context(), token)) - unittest.AssertExistsAndLoadBean(t, token) + tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 2}) + assert.NoError(t, err) + if assert.Len(t, tokens, 1) { + assert.Equal(t, int64(2), tokens[0].UID) + assert.Equal(t, "Token A", tokens[0].Name) + } - // This token must be found by name in the DB now - exist, err = auth_model.AccessTokenByNameExists(t.Context(), token) - assert.NoError(t, err) - assert.True(t, exist) + tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 100}) + assert.NoError(t, err) + assert.Empty(t, tokens) + }) - user4Token := &auth_model.AccessToken{ - UID: 4, - Name: name, - } + t.Run("NewAccessToken", func(t *testing.T) { + token := &auth_model.AccessToken{ + UID: 3, + Name: "Token C", + } + assert.NoError(t, auth_model.NewAccessToken(t.Context(), token)) + unittest.AssertExistsAndLoadBean(t, token) - // Name matches but different user ID, this shouldn't exists in the - // database - exist, err = auth_model.AccessTokenByNameExists(t.Context(), user4Token) - assert.NoError(t, err) - assert.False(t, exist) -} + invalidToken := &auth_model.AccessToken{ + ID: token.ID, // duplicate + UID: 2, + Name: "Token F", + } + assert.Error(t, auth_model.NewAccessToken(t.Context(), invalidToken)) + }) -func TestGetAccessTokenBySHA(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - token, err := auth_model.GetAccessTokenBySHA(t.Context(), "d2c6c1ba3890b309189a8e618c72a162e4efbf36") - assert.NoError(t, err) - assert.Equal(t, int64(1), token.UID) - assert.Equal(t, "Token A", token.Name) - assert.Equal(t, "2b3668e11cb82d3af8c6e4524fc7841297668f5008d1626f0ad3417e9fa39af84c268248b78c481daa7e5dc437784003494f", token.TokenHash) - assert.Equal(t, "e4efbf36", token.TokenLastEight) + t.Run("AccessTokenByNameExists", func(t *testing.T) { + name := "Token Gitea" - _, err = auth_model.GetAccessTokenBySHA(t.Context(), "notahash") - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) + token := &auth_model.AccessToken{ + UID: 3, + Name: name, + } - _, err = auth_model.GetAccessTokenBySHA(t.Context(), "") - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) -} + // Check to make sure it doesn't exists already + exist, err := auth_model.AccessTokenByNameExists(t.Context(), token) + assert.NoError(t, err) + assert.False(t, exist) -func TestListAccessTokens(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 1}) - assert.NoError(t, err) - if assert.Len(t, tokens, 2) { - assert.Equal(t, int64(1), tokens[0].UID) - assert.Equal(t, int64(1), tokens[1].UID) - assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token A") - assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B") - } + // Save it to the database + assert.NoError(t, auth_model.NewAccessToken(t.Context(), token)) + unittest.AssertExistsAndLoadBean(t, token) - tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 2}) - assert.NoError(t, err) - if assert.Len(t, tokens, 1) { - assert.Equal(t, int64(2), tokens[0].UID) - assert.Equal(t, "Token A", tokens[0].Name) - } + // This token must be found by name in the DB now + exist, err = auth_model.AccessTokenByNameExists(t.Context(), token) + assert.NoError(t, err) + assert.True(t, exist) - tokens, err = db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: 100}) - assert.NoError(t, err) - assert.Empty(t, tokens) + user4Token := &auth_model.AccessToken{ + UID: 4, + Name: name, + } + + // Name matches but different user ID, this shouldn't exists in the + // database + exist, err = auth_model.AccessTokenByNameExists(t.Context(), user4Token) + assert.NoError(t, err) + assert.False(t, exist) + }) + + t.Run("RegenerateAccessToken", func(t *testing.T) { + const oldToken = "d2c6c1ba3890b309189a8e618c72a162e4efbf36" + + // prime the successful-lookup cache with the old token value, as a real request would + before, err := auth_model.GetAccessTokenBySHA(t.Context(), oldToken) + assert.NoError(t, err) + assert.Equal(t, "Token A", before.Name) + + regenerated, err := auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID) + assert.NoError(t, err) + assert.Equal(t, before.ID, regenerated.ID) + assert.Equal(t, before.Name, regenerated.Name) + assert.Equal(t, before.Scope, regenerated.Scope) + assert.NotEqual(t, before.TokenHash, regenerated.TokenHash) + assert.NotEmpty(t, regenerated.Token) + + // the old token value must stop authenticating, even though it was cached as successful above + _, err = auth_model.GetAccessTokenBySHA(t.Context(), oldToken) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) + + // the new token value must authenticate + found, err := auth_model.GetAccessTokenBySHA(t.Context(), regenerated.Token) + assert.NoError(t, err) + assert.Equal(t, before.ID, found.ID) + assert.Equal(t, before.UpdatedUnix, found.UpdatedUnix) + + // wrong owner + _, err = auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID+1) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) + + // nonexistent token + _, err = auth_model.RegenerateAccessToken(t.Context(), 100, 100) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) + }) + + t.Run("DeleteAccessTokenByID", func(t *testing.T) { + token, err := auth_model.GetAccessTokenBySHA(t.Context(), "4c6f36e6cf498e2a448662f915d932c09c5a146c") + assert.NoError(t, err) + assert.Equal(t, int64(1), token.UID) + + assert.NoError(t, auth_model.DeleteAccessTokenByID(t.Context(), token.ID, 1)) + unittest.AssertNotExistsBean(t, token) + + err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) + }) } func TestUpdateAccessToken(t *testing.T) { @@ -116,58 +167,3 @@ func TestUpdateAccessToken(t *testing.T) { assert.NoError(t, auth_model.UpdateAccessToken(t.Context(), token)) unittest.AssertExistsAndLoadBean(t, token) } - -func TestRegenerateAccessToken(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - const oldToken = "d2c6c1ba3890b309189a8e618c72a162e4efbf36" - - // prime the successful-lookup cache with the old token value, as a real request would - before, err := auth_model.GetAccessTokenBySHA(t.Context(), oldToken) - assert.NoError(t, err) - assert.Equal(t, "Token A", before.Name) - - regenerated, err := auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID) - assert.NoError(t, err) - assert.Equal(t, before.ID, regenerated.ID) - assert.Equal(t, before.Name, regenerated.Name) - assert.Equal(t, before.Scope, regenerated.Scope) - assert.NotEqual(t, before.TokenHash, regenerated.TokenHash) - assert.NotEmpty(t, regenerated.Token) - - // the old token value must stop authenticating, even though it was cached as successful above - _, err = auth_model.GetAccessTokenBySHA(t.Context(), oldToken) - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) - - // the new token value must authenticate - found, err := auth_model.GetAccessTokenBySHA(t.Context(), regenerated.Token) - assert.NoError(t, err) - assert.Equal(t, before.ID, found.ID) - assert.Equal(t, before.UpdatedUnix, found.UpdatedUnix) - - // wrong owner - _, err = auth_model.RegenerateAccessToken(t.Context(), before.ID, before.UID+1) - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) - - // nonexistent token - _, err = auth_model.RegenerateAccessToken(t.Context(), 100, 100) - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) -} - -func TestDeleteAccessTokenByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - token, err := auth_model.GetAccessTokenBySHA(t.Context(), "4c6f36e6cf498e2a448662f915d932c09c5a146c") - assert.NoError(t, err) - assert.Equal(t, int64(1), token.UID) - - assert.NoError(t, auth_model.DeleteAccessTokenByID(t.Context(), token.ID, 1)) - unittest.AssertNotExistsBean(t, token) - - err = auth_model.DeleteAccessTokenByID(t.Context(), 100, 100) - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) -} diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index ea760eb2636..dee37d14067 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -57,13 +57,105 @@ func TestOAuth2AuthorizationCode(t *testing.T) { }) } -func TestOAuth2Application_GenerateClientSecret(t *testing.T) { +func TestOAuth2ApplicationAndGrant(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) - secret, err := app.GenerateClientSecret(t.Context()) - assert.NoError(t, err) - assert.NotEmpty(t, secret) - unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) + + t.Run("GetOAuth2ApplicationByClientID", func(t *testing.T) { + app, err := auth_model.GetOAuth2ApplicationByClientID(t.Context(), "da7da3ba-9a13-4167-856f-3899de0b0138") + assert.NoError(t, err) + assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID) + + app, err = auth_model.GetOAuth2ApplicationByClientID(t.Context(), "invalid client id") + assert.Error(t, err) + assert.Nil(t, app) + }) + + t.Run("OAuth2Application_GetGrantByUserID", func(t *testing.T) { + app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) + grant, err := app.GetGrantByUserID(t.Context(), 1) + assert.NoError(t, err) + assert.Equal(t, int64(1), grant.UserID) + + grant, err = app.GetGrantByUserID(t.Context(), 34923458) + assert.NoError(t, err) + assert.Nil(t, grant) + }) + + t.Run("GetOAuth2GrantByID", func(t *testing.T) { + grant, err := auth_model.GetOAuth2GrantByID(t.Context(), 1) + assert.NoError(t, err) + assert.Equal(t, int64(1), grant.ID) + + grant, err = auth_model.GetOAuth2GrantByID(t.Context(), 34923458) + assert.NoError(t, err) + assert.Nil(t, grant) + }) + + t.Run("OAuth2Grant_ScopeContains", func(t *testing.T) { + grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"}) + assert.True(t, grant.ScopeContains("openid")) + assert.True(t, grant.ScopeContains("profile")) + assert.False(t, grant.ScopeContains("profil")) + assert.False(t, grant.ScopeContains("profile2")) + }) + + t.Run("GetOAuth2GrantsByUserID", func(t *testing.T) { + result, err := auth_model.GetOAuth2GrantsByUserID(t.Context(), 1) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.Equal(t, int64(1), result[0].ID) + assert.Equal(t, result[0].ApplicationID, result[0].Application.ID) + + result, err = auth_model.GetOAuth2GrantsByUserID(t.Context(), 34134) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("OAuth2Grant_GenerateNewAuthorizationCode", func(t *testing.T) { + grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1}) + code, err := grant.GenerateNewAuthorizationCode(t.Context(), "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256") + assert.NoError(t, err) + assert.NotNil(t, code) + assert.Greater(t, len(code.Code), 32) // secret length > 32 + }) + + t.Run("OAuth2Application_CreateGrant", func(t *testing.T) { + app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) + grant, err := app.CreateGrant(t.Context(), 2, "") + assert.NoError(t, err) + assert.NotNil(t, grant) + assert.Equal(t, int64(2), grant.UserID) + assert.Equal(t, int64(1), grant.ApplicationID) + assert.Empty(t, grant.Scope) + }) + + t.Run("OAuth2Grant_IncreaseCounter", func(t *testing.T) { + grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1}) + stale := *grant + assert.NoError(t, grant.IncreaseCounter(t.Context())) + assert.Equal(t, int64(2), grant.Counter) + unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2}) + err := stale.IncreaseCounter(t.Context()) + assert.ErrorIs(t, err, auth_model.ErrOAuth2GrantStaleCounter) + }) + + t.Run("OAuth2Application_GenerateClientSecret", func(t *testing.T) { + app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) + secret, err := app.GenerateClientSecret(t.Context()) + assert.NoError(t, err) + assert.NotEmpty(t, secret) + unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1, ClientSecret: app.ClientSecret}) + assert.True(t, app.ValidateClientSecret([]byte(secret))) + assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew"))) + }) + + t.Run("CreateOAuth2Application", func(t *testing.T) { + app, err := auth_model.CreateOAuth2Application(t.Context(), auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1}) + assert.NoError(t, err) + assert.Equal(t, "newapp", app.Name) + assert.Len(t, app.ClientID, 36) + unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"}) + }) } func BenchmarkOAuth2Application_GenerateClientSecret(b *testing.B) { @@ -106,15 +198,12 @@ func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) { } func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) { - app := &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1"}} - assert.True(t, app.ContainsRedirectURI("http://127.0.0.1")) - assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/")) - assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other")) - - app = &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1/"}} - assert.True(t, app.ContainsRedirectURI("http://127.0.0.1")) - assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/")) - assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other")) + for _, redirectURI := range []string{"http://127.0.0.1", "http://127.0.0.1/"} { + app := &auth_model.OAuth2Application{RedirectURIs: []string{redirectURI}} + assert.True(t, app.ContainsRedirectURI("http://127.0.0.1")) + assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/")) + assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other")) + } } func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing.T) { @@ -158,128 +247,16 @@ func TestOAuth2Application_ContainsRedirectURI_ASCIIOnlyNormalization(t *testing } } -func TestOAuth2Application_ValidateClientSecret(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) - secret, err := app.GenerateClientSecret(t.Context()) - assert.NoError(t, err) - assert.True(t, app.ValidateClientSecret([]byte(secret))) - assert.False(t, app.ValidateClientSecret([]byte("fewijfowejgfiowjeoifew"))) -} - -func TestGetOAuth2ApplicationByClientID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - app, err := auth_model.GetOAuth2ApplicationByClientID(t.Context(), "da7da3ba-9a13-4167-856f-3899de0b0138") - assert.NoError(t, err) - assert.Equal(t, "da7da3ba-9a13-4167-856f-3899de0b0138", app.ClientID) - - app, err = auth_model.GetOAuth2ApplicationByClientID(t.Context(), "invalid client id") - assert.Error(t, err) - assert.Nil(t, app) -} - -func TestCreateOAuth2Application(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - app, err := auth_model.CreateOAuth2Application(t.Context(), auth_model.CreateOAuth2ApplicationOptions{Name: "newapp", UserID: 1}) - assert.NoError(t, err) - assert.Equal(t, "newapp", app.Name) - assert.Len(t, app.ClientID, 36) - unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{Name: "newapp"}) -} - func TestOAuth2Application_TableName(t *testing.T) { assert.Equal(t, "oauth2_application", new(auth_model.OAuth2Application).TableName()) } -func TestOAuth2Application_GetGrantByUserID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) - grant, err := app.GetGrantByUserID(t.Context(), 1) - assert.NoError(t, err) - assert.Equal(t, int64(1), grant.UserID) - - grant, err = app.GetGrantByUserID(t.Context(), 34923458) - assert.NoError(t, err) - assert.Nil(t, grant) -} - -func TestOAuth2Application_CreateGrant(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) - grant, err := app.CreateGrant(t.Context(), 2, "") - assert.NoError(t, err) - assert.NotNil(t, grant) - assert.Equal(t, int64(2), grant.UserID) - assert.Equal(t, int64(1), grant.ApplicationID) - assert.Empty(t, grant.Scope) -} - //////////////////// Grant -func TestGetOAuth2GrantByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - grant, err := auth_model.GetOAuth2GrantByID(t.Context(), 1) - assert.NoError(t, err) - assert.Equal(t, int64(1), grant.ID) - - grant, err = auth_model.GetOAuth2GrantByID(t.Context(), 34923458) - assert.NoError(t, err) - assert.Nil(t, grant) -} - -func TestOAuth2Grant_IncreaseCounter(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1}) - assert.NoError(t, grant.IncreaseCounter(t.Context())) - assert.Equal(t, int64(2), grant.Counter) - unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 2}) -} - -func TestOAuth2Grant_IncreaseCounterRejectsStaleCounter(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Counter: 1}) - stale := *grant - - assert.NoError(t, grant.IncreaseCounter(t.Context())) - err := stale.IncreaseCounter(t.Context()) - assert.ErrorIs(t, err, auth_model.ErrOAuth2GrantStaleCounter) -} - -func TestOAuth2Grant_ScopeContains(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1, Scope: "openid profile"}) - assert.True(t, grant.ScopeContains("openid")) - assert.True(t, grant.ScopeContains("profile")) - assert.False(t, grant.ScopeContains("profil")) - assert.False(t, grant.ScopeContains("profile2")) -} - -func TestOAuth2Grant_GenerateNewAuthorizationCode(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1}) - code, err := grant.GenerateNewAuthorizationCode(t.Context(), "https://example2.com/callback", "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg", "S256") - assert.NoError(t, err) - assert.NotNil(t, code) - assert.Greater(t, len(code.Code), 32) // secret length > 32 -} - func TestOAuth2Grant_TableName(t *testing.T) { assert.Equal(t, "oauth2_grant", new(auth_model.OAuth2Grant).TableName()) } -func TestGetOAuth2GrantsByUserID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - result, err := auth_model.GetOAuth2GrantsByUserID(t.Context(), 1) - assert.NoError(t, err) - assert.Len(t, result, 1) - assert.Equal(t, int64(1), result[0].ID) - assert.Equal(t, result[0].ApplicationID, result[0].Application.ID) - - result, err = auth_model.GetOAuth2GrantsByUserID(t.Context(), 34134) - assert.NoError(t, err) - assert.Empty(t, result) -} - func TestRevokeOAuth2Grant(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, auth_model.RevokeOAuth2Grant(t.Context(), 1, 1)) diff --git a/models/auth/twofactor_test.go b/models/auth/twofactor_test.go index 835e7c9c418..a3ed2187d0f 100644 --- a/models/auth/twofactor_test.go +++ b/models/auth/twofactor_test.go @@ -16,67 +16,68 @@ import ( "github.com/stretchr/testify/require" ) -func TestTwoFactorValidateAndConsumeTOTP(t *testing.T) { +func TestTwoFactorLifecycle(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) - key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"}) - require.NoError(t, err) + t.Run("TwoFactorValidateAndConsumeTOTP", func(t *testing.T) { + key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"}) + require.NoError(t, err) - tfa := &auth_model.TwoFactor{UID: 1} - require.NoError(t, tfa.SetSecret(key.Secret())) - require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) + tfa := &auth_model.TwoFactor{UID: 1} + require.NoError(t, tfa.SetSecret(key.Secret())) + require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) - passcode, err := totp.GenerateCode(key.Secret(), time.Now()) - require.NoError(t, err) + passcode, err := totp.GenerateCode(key.Secret(), time.Now()) + require.NoError(t, err) - // first use of a valid passcode succeeds - ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode) - require.NoError(t, err) - assert.True(t, ok) + // first use of a valid passcode succeeds + ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode) + require.NoError(t, err) + assert.True(t, ok) - // replaying the same passcode is refused, even when still inside the TOTP validity window - reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID) - require.NoError(t, err) - ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode) - require.NoError(t, err) - assert.False(t, ok) + // replaying the same passcode is refused, even when still inside the TOTP validity window + reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID) + require.NoError(t, err) + ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode) + require.NoError(t, err) + assert.False(t, ok) - // an invalid passcode is rejected without consuming anything - ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000") - require.NoError(t, err) - assert.False(t, ok) -} - -func TestDisableTwoFactor(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() - - const uid = 1000 // a uid with no user/2FA fixtures - - // Enroll TOTP and register a WebAuthn credential. - tfa := &auth_model.TwoFactor{UID: uid} - require.NoError(t, tfa.SetSecret("test-secret")) - require.NoError(t, auth_model.NewTwoFactor(ctx, tfa)) - _, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")}) - require.NoError(t, err) - - has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid) - require.NoError(t, err) - require.True(t, has) - - // Both records are removed and counted separately. - totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid) - require.NoError(t, err) - assert.EqualValues(t, 1, totp) - assert.EqualValues(t, 1, webAuthn) - - has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid) - require.NoError(t, err) - assert.False(t, has) - - // A second call on a user without 2FA is a no-op. - totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid) - require.NoError(t, err) - assert.EqualValues(t, 0, totp) - assert.EqualValues(t, 0, webAuthn) + // an invalid passcode is rejected without consuming anything + ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000") + require.NoError(t, err) + assert.False(t, ok) + }) + + t.Run("DisableTwoFactor", func(t *testing.T) { + ctx := t.Context() + + const uid = 1000 // a uid with no user/2FA fixtures + + // Enroll TOTP and register a WebAuthn credential. + tfa := &auth_model.TwoFactor{UID: uid} + require.NoError(t, tfa.SetSecret("test-secret")) + require.NoError(t, auth_model.NewTwoFactor(ctx, tfa)) + _, err := auth_model.CreateCredential(ctx, uid, "test-key", &webauthn.Credential{ID: []byte("test-cred-id")}) + require.NoError(t, err) + + has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, uid) + require.NoError(t, err) + require.True(t, has) + + // Both records are removed and counted separately. + totp, webAuthn, err := auth_model.DisableTwoFactor(ctx, uid) + require.NoError(t, err) + assert.EqualValues(t, 1, totp) + assert.EqualValues(t, 1, webAuthn) + + has, err = auth_model.HasTwoFactorOrWebAuthn(ctx, uid) + require.NoError(t, err) + assert.False(t, has) + + // A second call on a user without 2FA is a no-op. + totp, webAuthn, err = auth_model.DisableTwoFactor(ctx, uid) + require.NoError(t, err) + assert.EqualValues(t, 0, totp) + assert.EqualValues(t, 0, webAuthn) + }) } diff --git a/models/auth/webauthn_test.go b/models/auth/webauthn_test.go index 81eeacf1e08..1e6817353be 100644 --- a/models/auth/webauthn_test.go +++ b/models/auth/webauthn_test.go @@ -13,29 +13,38 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetWebAuthnCredentialByID(t *testing.T) { +func TestWebAuthnCredentials(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1) - assert.NoError(t, err) - assert.Equal(t, "WebAuthn credential", res.Name) + t.Run("GetWebAuthnCredentialByID", func(t *testing.T) { + res, err := auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 1) + assert.NoError(t, err) + assert.Equal(t, "WebAuthn credential", res.Name) - _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1) - assert.Error(t, err) - assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err)) + _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 99999, 1) + assert.Error(t, err) + assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err)) - _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999) - assert.Error(t, err) - assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err)) -} + _, err = auth_model.GetWebAuthnCredentialByID(t.Context(), 32, 99999) + assert.Error(t, err) + assert.True(t, auth_model.IsErrWebAuthnCredentialNotExist(err)) + }) -func TestGetWebAuthnCredentialsByUID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("GetWebAuthnCredentialsByUID", func(t *testing.T) { + res, err := auth_model.GetWebAuthnCredentialsByUID(t.Context(), 32) + assert.NoError(t, err) + assert.Len(t, res, 1) + assert.Equal(t, "WebAuthn credential", res[0].Name) + }) - res, err := auth_model.GetWebAuthnCredentialsByUID(t.Context(), 32) - assert.NoError(t, err) - assert.Len(t, res, 1) - assert.Equal(t, "WebAuthn credential", res[0].Name) + t.Run("CreateCredential", func(t *testing.T) { + res, err := auth_model.CreateCredential(t.Context(), 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) + assert.NoError(t, err) + assert.Equal(t, "WebAuthn Created Credential", res.Name) + assert.Equal(t, []byte("Test"), res.CredentialID) + + unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1}) + }) } func TestWebAuthnCredential_TableName(t *testing.T) { @@ -43,28 +52,11 @@ func TestWebAuthnCredential_TableName(t *testing.T) { } func TestWebAuthnCredential_UpdateSignCount(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) - cred.SignCount = 1 - assert.NoError(t, cred.UpdateSignCount(t.Context())) - unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: 1}) -} - -func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) - cred.SignCount = 0xffffffff - assert.NoError(t, cred.UpdateSignCount(t.Context())) - unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: 0xffffffff}) -} - -func TestCreateCredential(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - res, err := auth_model.CreateCredential(t.Context(), 1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")}) - assert.NoError(t, err) - assert.Equal(t, "WebAuthn Created Credential", res.Name) - assert.Equal(t, []byte("Test"), res.CredentialID) - - unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1}) + for _, signCount := range []uint32{1, 0xffffffff} { + assert.NoError(t, unittest.PrepareTestDatabase()) + cred := unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1}) + cred.SignCount = signCount + assert.NoError(t, cred.UpdateSignCount(t.Context())) + unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{ID: 1, SignCount: signCount}) + } } diff --git a/models/db/context_test.go b/models/db/context_test.go index c409f5bbdab..880c740500c 100644 --- a/models/db/context_test.go +++ b/models/db/context_test.go @@ -42,7 +42,10 @@ func TestTxContext(t *testing.T) { assert.NoError(t, committer.Commit()) } - { // reuse the transaction created by TxContext and commit it + for _, finish := range []func(db.Committer) error{ + db.Committer.Commit, // reuse the transaction created by TxContext and commit it + db.Committer.Close, // reuse the transaction created by TxContext and close it + } { ctx, committer, err := db.TxContext(t.Context()) engine := db.GetEngine(ctx) assert.NoError(t, err) @@ -52,24 +55,9 @@ func TestTxContext(t *testing.T) { assert.NoError(t, err) assert.True(t, db.InTransaction(ctx)) assert.Equal(t, engine, db.GetEngine(ctx)) - assert.NoError(t, committer.Commit()) + assert.NoError(t, finish(committer)) } - assert.NoError(t, committer.Commit()) - } - - { // reuse the transaction created by TxContext and close it - ctx, committer, err := db.TxContext(t.Context()) - engine := db.GetEngine(ctx) - assert.NoError(t, err) - assert.True(t, db.InTransaction(ctx)) - { - ctx, committer, err := db.TxContext(ctx) - assert.NoError(t, err) - assert.True(t, db.InTransaction(ctx)) - assert.Equal(t, engine, db.GetEngine(ctx)) - assert.NoError(t, committer.Close()) - } - assert.NoError(t, committer.Close()) + assert.NoError(t, finish(committer)) } { // reuse the transaction created by WithTx diff --git a/models/db/index_test.go b/models/db/index_test.go index 5dece7e1beb..e1cf937e746 100644 --- a/models/db/index_test.go +++ b/models/db/index_test.go @@ -35,27 +35,22 @@ func TestSyncMaxResourceIndex(t *testing.T) { xe := unittest.GetXORMEngine() assert.NoError(t, xe.Sync(&TestIndex{})) - err := db.SyncMaxResourceIndex(t.Context(), "test_index", 10, 51) - assert.NoError(t, err) - - // sync new max index - maxIndex, err := getCurrentResourceIndex(t.Context(), "test_index", 10) - assert.NoError(t, err) - assert.EqualValues(t, 51, maxIndex) - - // smaller index doesn't change - err = db.SyncMaxResourceIndex(t.Context(), "test_index", 10, 30) - assert.NoError(t, err) - maxIndex, err = getCurrentResourceIndex(t.Context(), "test_index", 10) - assert.NoError(t, err) - assert.EqualValues(t, 51, maxIndex) - - // larger index changes - err = db.SyncMaxResourceIndex(t.Context(), "test_index", 10, 62) - assert.NoError(t, err) - maxIndex, err = getCurrentResourceIndex(t.Context(), "test_index", 10) - assert.NoError(t, err) - assert.EqualValues(t, 62, maxIndex) + var maxIndex int64 + var err error + for _, test := range []struct { + index int64 + expected int + }{ + {51, 51}, // sync new max index + {30, 51}, // smaller index doesn't change + {62, 62}, // larger index changes + } { + err = db.SyncMaxResourceIndex(t.Context(), "test_index", 10, test.index) + assert.NoError(t, err) + maxIndex, err = getCurrentResourceIndex(t.Context(), "test_index", 10) + assert.NoError(t, err) + assert.EqualValues(t, test.expected, maxIndex) + } // commit transaction err = db.WithTx(t.Context(), func(ctx context.Context) error { diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go index 63ea3feffb9..6a5f4534632 100644 --- a/models/dbfs/dbfs_test.go +++ b/models/dbfs/dbfs_test.go @@ -54,38 +54,29 @@ func TestDbfsBasic(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "0bcdefghi9", string(buf)) - // write to new size - _, err = f.Seek(-1, io.SeekEnd) - assert.NoError(t, err) - _, err = f.Write([]byte("JKLMNOP")) // blocks: 0bcd efgh iJKL MNOP - assert.NoError(t, err) - _, err = f.Seek(0, io.SeekStart) - assert.NoError(t, err) - buf, err = io.ReadAll(f) - assert.NoError(t, err) - assert.Equal(t, "0bcdefghiJKLMNOP", string(buf)) - - // write beyond EOF and fill with zero - _, err = f.Seek(5, io.SeekCurrent) - assert.NoError(t, err) - _, err = f.Write([]byte("xyzu")) // blocks: 0bcd efgh iJKL MNOP 0000 0xyz u - assert.NoError(t, err) - _, err = f.Seek(0, io.SeekStart) - assert.NoError(t, err) - buf, err = io.ReadAll(f) - assert.NoError(t, err) - assert.Equal(t, "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu", string(buf)) - - // write to the block with zeros - _, err = f.Seek(-6, io.SeekCurrent) - assert.NoError(t, err) - _, err = f.Write([]byte("ABCD")) // blocks: 0bcd efgh iJKL MNOP 000A BCDz u - assert.NoError(t, err) - _, err = f.Seek(0, io.SeekStart) - assert.NoError(t, err) - buf, err = io.ReadAll(f) - assert.NoError(t, err) - assert.Equal(t, "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu", string(buf)) + for _, write := range []struct { + offset int64 + whence int + content string + expected string + }{ + // write to new size + {-1, io.SeekEnd, "JKLMNOP", "0bcdefghiJKLMNOP"}, // blocks: 0bcd efgh iJKL MNOP + // write beyond EOF and fill with zero + {5, io.SeekCurrent, "xyzu", "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu"}, // blocks: 0bcd efgh iJKL MNOP 0000 0xyz u + // write to the block with zeros + {-6, io.SeekCurrent, "ABCD", "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu"}, // blocks: 0bcd efgh iJKL MNOP 000A BCDz u + } { + _, err = f.Seek(write.offset, write.whence) + assert.NoError(t, err) + _, err = f.Write([]byte(write.content)) + assert.NoError(t, err) + _, err = f.Seek(0, io.SeekStart) + assert.NoError(t, err) + buf, err = io.ReadAll(f) + assert.NoError(t, err) + assert.Equal(t, write.expected, string(buf)) + } assert.NoError(t, f.Close()) @@ -121,17 +112,11 @@ func TestDbfsBasic(t *testing.T) { assert.EqualValues(t, 10, stat.Size()) t.Run("NonExisting", func(t *testing.T) { - f, err := OpenFile(t.Context(), "non-existing.txt", os.O_RDONLY) - assert.ErrorIs(t, err, os.ErrNotExist) - assert.Nil(t, f) - - f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY) - assert.ErrorIs(t, err, os.ErrNotExist) - assert.Nil(t, f) - - f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY|os.O_APPEND|os.O_TRUNC) - assert.ErrorIs(t, err, os.ErrNotExist) - assert.Nil(t, f) + for _, flags := range []int{os.O_RDONLY, os.O_WRONLY, os.O_WRONLY | os.O_APPEND | os.O_TRUNC} { + f, err := OpenFile(t.Context(), "non-existing.txt", flags) + assert.ErrorIs(t, err, os.ErrNotExist) + assert.Nil(t, f) + } }) t.Run("Existing", func(t *testing.T) { @@ -211,17 +196,12 @@ func TestDbfsSeekWrite(t *testing.T) { n, err := fw.Write([]byte("111")) assert.NoError(t, err) - _, err = fw.Seek(int64(n), io.SeekStart) - assert.NoError(t, err) - - _, err = fw.Write([]byte("222")) - assert.NoError(t, err) - - _, err = fw.Seek(int64(n), io.SeekStart) - assert.NoError(t, err) - - _, err = fw.Write([]byte("333")) - assert.NoError(t, err) + for _, content := range []string{"222", "333"} { + _, err = fw.Seek(int64(n), io.SeekStart) + assert.NoError(t, err) + _, err = fw.Write([]byte(content)) + assert.NoError(t, err) + } // then read it fr, err := OpenFile(t.Context(), "test2.log", os.O_RDONLY) diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index 8b1c250cf28..74e2e21762b 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -92,116 +92,45 @@ func TestGetCommitStatuses(t *testing.T) { func Test_CalcCommitStatus(t *testing.T) { kases := []struct { - statuses []*git_model.CommitStatus - expected *git_model.CommitStatus + statuses commitstatus.CommitStatusStates + expected commitstatus.CommitStatusState }{ { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusPending, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusPending, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusPending}, + expected: commitstatus.CommitStatusPending, }, { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusSuccess, - }, - { - State: commitstatus.CommitStatusPending, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusPending, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusSuccess, commitstatus.CommitStatusPending}, + expected: commitstatus.CommitStatusPending, }, { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusSuccess, - }, - { - State: commitstatus.CommitStatusPending, - }, - { - State: commitstatus.CommitStatusSuccess, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusPending, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusSuccess, commitstatus.CommitStatusPending, commitstatus.CommitStatusSuccess}, + expected: commitstatus.CommitStatusPending, }, { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusError, - }, - { - State: commitstatus.CommitStatusPending, - }, - { - State: commitstatus.CommitStatusSuccess, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusFailure, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusError, commitstatus.CommitStatusPending, commitstatus.CommitStatusSuccess}, + expected: commitstatus.CommitStatusFailure, }, { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusWarning, - }, - { - State: commitstatus.CommitStatusPending, - }, - { - State: commitstatus.CommitStatusSuccess, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusFailure, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusWarning, commitstatus.CommitStatusPending, commitstatus.CommitStatusSuccess}, + expected: commitstatus.CommitStatusFailure, }, { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusSuccess, - }, - { - State: commitstatus.CommitStatusSuccess, - }, - { - State: commitstatus.CommitStatusSuccess, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusSuccess, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusSuccess, commitstatus.CommitStatusSuccess, commitstatus.CommitStatusSuccess}, + expected: commitstatus.CommitStatusSuccess, }, { - statuses: []*git_model.CommitStatus{ - { - State: commitstatus.CommitStatusFailure, - }, - { - State: commitstatus.CommitStatusError, - }, - { - State: commitstatus.CommitStatusWarning, - }, - }, - expected: &git_model.CommitStatus{ - State: commitstatus.CommitStatusFailure, - }, + statuses: commitstatus.CommitStatusStates{commitstatus.CommitStatusFailure, commitstatus.CommitStatusError, commitstatus.CommitStatusWarning}, + expected: commitstatus.CommitStatusFailure, }, } for _, kase := range kases { - assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses), "statuses: %v", kase.statuses) + statuses := make([]*git_model.CommitStatus, len(kase.statuses)) + for i, state := range kase.statuses { + statuses[i] = &git_model.CommitStatus{State: state} + } + assert.Equal(t, &git_model.CommitStatus{State: kase.expected}, git_model.CalcCommitStatus(statuses), "statuses: %v", statuses) } } @@ -226,29 +155,19 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) { assert.NoError(t, err) }() - err = git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{ - Repo: repo2, - Creator: user2, - SHA: commit.ID, - CommitStatus: &git_model.CommitStatus{ - State: commitstatus.CommitStatusFailure, - TargetURL: "https://example.com/tests/", - Context: "compliance/lint-backend", - }, - }) - assert.NoError(t, err) - - err = git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{ - Repo: repo2, - Creator: user2, - SHA: commit.ID, - CommitStatus: &git_model.CommitStatus{ - State: commitstatus.CommitStatusSuccess, - TargetURL: "https://example.com/tests/", - Context: "compliance/lint-backend", - }, - }) - assert.NoError(t, err) + for _, state := range []commitstatus.CommitStatusState{commitstatus.CommitStatusFailure, commitstatus.CommitStatusSuccess} { + err = git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{ + Repo: repo2, + Creator: user2, + SHA: commit.ID, + CommitStatus: &git_model.CommitStatus{ + State: state, + TargetURL: "https://example.com/tests/", + Context: "compliance/lint-backend", + }, + }) + assert.NoError(t, err) + } contexts, err := git_model.FindRepoRecentCommitStatusContexts(t.Context(), repo2.ID, time.Hour) assert.NoError(t, err) diff --git a/models/git/protected_branch_list_test.go b/models/git/protected_branch_list_test.go index 298c2fa0741..d22a1950712 100644 --- a/models/git/protected_branch_list_test.go +++ b/models/git/protected_branch_list_test.go @@ -75,65 +75,34 @@ func TestBranchRuleMatchPriority(t *testing.T) { } } -func TestBranchRuleSortLegacy(t *testing.T) { - in := []*ProtectedBranch{{ - RuleName: "b", - CreatedUnix: 1, - }, { - RuleName: "b/*", - CreatedUnix: 3, - }, { - RuleName: "a/*", - CreatedUnix: 2, - }, { - RuleName: "c", - CreatedUnix: 0, - }, { - RuleName: "a", - CreatedUnix: 4, - }} - expect := []string{"c", "b", "a", "a/*", "b/*"} - - pbr := ProtectedBranchRules(in) - pbr.sort() - - var got []string - for i := range pbr { - got = append(got, pbr[i].RuleName) +func TestBranchRuleSort(t *testing.T) { + for _, tc := range []struct { + name string + rules ProtectedBranchRules + expected []string + }{ + {"Legacy", ProtectedBranchRules{ + {RuleName: "b", CreatedUnix: 1}, + {RuleName: "b/*", CreatedUnix: 3}, + {RuleName: "a/*", CreatedUnix: 2}, + {RuleName: "c", CreatedUnix: 0}, + {RuleName: "a", CreatedUnix: 4}, + }, []string{"c", "b", "a", "a/*", "b/*"}}, + {"Priority", ProtectedBranchRules{ + {RuleName: "b", CreatedUnix: 1, Priority: 4}, + {RuleName: "b/*", CreatedUnix: 3, Priority: 2}, + {RuleName: "a/*", CreatedUnix: 2, Priority: 1}, + {RuleName: "c", CreatedUnix: 0, Priority: 0}, + {RuleName: "a", CreatedUnix: 4, Priority: 3}, + }, []string{"c", "a/*", "b/*", "a", "b"}}, + } { + t.Run(tc.name, func(t *testing.T) { + tc.rules.sort() + var got []string + for i := range tc.rules { + got = append(got, tc.rules[i].RuleName) + } + assert.Equal(t, tc.expected, got) + }) } - assert.Equal(t, expect, got) -} - -func TestBranchRuleSortPriority(t *testing.T) { - in := []*ProtectedBranch{{ - RuleName: "b", - CreatedUnix: 1, - Priority: 4, - }, { - RuleName: "b/*", - CreatedUnix: 3, - Priority: 2, - }, { - RuleName: "a/*", - CreatedUnix: 2, - Priority: 1, - }, { - RuleName: "c", - CreatedUnix: 0, - Priority: 0, - }, { - RuleName: "a", - CreatedUnix: 4, - Priority: 3, - }} - expect := []string{"c", "a/*", "b/*", "a", "b"} - - pbr := ProtectedBranchRules(in) - pbr.sort() - - var got []string - for i := range pbr { - got = append(got, pbr[i].RuleName) - } - assert.Equal(t, expect, got) } diff --git a/models/git/protected_branch_test.go b/models/git/protected_branch_test.go index 32133860104..5ac0a1dc747 100644 --- a/models/git/protected_branch_test.go +++ b/models/git/protected_branch_test.go @@ -85,194 +85,202 @@ func TestBranchRuleMatch(t *testing.T) { } } -func TestUpdateProtectBranchPriorities(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) +func TestProtectedBranchPermissionsAndPriorities(t *testing.T) { + setupErr := unittest.PrepareTestDatabase() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + t.Run("CanBypassBranchProtection", func(t *testing.T) { + assert.NoError(t, setupErr) - // Create some test protected branches with initial priorities - protectedBranches := []*ProtectedBranch{ - { - RepoID: repo.ID, - RuleName: "master", - Priority: 1, - }, - { - RepoID: repo.ID, - RuleName: "develop", - Priority: 2, - }, - { - RepoID: repo.ID, - RuleName: "feature/*", - Priority: 3, - }, - } + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) // not in team 1 + teamMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - for _, pb := range protectedBranches { - _, err := db.GetEngine(t.Context()).Insert(pb) + pb := &ProtectedBranch{ + EnableBypassAllowlist: true, + BypassAllowlistUserIDs: []int64{user.ID}, + } + + testBypass := func(t *testing.T, expected bool, pb *ProtectedBranch, doer *user_model.User, isAdmin bool) { + assert.Equal(t, expected, CanBypassBranchProtection(t.Context(), pb, doer, isAdmin)) + } + // User bypasses via explicit allowlist. + testBypass(t, true, pb, user, false) + + // Non-admin cannot bypass when allowlist is disabled. + pb.EnableBypassAllowlist = false + testBypass(t, false, pb, user, false) + + // Repo admin can bypass independently of allowlist when not blocked. + testBypass(t, true, pb, user, true) + + // Admin override block still allows bypass for allowlisted users. + pb.EnableBypassAllowlist = true + pb.BlockAdminMergeOverride = true + testBypass(t, true, pb, user, false) + + // admin cannot bypass without allowlist membership. + pb.BypassAllowlistUserIDs = nil + testBypass(t, false, pb, user, true) + + // admin can bypass when allowlisted. + pb.BypassAllowlistUserIDs = []int64{user.ID} + testBypass(t, true, pb, user, true) + + // User bypasses via team allowlist membership. + pb.EnableBypassAllowlist = true + pb.BlockAdminMergeOverride = false + pb.BypassAllowlistUserIDs = nil + pb.BypassAllowlistTeamIDs = []int64{1} // team 1 contains user 2 in test fixtures + testBypass(t, true, pb, teamMember, false) + + // User does not bypass when not in allowlisted teams. + testBypass(t, false, pb, user, false) + }) + + t.Run("ProtectedBranchCanUserDelete", func(t *testing.T) { + require.NoError(t, setupErr) + writePermission := access_model.Permission{} + writePermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeWrite) + readPermission := access_model.Permission{} + readPermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeRead) + + for _, user := range []*user_model.User{{ID: 2, Name: "user2"}, user_model.NewActionsUser(), user_model.NewDeployKeyUser()} { + t.Run(user.GetDisplayName(), func(t *testing.T) { + for _, tt := range []struct { + name string + config ProtectedBranchConfig + want bool + }{ + {name: "disabled by default", config: ProtectedBranchConfig{CanPush: true}}, + {name: "push disabled", config: ProtectedBranchConfig{CanDelete: true}}, + {name: "unrestricted", config: ProtectedBranchConfig{CanPush: true, CanDelete: true}, want: true}, + {name: "empty push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true}}, + {name: "not in push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{1}}}, + {name: "empty deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true}}, + {name: "not in deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{1}}}, + {name: "in both allowlists", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{user.ID}, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}, want: true}, + {name: "deletion allowlist does not grant push", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}}, + {name: "team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{1}}, want: user.ID == 2}, + {name: "push team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistTeamIDs: []int64{1}}, want: user.ID == 2}, + {name: "not in team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{3}}}, + {name: "user or team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}, DeletionAllowlistTeamIDs: []int64{3}}, want: true}, + } { + t.Run(tt.name, func(t *testing.T) { + pb := &ProtectedBranch{ProtectedBranchConfig: tt.config} + assert.Equal(t, tt.want, pb.CanUserDelete(t.Context(), user, writePermission)) + assert.False(t, pb.CanUserDelete(t.Context(), user, readPermission)) + assert.False(t, pb.CanUserDelete(t.Context(), nil, writePermission)) + }) + } + }) + } + }) + + t.Run("UpdateProtectBranchPriorities", func(t *testing.T) { + assert.NoError(t, setupErr) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + // Create some test protected branches with initial priorities + protectedBranches := []*ProtectedBranch{ + { + RepoID: repo.ID, + RuleName: "master", + Priority: 1, + }, + { + RepoID: repo.ID, + RuleName: "develop", + Priority: 2, + }, + { + RepoID: repo.ID, + RuleName: "feature/*", + Priority: 3, + }, + } + + for _, pb := range protectedBranches { + _, err := db.GetEngine(t.Context()).Insert(pb) + assert.NoError(t, err) + } + + // Test updating priorities + newPriorities := []int64{protectedBranches[2].ID, protectedBranches[0].ID, protectedBranches[1].ID} + err := UpdateProtectBranchPriorities(t.Context(), repo, newPriorities) assert.NoError(t, err) - } - // Test updating priorities - newPriorities := []int64{protectedBranches[2].ID, protectedBranches[0].ID, protectedBranches[1].ID} - err := UpdateProtectBranchPriorities(t.Context(), repo, newPriorities) - assert.NoError(t, err) + // Verify new priorities + pbs, err := FindRepoProtectedBranchRules(t.Context(), repo.ID) + assert.NoError(t, err) - // Verify new priorities - pbs, err := FindRepoProtectedBranchRules(t.Context(), repo.ID) - assert.NoError(t, err) + expectedPriorities := map[string]int64{ + "feature/*": 1, + "master": 2, + "develop": 3, + } - expectedPriorities := map[string]int64{ - "feature/*": 1, - "master": 2, - "develop": 3, - } - - for _, pb := range pbs { - assert.Equal(t, expectedPriorities[pb.RuleName], pb.Priority) - } + for _, pb := range pbs { + assert.Equal(t, expectedPriorities[pb.RuleName], pb.Priority) + } + }) } func TestNewProtectBranchPriority(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + setupErr := unittest.PrepareTestDatabase() - err := UpdateProtectBranch(t.Context(), repo, &ProtectedBranch{ - RepoID: repo.ID, - RuleName: "branch-1", - Priority: 1, - }, WhitelistOptions{}) - assert.NoError(t, err) + t.Run("NewProtectBranchPriority", func(t *testing.T) { + assert.NoError(t, setupErr) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - newPB := &ProtectedBranch{ - RepoID: repo.ID, - RuleName: "branch-2", - // Priority intentionally omitted - } + err := UpdateProtectBranch(t.Context(), repo, &ProtectedBranch{ + RepoID: repo.ID, + RuleName: "branch-1", + Priority: 1, + }, WhitelistOptions{}) + assert.NoError(t, err) - err = UpdateProtectBranch(t.Context(), repo, newPB, WhitelistOptions{}) - assert.NoError(t, err) + newPB := &ProtectedBranch{ + RepoID: repo.ID, + RuleName: "branch-2", + // Priority intentionally omitted + } - savedPB2, err := GetFirstMatchProtectedBranchRule(t.Context(), repo.ID, "branch-2") - assert.NoError(t, err) - assert.Equal(t, int64(2), savedPB2.Priority) -} - -func TestCanBypassBranchProtection(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) // not in team 1 - teamMember := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - pb := &ProtectedBranch{ - EnableBypassAllowlist: true, - BypassAllowlistUserIDs: []int64{user.ID}, - } - - testBypass := func(t *testing.T, expected bool, pb *ProtectedBranch, doer *user_model.User, isAdmin bool) { - assert.Equal(t, expected, CanBypassBranchProtection(t.Context(), pb, doer, isAdmin)) - } - // User bypasses via explicit allowlist. - testBypass(t, true, pb, user, false) - - // Non-admin cannot bypass when allowlist is disabled. - pb.EnableBypassAllowlist = false - testBypass(t, false, pb, user, false) - - // Repo admin can bypass independently of allowlist when not blocked. - testBypass(t, true, pb, user, true) - - // Admin override block still allows bypass for allowlisted users. - pb.EnableBypassAllowlist = true - pb.BlockAdminMergeOverride = true - testBypass(t, true, pb, user, false) - - // admin cannot bypass without allowlist membership. - pb.BypassAllowlistUserIDs = nil - testBypass(t, false, pb, user, true) - - // admin can bypass when allowlisted. - pb.BypassAllowlistUserIDs = []int64{user.ID} - testBypass(t, true, pb, user, true) - - // User bypasses via team allowlist membership. - pb.EnableBypassAllowlist = true - pb.BlockAdminMergeOverride = false - pb.BypassAllowlistUserIDs = nil - pb.BypassAllowlistTeamIDs = []int64{1} // team 1 contains user 2 in test fixtures - testBypass(t, true, pb, teamMember, false) - - // User does not bypass when not in allowlisted teams. - testBypass(t, false, pb, user, false) -} - -func TestProtectedBranchConfig(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() - pb := &ProtectedBranch{ - RepoID: 1, RuleName: "config-test", - ProtectedBranchConfig: ProtectedBranchConfig{ - CanDelete: true, EnableDeletionAllowlist: true, - DeletionAllowlistUserIDs: []int64{2, 4, user_model.ActionsUserID, user_model.DeployKeyUserID}, DeletionAllowlistTeamIDs: []int64{1, 2}, - CanPush: true, WhitelistUserIDs: []int64{2, 4}, WhitelistTeamIDs: []int64{1, 2}, StatusCheckContexts: []string{}, - }, - } - require.NoError(t, db.Insert(ctx, pb)) - loaded := unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID}) - assert.Equal(t, pb.ProtectedBranchConfig, loaded.ProtectedBranchConfig) - require.NoError(t, RemoveUserIDFromProtectedBranch(ctx, loaded, 2)) - require.NoError(t, RemoveTeamIDFromProtectedBranch(ctx, loaded, 1)) - loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID}) - assert.Equal(t, []int64{4}, loaded.WhitelistUserIDs) - assert.Equal(t, []int64{2}, loaded.WhitelistTeamIDs) - assert.True(t, loaded.CanPush) - assert.True(t, loaded.CanDelete) - assert.True(t, loaded.EnableDeletionAllowlist) - assert.Equal(t, []int64{4, user_model.ActionsUserID, user_model.DeployKeyUserID}, loaded.DeletionAllowlistUserIDs) - assert.Equal(t, []int64{2}, loaded.DeletionAllowlistTeamIDs) - loaded.ProtectedBranchConfig = ProtectedBranchConfig{} - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - require.NoError(t, UpdateProtectBranch(ctx, repo, loaded, WhitelistOptions{})) - loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID}) - assert.Equal(t, ProtectedBranchConfig{}, loaded.ProtectedBranchConfig) -} - -func TestProtectedBranchCanUserDelete(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - writePermission := access_model.Permission{} - writePermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeWrite) - readPermission := access_model.Permission{} - readPermission.SetUnitsWithDefaultAccessMode([]*repo_model.RepoUnit{{Type: unit.TypeCode}}, perm.AccessModeRead) - - for _, user := range []*user_model.User{{ID: 2, Name: "user2"}, user_model.NewActionsUser(), user_model.NewDeployKeyUser()} { - t.Run(user.GetDisplayName(), func(t *testing.T) { - for _, tt := range []struct { - name string - config ProtectedBranchConfig - want bool - }{ - {name: "disabled by default", config: ProtectedBranchConfig{CanPush: true}}, - {name: "push disabled", config: ProtectedBranchConfig{CanDelete: true}}, - {name: "unrestricted", config: ProtectedBranchConfig{CanPush: true, CanDelete: true}, want: true}, - {name: "empty push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true}}, - {name: "not in push allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{1}}}, - {name: "empty deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true}}, - {name: "not in deletion allowlist", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{1}}}, - {name: "in both allowlists", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistUserIDs: []int64{user.ID}, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}, want: true}, - {name: "deletion allowlist does not grant push", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}}}, - {name: "team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{1}}, want: user.ID == 2}, - {name: "push team member", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableWhitelist: true, WhitelistTeamIDs: []int64{1}}, want: user.ID == 2}, - {name: "not in team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistTeamIDs: []int64{3}}}, - {name: "user or team", config: ProtectedBranchConfig{CanPush: true, CanDelete: true, EnableDeletionAllowlist: true, DeletionAllowlistUserIDs: []int64{user.ID}, DeletionAllowlistTeamIDs: []int64{3}}, want: true}, - } { - t.Run(tt.name, func(t *testing.T) { - pb := &ProtectedBranch{ProtectedBranchConfig: tt.config} - assert.Equal(t, tt.want, pb.CanUserDelete(t.Context(), user, writePermission)) - assert.False(t, pb.CanUserDelete(t.Context(), user, readPermission)) - assert.False(t, pb.CanUserDelete(t.Context(), nil, writePermission)) - }) - } - }) - } + err = UpdateProtectBranch(t.Context(), repo, newPB, WhitelistOptions{}) + assert.NoError(t, err) + + savedPB2, err := GetFirstMatchProtectedBranchRule(t.Context(), repo.ID, "branch-2") + assert.NoError(t, err) + assert.Equal(t, int64(2), savedPB2.Priority) + }) + + t.Run("ProtectedBranchConfig", func(t *testing.T) { + require.NoError(t, setupErr) + ctx := t.Context() + pb := &ProtectedBranch{ + RepoID: 1, RuleName: "config-test", + ProtectedBranchConfig: ProtectedBranchConfig{ + CanDelete: true, EnableDeletionAllowlist: true, + DeletionAllowlistUserIDs: []int64{2, 4, user_model.ActionsUserID, user_model.DeployKeyUserID}, DeletionAllowlistTeamIDs: []int64{1, 2}, + CanPush: true, WhitelistUserIDs: []int64{2, 4}, WhitelistTeamIDs: []int64{1, 2}, StatusCheckContexts: []string{}, + }, + } + require.NoError(t, db.Insert(ctx, pb)) + loaded := unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID}) + assert.Equal(t, pb.ProtectedBranchConfig, loaded.ProtectedBranchConfig) + require.NoError(t, RemoveUserIDFromProtectedBranch(ctx, loaded, 2)) + require.NoError(t, RemoveTeamIDFromProtectedBranch(ctx, loaded, 1)) + loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID}) + assert.Equal(t, []int64{4}, loaded.WhitelistUserIDs) + assert.Equal(t, []int64{2}, loaded.WhitelistTeamIDs) + assert.True(t, loaded.CanPush) + assert.True(t, loaded.CanDelete) + assert.True(t, loaded.EnableDeletionAllowlist) + assert.Equal(t, []int64{4, user_model.ActionsUserID, user_model.DeployKeyUserID}, loaded.DeletionAllowlistUserIDs) + assert.Equal(t, []int64{2}, loaded.DeletionAllowlistTeamIDs) + loaded.ProtectedBranchConfig = ProtectedBranchConfig{} + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + require.NoError(t, UpdateProtectBranch(ctx, repo, loaded, WhitelistOptions{})) + loaded = unittest.AssertExistsAndLoadBean(t, &ProtectedBranch{ID: pb.ID}) + assert.Equal(t, ProtectedBranchConfig{}, loaded.ProtectedBranchConfig) + }) } diff --git a/models/git/protected_tag_test.go b/models/git/protected_tag_test.go index a0400c9bfa1..46d5dfc9ca3 100644 --- a/models/git/protected_tag_test.go +++ b/models/git/protected_tag_test.go @@ -15,44 +15,31 @@ import ( func TestIsUserAllowed(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - pt := &git_model.ProtectedTag{} - allowed, err := git_model.IsUserAllowedModifyTag(t.Context(), pt, 1) - assert.NoError(t, err) - assert.False(t, allowed) + checkAllowed := func(pt *git_model.ProtectedTag, userID int64, check assert.BoolAssertionFunc) { + allowed, err := git_model.IsUserAllowedModifyTag(t.Context(), pt, userID) + assert.NoError(t, err) + check(t, allowed) + } + checkAllowed(&git_model.ProtectedTag{}, 1, assert.False) - pt = &git_model.ProtectedTag{ + pt := &git_model.ProtectedTag{ AllowlistUserIDs: []int64{1}, } - allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 1) - assert.NoError(t, err) - assert.True(t, allowed) - - allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 2) - assert.NoError(t, err) - assert.False(t, allowed) + checkAllowed(pt, 1, assert.True) + checkAllowed(pt, 2, assert.False) pt = &git_model.ProtectedTag{ AllowlistTeamIDs: []int64{1}, } - allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 1) - assert.NoError(t, err) - assert.False(t, allowed) - - allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 2) - assert.NoError(t, err) - assert.True(t, allowed) + checkAllowed(pt, 1, assert.False) + checkAllowed(pt, 2, assert.True) pt = &git_model.ProtectedTag{ AllowlistUserIDs: []int64{1}, AllowlistTeamIDs: []int64{1}, } - allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 1) - assert.NoError(t, err) - assert.True(t, allowed) - - allowed, err = git_model.IsUserAllowedModifyTag(t.Context(), pt, 2) - assert.NoError(t, err) - assert.True(t, allowed) + checkAllowed(pt, 1, assert.True) + checkAllowed(pt, 2, assert.True) } func TestIsUserAllowedToControlTag(t *testing.T) { @@ -118,47 +105,33 @@ func TestIsUserAllowedToControlTag(t *testing.T) { }, } - t.Run("Glob", func(t *testing.T) { - protectedTags := []*git_model.ProtectedTag{ - { - NamePattern: `*gitea`, - AllowlistUserIDs: []int64{1}, - }, - { - NamePattern: `v-*`, - AllowlistUserIDs: []int64{2}, - }, - { - NamePattern: "release", - }, - } + for _, tc := range []struct { + name string + patterns []string + }{ + {"Glob", []string{`*gitea`, `v-*`, "release"}}, + {"Regex", []string{`/gitea\z/`, `/\Av-/`, "/release/"}}, + } { + t.Run(tc.name, func(t *testing.T) { + protectedTags := []*git_model.ProtectedTag{ + { + NamePattern: tc.patterns[0], + AllowlistUserIDs: []int64{1}, + }, + { + NamePattern: tc.patterns[1], + AllowlistUserIDs: []int64{2}, + }, + { + NamePattern: tc.patterns[2], + }, + } - for n, c := range cases { - isAllowed, err := git_model.IsUserAllowedToControlTag(t.Context(), protectedTags, c.name, c.userid) - assert.NoError(t, err) - assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n) - } - }) - - t.Run("Regex", func(t *testing.T) { - protectedTags := []*git_model.ProtectedTag{ - { - NamePattern: `/gitea\z/`, - AllowlistUserIDs: []int64{1}, - }, - { - NamePattern: `/\Av-/`, - AllowlistUserIDs: []int64{2}, - }, - { - NamePattern: "/release/", - }, - } - - for n, c := range cases { - isAllowed, err := git_model.IsUserAllowedToControlTag(t.Context(), protectedTags, c.name, c.userid) - assert.NoError(t, err) - assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n) - } - }) + for n, c := range cases { + isAllowed, err := git_model.IsUserAllowedToControlTag(t.Context(), protectedTags, c.name, c.userid) + assert.NoError(t, err) + assert.Equal(t, c.allowed, isAllowed, "case %d: error should match", n) + } + }) + } } diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go index aa63956a011..0113151e9b2 100644 --- a/models/issues/comment_test.go +++ b/models/issues/comment_test.go @@ -19,162 +19,177 @@ import ( ) func TestCreateComment(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + prepareErr := unittest.PrepareTestDatabase() - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + t.Run("LoadAssigneeUserAndTeam_DeletedTeamBecomesGhostTeam", func(t *testing.T) { + assert.NoError(t, prepareErr) - now := time.Now().Unix() - comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{ - Type: issues_model.CommentTypeComment, - Doer: doer, - Repo: repo, - Issue: issue, - Content: "Hello", - }) - assert.NoError(t, err) - then := time.Now().Unix() - - assert.Equal(t, issues_model.CommentTypeComment, comment.Type) - assert.Equal(t, "Hello", comment.Content) - assert.Equal(t, issue.ID, comment.IssueID) - assert.Equal(t, doer.ID, comment.PosterID) - unittest.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix)) - unittest.AssertExistsAndLoadBean(t, comment) // assert actually added to DB - - updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}) - unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) - assert.Nil(t, comment.CommentMetaData) - - ctx := issues_model.WithProjectWorkflow(t.Context(), project_model.WorkflowEventItemClosed) - comment, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{ - Type: issues_model.CommentTypeProjectColumn, - Doer: doer, - Repo: repo, - Issue: issue, - ProjectColumnID: 5, - ProjectColumnTitle: "Done", - ProjectTitle: "Board", - }) - require.NoError(t, err) - assert.Equal(t, &issues_model.CommentMetaData{ - ProjectColumnID: 5, - ProjectColumnTitle: "Done", - ProjectTitle: "Board", - ProjectWorkflowEvent: project_model.WorkflowEventItemClosed, - SpecialDoerName: issues_model.SpecialDoerNameProjectWorkflow, - }, unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID}).CommentMetaData) -} - -func TestLoadAssigneeUserAndTeam_DeletedTeamBecomesGhostTeam(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 15}) - comment := &issues_model.Comment{ - Type: issues_model.CommentTypeAssignees, - IssueID: issue.ID, - AssigneeTeamID: 999999, // non-existing team ID - } - assert.NoError(t, comment.LoadAssigneeUserAndTeam(t.Context())) - assert.NotNil(t, comment.AssigneeTeam) - assert.EqualValues(t, -1, comment.AssigneeTeam.ID) -} - -func TestCommentListLoadAttributesMixedAssignees(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - first := &issues_model.Comment{ - ID: 1, PosterID: 1, IssueID: 1, LabelID: 1, MilestoneID: 1, OldMilestoneID: 1, AssigneeID: 1, DependentIssueID: 2, - } - last := &issues_model.Comment{ - ID: 2, PosterID: 2, IssueID: 4, LabelID: 2, MilestoneID: 3, OldMilestoneID: 3, AssigneeID: 2, DependentIssueID: 1, - } - comments := issues_model.CommentList{ - {AssigneeTeamID: 8}, - {AssigneeID: 999999}, - first, - } - for i := range db.DefaultMaxInSize { - id := unittest.NonexistentID - int64(i) - comments = append(comments, &issues_model.Comment{ - ID: id, PosterID: id, IssueID: id, LabelID: id, MilestoneID: id, OldMilestoneID: id, AssigneeID: id, DependentIssueID: id, - }) - } - comments = append(comments, last) - - require.NoError(t, comments.LoadAttributes(t.Context())) - assert.Nil(t, comments[0].Assignee) - assert.Zero(t, comments[0].AssigneeID) - assert.Equal(t, user_model.GhostUserID, comments[1].AssigneeID) - assert.Equal(t, user_model.GhostUserID, comments[1].Assignee.ID) - for _, comment := range []*issues_model.Comment{first, last} { - require.NotNil(t, comment.Poster) - assert.Equal(t, comment.PosterID, comment.Poster.ID) - require.NotNil(t, comment.Issue) - assert.Equal(t, comment.IssueID, comment.Issue.ID) - require.NotNil(t, comment.Label) - assert.Equal(t, comment.LabelID, comment.Label.ID) - require.NotNil(t, comment.Milestone) - assert.Equal(t, comment.MilestoneID, comment.Milestone.ID) - require.NotNil(t, comment.OldMilestone) - assert.Equal(t, comment.OldMilestoneID, comment.OldMilestone.ID) - require.NotNil(t, comment.Assignee) - assert.Equal(t, comment.AssigneeID, comment.Assignee.ID) - require.NotNil(t, comment.DependentIssue) - assert.Equal(t, comment.DependentIssueID, comment.DependentIssue.ID) - require.NotNil(t, comment.DependentIssue.Repo) - assert.Equal(t, comment.DependentIssue.RepoID, comment.DependentIssue.Repo.ID) - require.Len(t, comment.Attachments, 2) - for _, attachment := range comment.Attachments { - assert.Equal(t, comment.ID, attachment.CommentID) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 15}) + comment := &issues_model.Comment{ + Type: issues_model.CommentTypeAssignees, + IssueID: issue.ID, + AssigneeTeamID: 999999, // non-existing team ID } - } - missing := comments[3] - assert.Equal(t, user_model.GhostUserID, missing.Poster.ID) - assert.Equal(t, user_model.GhostUserID, missing.Assignee.ID) - assert.Nil(t, missing.Issue) - assert.Nil(t, missing.Label) - assert.Nil(t, missing.Milestone) - assert.Nil(t, missing.OldMilestone) - assert.Nil(t, missing.DependentIssue) - assert.Empty(t, missing.Attachments) -} + assert.NoError(t, comment.LoadAssigneeUserAndTeam(t.Context())) + assert.NotNil(t, comment.AssigneeTeam) + assert.EqualValues(t, -1, comment.AssigneeTeam.ID) + }) -func Test_UpdateCommentAttachment(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("CommentListLoadAttributesMixedAssignees", func(t *testing.T) { + require.NoError(t, prepareErr) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - attachment := repo_model.Attachment{ - RepoID: issue.RepoID, // must match the comment's repo, else the cross-repo guard rejects it - Name: "test.txt", - } - assert.NoError(t, db.Insert(t.Context(), &attachment)) + first := &issues_model.Comment{ + ID: 1, PosterID: 1, IssueID: 1, LabelID: 1, MilestoneID: 1, OldMilestoneID: 1, AssigneeID: 1, DependentIssueID: 2, + } + last := &issues_model.Comment{ + ID: 2, PosterID: 2, IssueID: 4, LabelID: 2, MilestoneID: 3, OldMilestoneID: 3, AssigneeID: 2, DependentIssueID: 1, + } + comments := issues_model.CommentList{ + {AssigneeTeamID: 8}, + {AssigneeID: 999999}, + first, + } + for i := range db.DefaultMaxInSize { + id := unittest.NonexistentID - int64(i) + comments = append(comments, &issues_model.Comment{ + ID: id, PosterID: id, IssueID: id, LabelID: id, MilestoneID: id, OldMilestoneID: id, AssigneeID: id, DependentIssueID: id, + }) + } + comments = append(comments, last) - err := issues_model.UpdateCommentAttachments(t.Context(), comment, []string{attachment.UUID}) - assert.NoError(t, err) + require.NoError(t, comments.LoadAttributes(t.Context())) + assert.Nil(t, comments[0].Assignee) + assert.Zero(t, comments[0].AssigneeID) + assert.Equal(t, user_model.GhostUserID, comments[1].AssigneeID) + assert.Equal(t, user_model.GhostUserID, comments[1].Assignee.ID) + for _, comment := range []*issues_model.Comment{first, last} { + require.NotNil(t, comment.Poster) + assert.Equal(t, comment.PosterID, comment.Poster.ID) + require.NotNil(t, comment.Issue) + assert.Equal(t, comment.IssueID, comment.Issue.ID) + require.NotNil(t, comment.Label) + assert.Equal(t, comment.LabelID, comment.Label.ID) + require.NotNil(t, comment.Milestone) + assert.Equal(t, comment.MilestoneID, comment.Milestone.ID) + require.NotNil(t, comment.OldMilestone) + assert.Equal(t, comment.OldMilestoneID, comment.OldMilestone.ID) + require.NotNil(t, comment.Assignee) + assert.Equal(t, comment.AssigneeID, comment.Assignee.ID) + require.NotNil(t, comment.DependentIssue) + assert.Equal(t, comment.DependentIssueID, comment.DependentIssue.ID) + require.NotNil(t, comment.DependentIssue.Repo) + assert.Equal(t, comment.DependentIssue.RepoID, comment.DependentIssue.Repo.ID) + require.Len(t, comment.Attachments, 2) + for _, attachment := range comment.Attachments { + assert.Equal(t, comment.ID, attachment.CommentID) + } + } + missing := comments[3] + assert.Equal(t, user_model.GhostUserID, missing.Poster.ID) + assert.Equal(t, user_model.GhostUserID, missing.Assignee.ID) + assert.Nil(t, missing.Issue) + assert.Nil(t, missing.Label) + assert.Nil(t, missing.Milestone) + assert.Nil(t, missing.OldMilestone) + assert.Nil(t, missing.DependentIssue) + assert.Empty(t, missing.Attachments) + }) - attachment2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID}) - assert.Equal(t, attachment.Name, attachment2.Name) - assert.Equal(t, comment.ID, attachment2.CommentID) - assert.Equal(t, comment.IssueID, attachment2.IssueID) -} + t.Run("FetchCodeComments", func(t *testing.T) { + assert.NoError(t, prepareErr) -func TestFetchCodeComments(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + res, err := issues_model.FetchCodeComments(t.Context(), issue, user, false) + assert.NoError(t, err) + assert.Contains(t, res, "README.md") + assert.Contains(t, res["README.md"], int64(4)) + assert.Len(t, res["README.md"][4], 1) + assert.Equal(t, int64(4), res["README.md"][4][0].ID) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - res, err := issues_model.FetchCodeComments(t.Context(), issue, user, false) - assert.NoError(t, err) - assert.Contains(t, res, "README.md") - assert.Contains(t, res["README.md"], int64(4)) - assert.Len(t, res["README.md"][4], 1) - assert.Equal(t, int64(4), res["README.md"][4][0].ID) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + res, err = issues_model.FetchCodeComments(t.Context(), issue, user2, false) + assert.NoError(t, err) + assert.Len(t, res, 1) + }) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - res, err = issues_model.FetchCodeComments(t.Context(), issue, user2, false) - assert.NoError(t, err) - assert.Len(t, res, 1) + t.Run("UpdateCommentAttachment", func(t *testing.T) { + assert.NoError(t, prepareErr) + + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + attachment := repo_model.Attachment{ + RepoID: issue.RepoID, // must match the comment's repo, else the cross-repo guard rejects it + Name: "test.txt", + } + assert.NoError(t, db.Insert(t.Context(), &attachment)) + + err := issues_model.UpdateCommentAttachments(t.Context(), comment, []string{attachment.UUID}) + assert.NoError(t, err) + + attachment2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID}) + assert.Equal(t, attachment.Name, attachment2.Name) + assert.Equal(t, comment.ID, attachment2.CommentID) + assert.Equal(t, comment.IssueID, attachment2.IssueID) + }) + + t.Run("UpdateIssueNumComments", func(t *testing.T) { + assert.NoError(t, prepareErr) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + + assert.NoError(t, issues_model.UpdateIssueNumComments(t.Context(), issue2.ID)) + issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.Equal(t, 1, issue2.NumComments) + }) + + t.Run("CreateComment", func(t *testing.T) { + assert.NoError(t, prepareErr) + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + now := time.Now().Unix() + comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeComment, + Doer: doer, + Repo: repo, + Issue: issue, + Content: "Hello", + }) + assert.NoError(t, err) + then := time.Now().Unix() + + assert.Equal(t, issues_model.CommentTypeComment, comment.Type) + assert.Equal(t, "Hello", comment.Content) + assert.Equal(t, issue.ID, comment.IssueID) + assert.Equal(t, doer.ID, comment.PosterID) + unittest.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix)) + unittest.AssertExistsAndLoadBean(t, comment) // assert actually added to DB + + updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}) + unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) + assert.Nil(t, comment.CommentMetaData) + + ctx := issues_model.WithProjectWorkflow(t.Context(), project_model.WorkflowEventItemClosed) + comment, err = issues_model.CreateComment(ctx, &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeProjectColumn, + Doer: doer, + Repo: repo, + Issue: issue, + ProjectColumnID: 5, + ProjectColumnTitle: "Done", + ProjectTitle: "Board", + }) + require.NoError(t, err) + assert.Equal(t, &issues_model.CommentMetaData{ + ProjectColumnID: 5, + ProjectColumnTitle: "Done", + ProjectTitle: "Board", + ProjectWorkflowEvent: project_model.WorkflowEventItemClosed, + SpecialDoerName: issues_model.SpecialDoerNameProjectWorkflow, + }, unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID}).CommentMetaData) + }) } func TestAsCommentType(t *testing.T) { @@ -211,12 +226,3 @@ func TestMigrate_InsertIssueComments(t *testing.T) { unittest.CheckConsistencyFor(t, &issues_model.Issue{}) } - -func Test_UpdateIssueNumComments(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - - assert.NoError(t, issues_model.UpdateIssueNumComments(t.Context(), issue2.ID)) - issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.Equal(t, 1, issue2.NumComments) -} diff --git a/models/issues/issue_list_test.go b/models/issues/issue_list_test.go index 1d8f8febd62..864f370b82c 100644 --- a/models/issues/issue_list_test.go +++ b/models/issues/issue_list_test.go @@ -17,111 +17,117 @@ import ( ) func TestIssueList_LoadRepositories(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + prepareErr := unittest.PrepareTestDatabase() - issueList := issues_model.IssueList{ - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}), - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}), - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}), - } + t.Run("LoadRepositories", func(t *testing.T) { + assert.NoError(t, prepareErr) - repos, err := issueList.LoadRepositories(t.Context()) - assert.NoError(t, err) - assert.Len(t, repos, 2) - for _, issue := range issueList { - assert.Equal(t, issue.RepoID, issue.Repo.ID) - } -} - -func TestIssueList_LoadIsRead(t *testing.T) { - // Regression: In("issue_id") was missing the issueIDs argument, causing - // xorm to generate "0=1" and never mark any issue as read. - require.NoError(t, unittest.PrepareTestDatabase()) - - issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - - // Fixture: uid=1 has is_read=true on issue 1 only. - issueList := issues_model.IssueList{issue1, issue2} - require.NoError(t, issueList.LoadIsRead(t.Context(), 1)) - - assert.True(t, issue1.IsRead, "issue 1 should be marked read for user 1") - assert.False(t, issue2.IsRead, "issue 2 should not be marked read for user 1") -} - -func TestIssueList_LoadAttributes(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - defer test.MockVariableValue(&setting.Service.EnableTimetracking, true)() - fixtureIssues := issues_model.IssueList{ - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}), - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}), - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}), - } - issueList := issues_model.IssueList{fixtureIssues[0]} - for i := range db.DefaultMaxInSize { - issueList = append(issueList, &issues_model.Issue{ID: int64(1000 + i), RepoID: 1, IsPull: true}) - } - issueList = append(issueList, fixtureIssues[1:]...) - - require.NoError(t, issueList.LoadAttributes(t.Context())) - require.NoError(t, issueList.LoadAttachments(t.Context())) - require.NoError(t, issueList.LoadComments(t.Context())) - assert.Len(t, fixtureIssues[0].Labels, 1) - assert.Len(t, fixtureIssues[1].Labels, 2) - assert.Len(t, fixtureIssues[0].Assignees, 1) - assert.Empty(t, fixtureIssues[1].Assignees) - assert.Len(t, fixtureIssues[0].Attachments, 1) - assert.Len(t, fixtureIssues[1].Attachments, 1) - assert.Len(t, fixtureIssues[2].Attachments, 2) - assert.Len(t, fixtureIssues[0].Comments, 3) - assert.Len(t, fixtureIssues[1].Comments, 4) - assert.Len(t, fixtureIssues[2].Comments, 1) - for _, issue := range fixtureIssues { - assert.Equal(t, issue.RepoID, issue.Repo.ID) - for _, label := range issue.Labels { - assert.Equal(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: label.ID}), label) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) - } - if issue.PosterID > 0 { - assert.Equal(t, issue.PosterID, issue.Poster.ID) - } - if issue.AssigneeID > 0 { - assert.Equal(t, issue.AssigneeID, issue.Assignee.ID) - } - if issue.MilestoneID > 0 { - assert.Equal(t, issue.MilestoneID, issue.Milestone.ID) - } - if issue.IsPull { - require.NotNil(t, issue.PullRequest) - assert.Equal(t, issue.ID, issue.PullRequest.IssueID) - assert.Same(t, issue, issue.PullRequest.Issue) - } - for _, attachment := range issue.Attachments { - assert.Equal(t, issue.ID, attachment.IssueID) - } - for _, comment := range issue.Comments { - assert.Equal(t, issue.ID, comment.IssueID) - } - if issue.ID == int64(1) { - assert.Equal(t, int64(400), issue.TotalTrackedTime) - } - if issue.ID == 1 || issue.ID == 2 { - require.Len(t, issue.Projects, 1) - assert.Equal(t, int64(1), issue.Projects[0].ID) - } else { - assert.Empty(t, issue.Projects) - } - } - assert.Equal(t, int64(3682), fixtureIssues[1].TotalTrackedTime) - - require.NoError(t, issueList.LoadDiscussComments(t.Context())) - assert.Len(t, fixtureIssues[0].Comments, 2) - assert.Empty(t, fixtureIssues[1].Comments) - assert.Len(t, fixtureIssues[2].Comments, 1) - for _, issue := range fixtureIssues { - for _, comment := range issue.Comments { - assert.Equal(t, issue.ID, comment.IssueID) - assert.Equal(t, issues_model.CommentTypeComment, comment.Type) - } - } + issueList := issues_model.IssueList{ + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}), + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}), + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}), + } + + repos, err := issueList.LoadRepositories(t.Context()) + assert.NoError(t, err) + assert.Len(t, repos, 2) + for _, issue := range issueList { + assert.Equal(t, issue.RepoID, issue.Repo.ID) + } + }) + + t.Run("LoadIsRead", func(t *testing.T) { + require.NoError(t, prepareErr) + + // Regression: In("issue_id") was missing the issueIDs argument, causing + // xorm to generate "0=1" and never mark any issue as read. + + issue1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + + // Fixture: uid=1 has is_read=true on issue 1 only. + issueList := issues_model.IssueList{issue1, issue2} + require.NoError(t, issueList.LoadIsRead(t.Context(), 1)) + + assert.True(t, issue1.IsRead, "issue 1 should be marked read for user 1") + assert.False(t, issue2.IsRead, "issue 2 should not be marked read for user 1") + }) + + t.Run("LoadAttributes", func(t *testing.T) { + require.NoError(t, prepareErr) + + defer test.MockVariableValue(&setting.Service.EnableTimetracking, true)() + fixtureIssues := issues_model.IssueList{ + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}), + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}), + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 4}), + } + issueList := issues_model.IssueList{fixtureIssues[0]} + for i := range db.DefaultMaxInSize { + issueList = append(issueList, &issues_model.Issue{ID: int64(1000 + i), RepoID: 1, IsPull: true}) + } + issueList = append(issueList, fixtureIssues[1:]...) + + require.NoError(t, issueList.LoadAttributes(t.Context())) + require.NoError(t, issueList.LoadAttachments(t.Context())) + require.NoError(t, issueList.LoadComments(t.Context())) + assert.Len(t, fixtureIssues[0].Labels, 1) + assert.Len(t, fixtureIssues[1].Labels, 2) + assert.Len(t, fixtureIssues[0].Assignees, 1) + assert.Empty(t, fixtureIssues[1].Assignees) + assert.Len(t, fixtureIssues[0].Attachments, 1) + assert.Len(t, fixtureIssues[1].Attachments, 1) + assert.Len(t, fixtureIssues[2].Attachments, 2) + assert.Len(t, fixtureIssues[0].Comments, 3) + assert.Len(t, fixtureIssues[1].Comments, 4) + assert.Len(t, fixtureIssues[2].Comments, 1) + for _, issue := range fixtureIssues { + assert.Equal(t, issue.RepoID, issue.Repo.ID) + for _, label := range issue.Labels { + assert.Equal(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: label.ID}), label) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) + } + if issue.PosterID > 0 { + assert.Equal(t, issue.PosterID, issue.Poster.ID) + } + if issue.AssigneeID > 0 { + assert.Equal(t, issue.AssigneeID, issue.Assignee.ID) + } + if issue.MilestoneID > 0 { + assert.Equal(t, issue.MilestoneID, issue.Milestone.ID) + } + if issue.IsPull { + require.NotNil(t, issue.PullRequest) + assert.Equal(t, issue.ID, issue.PullRequest.IssueID) + assert.Same(t, issue, issue.PullRequest.Issue) + } + for _, attachment := range issue.Attachments { + assert.Equal(t, issue.ID, attachment.IssueID) + } + for _, comment := range issue.Comments { + assert.Equal(t, issue.ID, comment.IssueID) + } + if issue.ID == int64(1) { + assert.Equal(t, int64(400), issue.TotalTrackedTime) + } + if issue.ID == 1 || issue.ID == 2 { + require.Len(t, issue.Projects, 1) + assert.Equal(t, int64(1), issue.Projects[0].ID) + } else { + assert.Empty(t, issue.Projects) + } + } + assert.Equal(t, int64(3682), fixtureIssues[1].TotalTrackedTime) + + require.NoError(t, issueList.LoadDiscussComments(t.Context())) + assert.Len(t, fixtureIssues[0].Comments, 2) + assert.Empty(t, fixtureIssues[1].Comments) + assert.Len(t, fixtureIssues[2].Comments, 1) + for _, issue := range fixtureIssues { + for _, comment := range issue.Comments { + assert.Equal(t, issue.ID, comment.IssueID) + assert.Equal(t, issues_model.CommentTypeComment, comment.Type) + } + } + }) } diff --git a/models/issues/issue_project_multi_test.go b/models/issues/issue_project_multi_test.go index 8023ee67d63..5b951db7aab 100644 --- a/models/issues/issue_project_multi_test.go +++ b/models/issues/issue_project_multi_test.go @@ -44,19 +44,14 @@ func TestIssueMultipleProjects(t *testing.T) { require.Empty(t, issue1.Projects) // assign issue to both projects (each project uses its own default column) - _, _, err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue1, user2, []int64{project1.ID}) - require.NoError(t, err) - assert.Nilf(t, issue1.Projects, "Issue's Projects should be nil after IssueAssignOrRemoveProject to ensure it reloads fresh data") - err = issue1.LoadProjects(t.Context()) - require.NoError(t, err) - require.Len(t, issue1.Projects, 1) - - _, _, err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue1, user2, []int64{project1.ID, project2.ID}) - require.NoError(t, err) - assert.Nilf(t, issue1.Projects, "Issue's Projects should be nil after IssueAssignOrRemoveProject to ensure it reloads fresh data") - err = issue1.LoadProjects(t.Context()) - require.NoError(t, err) - require.Len(t, issue1.Projects, 2) + for _, projectIDs := range [][]int64{{project1.ID}, {project1.ID, project2.ID}} { + _, _, err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue1, user2, projectIDs) + require.NoError(t, err) + assert.Nilf(t, issue1.Projects, "Issue's Projects should be nil after IssueAssignOrRemoveProject to ensure it reloads fresh data") + err = issue1.LoadProjects(t.Context()) + require.NoError(t, err) + require.Len(t, issue1.Projects, len(projectIDs)) + } assert.ElementsMatch(t, []int64{project1.ID, project2.ID}, []int64{issue1.Projects[0].ID, issue1.Projects[1].ID}, "Issue should be in both projects") // test issue's project column map diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index 2227ae26a96..ebf736dbfc6 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -53,79 +53,211 @@ func TestIssue_ReplaceLabels(t *testing.T) { testSuccess(18, []int64{6, 8, 7}, []int64{6, 7}) } -func Test_GetIssueIDsByRepoID(t *testing.T) { +func TestIssueQueries(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - ids, err := issues_model.GetIssueIDsByRepoID(t.Context(), 1) - assert.NoError(t, err) - assert.Len(t, ids, 5) -} - -func TestIssueAPIURL(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - err := issue.LoadAttributes(t.Context()) - - assert.NoError(t, err) - assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL(t.Context())) -} - -func TestGetIssuesByIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) { - issues, err := issues_model.GetIssuesByIDs(t.Context(), append(expectedIssueIDs, nonExistentIssueIDs...), true) + t.Run("GetIssueIDsByRepoID", func(t *testing.T) { + ids, err := issues_model.GetIssueIDsByRepoID(t.Context(), 1) assert.NoError(t, err) - actualIssueIDs := make([]int64, len(issues)) - for i, issue := range issues { - actualIssueIDs[i] = issue.ID - } - assert.Equal(t, expectedIssueIDs, actualIssueIDs) - } - testSuccess([]int64{1, 2, 3}, []int64{}) - testSuccess([]int64{1, 2, 3}, []int64{unittest.NonexistentID}) - testSuccess([]int64{3, 2, 1}, []int64{}) -} + assert.Len(t, ids, 5) + }) -func TestGetParticipantIDsByIssue(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("IssueAPIURL", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + err := issue.LoadAttributes(t.Context()) - checkParticipants := func(issueID int64, userIDs []int) { - issue, err := issues_model.GetIssueByID(t.Context(), issueID) assert.NoError(t, err) - participants, err := issue.GetParticipantIDsByIssue(t.Context()) - if assert.NoError(t, err) { - participantsIDs := make([]int, len(participants)) - for i, uid := range participants { - participantsIDs[i] = int(uid) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL(t.Context())) + }) + + t.Run("GetIssuesByIDs", func(t *testing.T) { + testSuccess := func(expectedIssueIDs, nonExistentIssueIDs []int64) { + issues, err := issues_model.GetIssuesByIDs(t.Context(), append(expectedIssueIDs, nonExistentIssueIDs...), true) + assert.NoError(t, err) + actualIssueIDs := make([]int64, len(issues)) + for i, issue := range issues { + actualIssueIDs[i] = issue.ID } - sort.Ints(participantsIDs) - sort.Ints(userIDs) - assert.Equal(t, userIDs, participantsIDs) + assert.Equal(t, expectedIssueIDs, actualIssueIDs) } - } + testSuccess([]int64{1, 2, 3}, []int64{}) + testSuccess([]int64{1, 2, 3}, []int64{unittest.NonexistentID}) + testSuccess([]int64{3, 2, 1}, []int64{}) + }) - // User 1 is issue1 poster (see fixtures/issue.yml) - // User 2 only labeled issue1 (see fixtures/comment.yml) - // Users 3 and 5 made actual comments (see fixtures/comment.yml) - // User 3 is inactive, thus not active participant - checkParticipants(1, []int{1, 5}) + t.Run("GetParticipantIDsByIssue", func(t *testing.T) { + checkParticipants := func(issueID int64, userIDs []int) { + issue, err := issues_model.GetIssueByID(t.Context(), issueID) + assert.NoError(t, err) + participants, err := issue.GetParticipantIDsByIssue(t.Context()) + if assert.NoError(t, err) { + participantsIDs := make([]int, len(participants)) + for i, uid := range participants { + participantsIDs[i] = int(uid) + } + sort.Ints(participantsIDs) + sort.Ints(userIDs) + assert.Equal(t, userIDs, participantsIDs) + } + } + + // User 1 is issue1 poster (see fixtures/issue.yml) + // User 2 only labeled issue1 (see fixtures/comment.yml) + // Users 3 and 5 made actual comments (see fixtures/comment.yml) + // User 3 is inactive, thus not active participant + checkParticipants(1, []int{1, 5}) + }) + + t.Run("Issues", func(t *testing.T) { + publicOnlyUser2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + publicOnlyUser2.ExtDoerData = user_model.NewTokenExtDoerData(user_model.CredentialAccessToken, 1, "public-only,read:issue") + for _, test := range []struct { + Opts issues_model.IssuesOptions + ExpectedIssueIDs []int64 + }{ + { + issues_model.IssuesOptions{ + AssigneeID: "1", + SortType: "oldest", + }, + []int64{1, 6}, + }, + { + issues_model.IssuesOptions{ + RepoCond: builder.In("repo_id", 1, 3), + SortType: "oldest", + Paginator: &db.ListOptions{ + Page: 1, + PageSize: 4, + }, + }, + []int64{1, 2, 3, 5}, + }, + { + issues_model.IssuesOptions{ + LabelIDs: []int64{1}, + Paginator: &db.ListOptions{ + Page: 1, + PageSize: 4, + }, + }, + []int64{2, 1}, + }, + { + issues_model.IssuesOptions{ + LabelIDs: []int64{1, 2}, + Paginator: &db.ListOptions{ + Page: 1, + PageSize: 4, + }, + }, + []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests + }, + { + issues_model.IssuesOptions{ + MilestoneIDs: []int64{1}, + }, + []int64{2}, + }, + { + issues_model.IssuesOptions{ + SubscriberID: 11, + }, + []int64{11, 5, 9, 8, 3, 2, 1}, + }, + { + issues_model.IssuesOptions{ + RepoCond: builder.In("repo_id", 1, 2), + Doer: publicOnlyUser2, + IsPull: optional.Some(false), + SortType: "oldest", + }, + []int64{1, 5}, + }, + } { + issues, err := issues_model.Issues(t.Context(), &test.Opts) + assert.NoError(t, err) + if assert.Len(t, issues, len(test.ExpectedIssueIDs)) { + for i, issue := range issues { + assert.Equal(t, test.ExpectedIssueIDs[i], issue.ID) + } + } + } + }) + + t.Run("Issue_loadTotalTimes", func(t *testing.T) { + ms, err := issues_model.GetIssueByID(t.Context(), 2) + assert.NoError(t, err) + assert.NoError(t, ms.LoadTotalTimes(t.Context())) + assert.Equal(t, int64(3682), ms.TotalTrackedTime) + }) + + t.Run("Issue_ResolveMentions", func(t *testing.T) { + testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) { + o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner}) + r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo}) + issue := &issues_model.Issue{RepoID: r.ID} + d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer}) + resolved, err := issues_model.ResolveIssueMentionsByVisibility(t.Context(), issue, d, mentions) + assert.NoError(t, err) + ids := make([]int64, len(resolved)) + for i, user := range resolved { + ids[i] = user.ID + } + slices.Sort(ids) + assert.Equal(t, expected, ids) + } + + // Public repo, existing user + testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5}) + // Public repo, non-existing user + testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{}) + // Public repo, doer + testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{}) + // Private repo, team member + testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2}) + // Private repo, not a team member + testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{}) + // Private repo, whole team + testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18}) + }) + + t.Run("MilestoneList_LoadTotalTrackedTimes", func(t *testing.T) { + miles := issues_model.MilestoneList{ + unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}), + } + + assert.NoError(t, miles.LoadTotalTrackedTimes(t.Context())) + + assert.Equal(t, int64(3682), miles[0].TotalTrackedTime) + }) + + t.Run("LoadTotalTrackedTime", func(t *testing.T) { + milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) + + assert.NoError(t, milestone.LoadTotalTrackedTime(t.Context())) + + assert.Equal(t, int64(3682), milestone.TotalTrackedTime) + }) + + t.Run("CountIssues", func(t *testing.T) { + count, err := issues_model.CountIssues(t.Context(), &issues_model.IssuesOptions{}) + assert.NoError(t, err) + assert.EqualValues(t, 22, count) + }) } func TestIssue_ClearLabels(t *testing.T) { - tests := []struct { - issueID int64 - doerID int64 - }{ - {1, 2}, // non-pull-request, has labels - {2, 2}, // pull-request, has labels - {3, 2}, // pull-request, has no labels - } - for _, test := range tests { + for _, issueID := range []int64{ + 1, // non-pull-request, has labels + 2, // pull-request, has labels + 3, // pull-request, has no labels + } { assert.NoError(t, unittest.PrepareTestDatabase()) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: test.doerID}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issueID}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) assert.NoError(t, issues_model.ClearIssueLabels(t.Context(), issue, doer)) - unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: test.issueID}) + unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: issueID}) } } @@ -149,92 +281,6 @@ func TestUpdateIssueCols(t *testing.T) { unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) } -func TestIssues(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - publicOnlyUser2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - publicOnlyUser2.ExtDoerData = user_model.NewTokenExtDoerData(user_model.CredentialAccessToken, 1, "public-only,read:issue") - for _, test := range []struct { - Opts issues_model.IssuesOptions - ExpectedIssueIDs []int64 - }{ - { - issues_model.IssuesOptions{ - AssigneeID: "1", - SortType: "oldest", - }, - []int64{1, 6}, - }, - { - issues_model.IssuesOptions{ - RepoCond: builder.In("repo_id", 1, 3), - SortType: "oldest", - Paginator: &db.ListOptions{ - Page: 1, - PageSize: 4, - }, - }, - []int64{1, 2, 3, 5}, - }, - { - issues_model.IssuesOptions{ - LabelIDs: []int64{1}, - Paginator: &db.ListOptions{ - Page: 1, - PageSize: 4, - }, - }, - []int64{2, 1}, - }, - { - issues_model.IssuesOptions{ - LabelIDs: []int64{1, 2}, - Paginator: &db.ListOptions{ - Page: 1, - PageSize: 4, - }, - }, - []int64{}, // issues with **both** label 1 and 2, none of these issues matches, TODO: add more tests - }, - { - issues_model.IssuesOptions{ - MilestoneIDs: []int64{1}, - }, - []int64{2}, - }, - { - issues_model.IssuesOptions{ - SubscriberID: 11, - }, - []int64{11, 5, 9, 8, 3, 2, 1}, - }, - { - issues_model.IssuesOptions{ - RepoCond: builder.In("repo_id", 1, 2), - Doer: publicOnlyUser2, - IsPull: optional.Some(false), - SortType: "oldest", - }, - []int64{1, 5}, - }, - } { - issues, err := issues_model.Issues(t.Context(), &test.Opts) - assert.NoError(t, err) - if assert.Len(t, issues, len(test.ExpectedIssueIDs)) { - for i, issue := range issues { - assert.Equal(t, test.ExpectedIssueIDs[i], issue.ID) - } - } - } -} - -func TestIssue_loadTotalTimes(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ms, err := issues_model.GetIssueByID(t.Context(), 2) - assert.NoError(t, err) - assert.NoError(t, ms.LoadTotalTimes(t.Context())) - assert.Equal(t, int64(3682), ms.TotalTrackedTime) -} - func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *issues_model.Issue { var newIssue issues_model.Issue t.Run(title, func(t *testing.T) { @@ -276,38 +322,6 @@ func TestIssue_InsertIssue(t *testing.T) { assert.NoError(t, err) } -func TestIssue_ResolveMentions(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) { - o := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: owner}) - r := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: o.ID, LowerName: repo}) - issue := &issues_model.Issue{RepoID: r.ID} - d := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: doer}) - resolved, err := issues_model.ResolveIssueMentionsByVisibility(t.Context(), issue, d, mentions) - assert.NoError(t, err) - ids := make([]int64, len(resolved)) - for i, user := range resolved { - ids[i] = user.ID - } - slices.Sort(ids) - assert.Equal(t, expected, ids) - } - - // Public repo, existing user - testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5}) - // Public repo, non-existing user - testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{}) - // Public repo, doer - testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{}) - // Private repo, team member - testSuccess("org17", "big_test_private_4", "user20", []string{"user2"}, []int64{2}) - // Private repo, not a team member - testSuccess("org17", "big_test_private_4", "user20", []string{"user5"}, []int64{}) - // Private repo, whole team - testSuccess("org17", "big_test_private_4", "user15", []string{"org17/owners"}, []int64{18}) -} - func TestResourceIndex(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) var wg sync.WaitGroup @@ -365,33 +379,6 @@ func TestCorrectIssueStats(t *testing.T) { assert.EqualValues(t, issueStats.OpenCount, issueAmount) } -func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - miles := issues_model.MilestoneList{ - unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}), - } - - assert.NoError(t, miles.LoadTotalTrackedTimes(t.Context())) - - assert.Equal(t, int64(3682), miles[0].TotalTrackedTime) -} - -func TestLoadTotalTrackedTime(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - - assert.NoError(t, milestone.LoadTotalTrackedTime(t.Context())) - - assert.Equal(t, int64(3682), milestone.TotalTrackedTime) -} - -func TestCountIssues(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - count, err := issues_model.CountIssues(t.Context(), &issues_model.IssuesOptions{}) - assert.NoError(t, err) - assert.EqualValues(t, 22, count) -} - func TestIssueLoadAttributes(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) setting.Service.EnableTimetracking = true diff --git a/models/issues/issue_user_test.go b/models/issues/issue_user_test.go index 58d2698e768..b840a02c7c7 100644 --- a/models/issues/issue_user_test.go +++ b/models/issues/issue_user_test.go @@ -15,47 +15,47 @@ import ( "github.com/stretchr/testify/require" ) -func Test_NewIssueUsers(t *testing.T) { +func TestIssueUsers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - newIssue := &issues_model.Issue{ - RepoID: repo.ID, - PosterID: 4, - Index: 6, - Title: "newTestIssueTitle", - Content: "newTestIssueContent", - } + t.Run("NewIssueUsers", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + newIssue := &issues_model.Issue{ + RepoID: repo.ID, + PosterID: 4, + Index: 6, + Title: "newTestIssueTitle", + Content: "newTestIssueContent", + } - // artificially insert new issue - require.NoError(t, db.Insert(t.Context(), newIssue)) - require.NoError(t, issues_model.NewIssueUsers(t.Context(), repo, newIssue)) + // artificially insert new issue + require.NoError(t, db.Insert(t.Context(), newIssue)) + require.NoError(t, issues_model.NewIssueUsers(t.Context(), repo, newIssue)) - // issue_user table should now have entries for new issue - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID}) -} - -func TestUpdateIssueUserByRead(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - - assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID)) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") - - assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID)) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") - - assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), unittest.NonexistentID, unittest.NonexistentID)) -} - -func TestUpdateIssueUsersByMentions(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - - uids := []int64{2, 5} - assert.NoError(t, issues_model.UpdateIssueUsersByMentions(t.Context(), issue.ID, uids)) - for _, uid := range uids { - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") - } + // issue_user table should now have entries for new issue + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: newIssue.PosterID}) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: newIssue.ID, UID: repo.OwnerID}) + }) + + t.Run("UpdateIssueUserByRead", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + + assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID)) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") + + assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), 4, issue.ID)) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: 4}, "is_read=1") + + assert.NoError(t, issues_model.UpdateIssueUserByRead(t.Context(), unittest.NonexistentID, unittest.NonexistentID)) + }) + + t.Run("UpdateIssueUsersByMentions", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + + uids := []int64{2, 5} + assert.NoError(t, issues_model.UpdateIssueUsersByMentions(t.Context(), issue.ID, uids)) + for _, uid := range uids { + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueUser{IssueID: issue.ID, UID: uid}, "is_mentioned=1") + } + }) } diff --git a/models/issues/issue_watch_test.go b/models/issues/issue_watch_test.go index 9ff5bd7e8e4..5029f414c5e 100644 --- a/models/issues/issue_watch_test.go +++ b/models/issues/issue_watch_test.go @@ -13,55 +13,48 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCreateOrUpdateIssueWatch(t *testing.T) { +func TestIssueWatch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 3, 1, true)) - iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1}) - assert.True(t, iw.IsWatching) + t.Run("GetIssueWatch", func(t *testing.T) { + _, exists, err := issues_model.GetIssueWatch(t.Context(), 9, 1) + assert.True(t, exists) + assert.NoError(t, err) - assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 1, 1, false)) - iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1}) - assert.False(t, iw.IsWatching) -} - -func TestGetIssueWatch(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - _, exists, err := issues_model.GetIssueWatch(t.Context(), 9, 1) - assert.True(t, exists) - assert.NoError(t, err) - - iw, exists, err := issues_model.GetIssueWatch(t.Context(), 2, 2) - assert.True(t, exists) - assert.NoError(t, err) - assert.False(t, iw.IsWatching) - - _, exists, err = issues_model.GetIssueWatch(t.Context(), 3, 1) - assert.False(t, exists) - assert.NoError(t, err) -} - -func TestGetIssueWatchers(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - iws, err := issues_model.GetIssueWatchers(t.Context(), 1, db.ListOptions{}) - assert.NoError(t, err) - // Watcher is inactive, thus 0 - assert.Empty(t, iws) - - iws, err = issues_model.GetIssueWatchers(t.Context(), 2, db.ListOptions{}) - assert.NoError(t, err) - // Watcher is explicit not watching - assert.Empty(t, iws) - - iws, err = issues_model.GetIssueWatchers(t.Context(), 5, db.ListOptions{}) - assert.NoError(t, err) - // Issue has no Watchers - assert.Empty(t, iws) - - iws, err = issues_model.GetIssueWatchers(t.Context(), 7, db.ListOptions{}) - assert.NoError(t, err) - // Issue has one watcher - assert.Len(t, iws, 1) + iw, exists, err := issues_model.GetIssueWatch(t.Context(), 2, 2) + assert.True(t, exists) + assert.NoError(t, err) + assert.False(t, iw.IsWatching) + + _, exists, err = issues_model.GetIssueWatch(t.Context(), 3, 1) + assert.False(t, exists) + assert.NoError(t, err) + }) + + t.Run("GetIssueWatchers", func(t *testing.T) { + for _, issueID := range []int64{ + 1, // Watcher is inactive, thus 0 + 2, // Watcher is explicit not watching + 5, // Issue has no Watchers + } { + iws, err := issues_model.GetIssueWatchers(t.Context(), issueID, db.ListOptions{}) + assert.NoError(t, err) + assert.Empty(t, iws) + } + + iws, err := issues_model.GetIssueWatchers(t.Context(), 7, db.ListOptions{}) + assert.NoError(t, err) + // Issue has one watcher + assert.Len(t, iws, 1) + }) + + t.Run("CreateOrUpdateIssueWatch", func(t *testing.T) { + assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 3, 1, true)) + iw := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 3, IssueID: 1}) + assert.True(t, iw.IsWatching) + + assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(t.Context(), 1, 1, false)) + iw = unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 1, IssueID: 1}) + assert.False(t, iw.IsWatching) + }) } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 547c1dd978c..e22693ec674 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -17,41 +17,190 @@ import ( "github.com/stretchr/testify/assert" ) -func TestLabel_CalOpenIssues(t *testing.T) { +func TestLabelQueries(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - label.CalOpenIssues() - assert.Equal(t, 2, label.NumOpenIssues) -} + t.Run("Label_CalOpenIssues", func(t *testing.T) { + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) + label.CalOpenIssues() + assert.Equal(t, 2, label.NumOpenIssues) + }) + t.Run("Label_LoadSelectedLabelsAfterClick", func(t *testing.T) { + // Loading the label id:8 which have a scope and an exclusivity + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8}) -func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - // Loading the label id:8 which have a scope and an exclusivity - label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 8}) + // First test : with negative and scope + label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"}) + assert.Equal(t, "1", label.QueryString) + assert.True(t, label.IsSelected) - // First test : with negative and scope - label.LoadSelectedLabelsAfterClick([]int64{1, -8}, []string{"", "scope"}) - assert.Equal(t, "1", label.QueryString) - assert.True(t, label.IsSelected) + // Second test : with duplicates + label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"}) + assert.Equal(t, "1,8", label.QueryString) + assert.False(t, label.IsSelected) - // Second test : with duplicates - label.LoadSelectedLabelsAfterClick([]int64{1, 7, 1, 7, 7}, []string{"", "scope", "", "scope", "scope"}) - assert.Equal(t, "1,8", label.QueryString) - assert.False(t, label.IsSelected) + // Third test : empty set + label.LoadSelectedLabelsAfterClick([]int64{}, []string{}) + assert.False(t, label.IsSelected) + assert.Equal(t, "8", label.QueryString) + }) + t.Run("Label_ExclusiveScope", func(t *testing.T) { + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7}) + assert.Equal(t, "scope", label.ExclusiveScope()) - // Third test : empty set - label.LoadSelectedLabelsAfterClick([]int64{}, []string{}) - assert.False(t, label.IsSelected) - assert.Equal(t, "8", label.QueryString) -} + label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 9}) + assert.Equal(t, "scope/subscope", label.ExclusiveScope()) + }) + t.Run("GetLabelByID", func(t *testing.T) { + label, err := issues_model.GetLabelByID(t.Context(), 1) + assert.NoError(t, err) + assert.EqualValues(t, 1, label.ID) -func TestLabel_ExclusiveScope(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 7}) - assert.Equal(t, "scope", label.ExclusiveScope()) + _, err = issues_model.GetLabelByID(t.Context(), unittest.NonexistentID) + assert.True(t, issues_model.IsErrLabelNotExist(err)) + }) + t.Run("GetLabelInRepoByName", func(t *testing.T) { + label, err := issues_model.GetLabelInRepoByName(t.Context(), 1, "label1") + assert.NoError(t, err) + assert.EqualValues(t, 1, label.ID) + assert.Equal(t, "label1", label.Name) - label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 9}) - assert.Equal(t, "scope/subscope", label.ExclusiveScope()) + _, err = issues_model.GetLabelInRepoByName(t.Context(), 1, "") + assert.ErrorIs(t, err, util.ErrNotExist) + + _, err = issues_model.GetLabelInRepoByName(t.Context(), unittest.NonexistentID, "nonexistent") + assert.ErrorIs(t, err, util.ErrNotExist) + }) + t.Run("GetLabelInRepoByNames", func(t *testing.T) { + for _, names := range [][]string{ + {"label1", "label2"}, + // label3 doesn't exists.. See labels.yml + {"label1", "label2", "label3"}, + } { + labelIDs, err := issues_model.GetLabelIDsInRepoByNames(t.Context(), 1, names) + assert.NoError(t, err) + assert.Len(t, labelIDs, 2) + assert.Equal(t, int64(1), labelIDs[0]) + assert.Equal(t, int64(2), labelIDs[1]) + } + }) + t.Run("GetLabelInRepoByID", func(t *testing.T) { + label, err := issues_model.GetLabelInRepoByID(t.Context(), 1, 1) + assert.NoError(t, err) + assert.EqualValues(t, 1, label.ID) + + _, err = issues_model.GetLabelInRepoByID(t.Context(), 1, -1) + assert.ErrorIs(t, err, util.ErrNotExist) + + _, err = issues_model.GetLabelInRepoByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) + assert.ErrorIs(t, err, util.ErrNotExist) + }) + t.Run("GetLabelsInRepoByIDs", func(t *testing.T) { + labels, err := issues_model.GetLabelsInRepoByIDs(t.Context(), 1, []int64{1, 2, unittest.NonexistentID}) + assert.NoError(t, err) + if assert.Len(t, labels, 2) { + assert.EqualValues(t, 1, labels[0].ID) + assert.EqualValues(t, 2, labels[1].ID) + } + }) + + // Org versions + t.Run("GetLabelInOrgByName", func(t *testing.T) { + label, err := issues_model.GetLabelInOrgByName(t.Context(), 3, "orglabel3") + assert.NoError(t, err) + assert.EqualValues(t, 3, label.ID) + assert.Equal(t, "orglabel3", label.Name) + + _, err = issues_model.GetLabelInOrgByName(t.Context(), 3, "") + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelInOrgByName(t.Context(), 0, "orglabel3") + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelInOrgByName(t.Context(), -1, "orglabel3") + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelInOrgByName(t.Context(), unittest.NonexistentID, "nonexistent") + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + }) + t.Run("GetLabelInOrgByID", func(t *testing.T) { + label, err := issues_model.GetLabelInOrgByID(t.Context(), 3, 3) + assert.NoError(t, err) + assert.EqualValues(t, 3, label.ID) + + _, err = issues_model.GetLabelInOrgByID(t.Context(), 3, -1) + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelInOrgByID(t.Context(), 0, 3) + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelInOrgByID(t.Context(), -1, 3) + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelInOrgByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + }) + t.Run("GetLabelsInOrgByIDs", func(t *testing.T) { + labels, err := issues_model.GetLabelsInOrgByIDs(t.Context(), 3, []int64{3, 4, unittest.NonexistentID}) + assert.NoError(t, err) + if assert.Len(t, labels, 2) { + assert.EqualValues(t, 3, labels[0].ID) + assert.EqualValues(t, 4, labels[1].ID) + } + }) + t.Run("GetLabelsByIssueID", func(t *testing.T) { + labels, err := issues_model.GetLabelsByIssueID(t.Context(), 1) + assert.NoError(t, err) + if assert.Len(t, labels, 1) { + assert.EqualValues(t, 1, labels[0].ID) + } + + labels, err = issues_model.GetLabelsByIssueID(t.Context(), unittest.NonexistentID) + assert.NoError(t, err) + assert.Empty(t, labels) + }) + t.Run("HasIssueLabel", func(t *testing.T) { + assert.True(t, issues_model.HasIssueLabel(t.Context(), 1, 1)) + assert.False(t, issues_model.HasIssueLabel(t.Context(), 1, 2)) + assert.False(t, issues_model.HasIssueLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID)) + }) + t.Run("GetLabelsByRepoID", func(t *testing.T) { + _, err := db.GetEngine(t.Context()).ID(2).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3}) + assert.NoError(t, err) + testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) { + labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{}) + assert.NoError(t, err) + assert.Len(t, labels, len(expectedIssueIDs)) + for i, label := range labels { + assert.Equal(t, expectedIssueIDs[i], label.ID) + } + } + testSuccess(1, "leastissues", []int64{2, 1}) + testSuccess(1, "mostissues", []int64{1, 2}) + testSuccess(1, "reversealphabetically", []int64{2, 1}) + testSuccess(1, "default", []int64{1, 2}) + }) + t.Run("GetLabelsByOrgID", func(t *testing.T) { + _, err := db.GetEngine(t.Context()).ID(3).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3}) + assert.NoError(t, err) + testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) { + labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{}) + assert.NoError(t, err) + assert.Len(t, labels, len(expectedIssueIDs)) + for i, label := range labels { + assert.Equal(t, expectedIssueIDs[i], label.ID) + } + } + testSuccess(3, "leastissues", []int64{3, 4}) + testSuccess(3, "mostissues", []int64{4, 3}) + testSuccess(3, "reversealphabetically", []int64{4, 3}) + testSuccess(3, "default", []int64{3, 4}) + + _, err = issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{}) + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + + _, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{}) + assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) + }) } func TestSortLabelsForDisplay(t *testing.T) { @@ -118,185 +267,6 @@ func TestNewLabels(t *testing.T) { unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) } -func TestGetLabelByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - label, err := issues_model.GetLabelByID(t.Context(), 1) - assert.NoError(t, err) - assert.EqualValues(t, 1, label.ID) - - _, err = issues_model.GetLabelByID(t.Context(), unittest.NonexistentID) - assert.True(t, issues_model.IsErrLabelNotExist(err)) -} - -func TestGetLabelInRepoByName(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - label, err := issues_model.GetLabelInRepoByName(t.Context(), 1, "label1") - assert.NoError(t, err) - assert.EqualValues(t, 1, label.ID) - assert.Equal(t, "label1", label.Name) - - _, err = issues_model.GetLabelInRepoByName(t.Context(), 1, "") - assert.ErrorIs(t, err, util.ErrNotExist) - - _, err = issues_model.GetLabelInRepoByName(t.Context(), unittest.NonexistentID, "nonexistent") - assert.ErrorIs(t, err, util.ErrNotExist) -} - -func TestGetLabelInRepoByNames(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - labelIDs, err := issues_model.GetLabelIDsInRepoByNames(t.Context(), 1, []string{"label1", "label2"}) - assert.NoError(t, err) - - assert.Len(t, labelIDs, 2) - - assert.Equal(t, int64(1), labelIDs[0]) - assert.Equal(t, int64(2), labelIDs[1]) -} - -func TestGetLabelInRepoByNamesDiscardsNonExistentLabels(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - // label3 doesn't exists.. See labels.yml - labelIDs, err := issues_model.GetLabelIDsInRepoByNames(t.Context(), 1, []string{"label1", "label2", "label3"}) - assert.NoError(t, err) - - assert.Len(t, labelIDs, 2) - - assert.Equal(t, int64(1), labelIDs[0]) - assert.Equal(t, int64(2), labelIDs[1]) - assert.NoError(t, err) -} - -func TestGetLabelInRepoByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - label, err := issues_model.GetLabelInRepoByID(t.Context(), 1, 1) - assert.NoError(t, err) - assert.EqualValues(t, 1, label.ID) - - _, err = issues_model.GetLabelInRepoByID(t.Context(), 1, -1) - assert.ErrorIs(t, err, util.ErrNotExist) - - _, err = issues_model.GetLabelInRepoByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) - assert.ErrorIs(t, err, util.ErrNotExist) -} - -func TestGetLabelsInRepoByIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - labels, err := issues_model.GetLabelsInRepoByIDs(t.Context(), 1, []int64{1, 2, unittest.NonexistentID}) - assert.NoError(t, err) - if assert.Len(t, labels, 2) { - assert.EqualValues(t, 1, labels[0].ID) - assert.EqualValues(t, 2, labels[1].ID) - } -} - -func TestGetLabelsByRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - _, err := db.GetEngine(t.Context()).ID(2).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3}) - assert.NoError(t, err) - testSuccess := func(repoID int64, sortType string, expectedIssueIDs []int64) { - labels, err := issues_model.GetLabelsByRepoID(t.Context(), repoID, sortType, db.ListOptions{}) - assert.NoError(t, err) - assert.Len(t, labels, len(expectedIssueIDs)) - for i, label := range labels { - assert.Equal(t, expectedIssueIDs[i], label.ID) - } - } - testSuccess(1, "leastissues", []int64{2, 1}) - testSuccess(1, "mostissues", []int64{1, 2}) - testSuccess(1, "reversealphabetically", []int64{2, 1}) - testSuccess(1, "default", []int64{1, 2}) -} - -// Org versions - -func TestGetLabelInOrgByName(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - label, err := issues_model.GetLabelInOrgByName(t.Context(), 3, "orglabel3") - assert.NoError(t, err) - assert.EqualValues(t, 3, label.ID) - assert.Equal(t, "orglabel3", label.Name) - - _, err = issues_model.GetLabelInOrgByName(t.Context(), 3, "") - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelInOrgByName(t.Context(), 0, "orglabel3") - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelInOrgByName(t.Context(), -1, "orglabel3") - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelInOrgByName(t.Context(), unittest.NonexistentID, "nonexistent") - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) -} - -func TestGetLabelInOrgByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - label, err := issues_model.GetLabelInOrgByID(t.Context(), 3, 3) - assert.NoError(t, err) - assert.EqualValues(t, 3, label.ID) - - _, err = issues_model.GetLabelInOrgByID(t.Context(), 3, -1) - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelInOrgByID(t.Context(), 0, 3) - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelInOrgByID(t.Context(), -1, 3) - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelInOrgByID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) -} - -func TestGetLabelsInOrgByIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - labels, err := issues_model.GetLabelsInOrgByIDs(t.Context(), 3, []int64{3, 4, unittest.NonexistentID}) - assert.NoError(t, err) - if assert.Len(t, labels, 2) { - assert.EqualValues(t, 3, labels[0].ID) - assert.EqualValues(t, 4, labels[1].ID) - } -} - -func TestGetLabelsByOrgID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - _, err := db.GetEngine(t.Context()).ID(3).Cols("num_issues", "num_closed_issues").Update(&issues_model.Label{NumIssues: 3, NumClosedIssues: 3}) - assert.NoError(t, err) - testSuccess := func(orgID int64, sortType string, expectedIssueIDs []int64) { - labels, err := issues_model.GetLabelsByOrgID(t.Context(), orgID, sortType, db.ListOptions{}) - assert.NoError(t, err) - assert.Len(t, labels, len(expectedIssueIDs)) - for i, label := range labels { - assert.Equal(t, expectedIssueIDs[i], label.ID) - } - } - testSuccess(3, "leastissues", []int64{3, 4}) - testSuccess(3, "mostissues", []int64{4, 3}) - testSuccess(3, "reversealphabetically", []int64{4, 3}) - testSuccess(3, "default", []int64{3, 4}) - - _, err = issues_model.GetLabelsByOrgID(t.Context(), 0, "leastissues", db.ListOptions{}) - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) - - _, err = issues_model.GetLabelsByOrgID(t.Context(), -1, "leastissues", db.ListOptions{}) - assert.True(t, issues_model.IsErrOrgLabelNotExist(err)) -} - -// - -func TestGetLabelsByIssueID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - labels, err := issues_model.GetLabelsByIssueID(t.Context(), 1) - assert.NoError(t, err) - if assert.Len(t, labels, 1) { - assert.EqualValues(t, 1, labels[0].ID) - } - - labels, err = issues_model.GetLabelsByIssueID(t.Context(), unittest.NonexistentID) - assert.NoError(t, err) - assert.Empty(t, labels) -} - func TestUpdateLabel(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) @@ -350,13 +320,6 @@ func TestDeleteLabel(t *testing.T) { unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) } -func TestHasIssueLabel(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, issues_model.HasIssueLabel(t.Context(), 1, 1)) - assert.False(t, issues_model.HasIssueLabel(t.Context(), 1, 2)) - assert.False(t, issues_model.HasIssueLabel(t.Context(), unittest.NonexistentID, unittest.NonexistentID)) -} - func TestNewIssueLabel(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go index 925038f68e2..262e59b1451 100644 --- a/models/issues/milestone_test.go +++ b/models/issues/milestone_test.go @@ -24,263 +24,189 @@ func TestMilestone_State(t *testing.T) { assert.Equal(t, api.StateClosed, (&issues_model.Milestone{IsClosed: true}).State()) } -func TestGetMilestoneByRepoID(t *testing.T) { +func TestMilestoneQueries(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - - milestone, err := issues_model.GetMilestoneByRepoID(t.Context(), 1, 1) - assert.NoError(t, err) - assert.EqualValues(t, 1, milestone.ID) - assert.EqualValues(t, 1, milestone.RepoID) - - _, err = issues_model.GetMilestoneByRepoID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) - assert.True(t, issues_model.IsErrMilestoneNotExist(err)) -} - -func TestGetMilestonesByRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(repoID int64, state api.StateType) { - var isClosed optional.Option[bool] - switch state { - case api.StateClosed, api.StateOpen: - isClosed = optional.Some(state == api.StateClosed) - } - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - RepoID: repo.ID, - IsClosed: isClosed, - }) + t.Run("GetMilestoneByRepoID", func(t *testing.T) { + milestone, err := issues_model.GetMilestoneByRepoID(t.Context(), 1, 1) assert.NoError(t, err) + assert.EqualValues(t, 1, milestone.ID) + assert.EqualValues(t, 1, milestone.RepoID) - var n int - - switch state { - case api.StateClosed: - n = repo.NumClosedMilestones - - case api.StateAll: - n = repo.NumMilestones - - case api.StateOpen: - fallthrough - - default: - n = repo.NumOpenMilestones - } - - assert.Len(t, milestones, n) - for _, milestone := range milestones { - assert.Equal(t, repoID, milestone.RepoID) - } - } - test(1, api.StateOpen) - test(1, api.StateAll) - test(1, api.StateClosed) - test(2, api.StateOpen) - test(2, api.StateAll) - test(2, api.StateClosed) - test(3, api.StateOpen) - test(3, api.StateClosed) - test(3, api.StateAll) - - milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - RepoID: unittest.NonexistentID, - IsClosed: optional.Some(false), + _, err = issues_model.GetMilestoneByRepoID(t.Context(), unittest.NonexistentID, unittest.NonexistentID) + assert.True(t, issues_model.IsErrMilestoneNotExist(err)) }) - assert.NoError(t, err) - assert.Empty(t, milestones) -} - -func TestGetMilestones(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - test := func(sortType string, sortCond func(*issues_model.Milestone) int) { - for _, page := range []int{0, 1} { + t.Run("GetMilestonesByRepoID", func(t *testing.T) { + test := func(repoID int64, state api.StateType) { + var isClosed optional.Option[bool] + switch state { + case api.StateClosed, api.StateOpen: + isClosed = optional.Some(state == api.StateClosed) + } + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: setting.UI.IssuePagingNum, - }, RepoID: repo.ID, - IsClosed: optional.Some(false), - SortType: sortType, + IsClosed: isClosed, }) assert.NoError(t, err) - assert.Len(t, milestones, repo.NumMilestones-repo.NumClosedMilestones) - values := make([]int, len(milestones)) - for i, milestone := range milestones { - values[i] = sortCond(milestone) - } - assert.True(t, sort.IntsAreSorted(values)) - milestones, err = db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: setting.UI.IssuePagingNum, - }, - RepoID: repo.ID, + var n int + + switch state { + case api.StateClosed: + n = repo.NumClosedMilestones + + case api.StateAll: + n = repo.NumMilestones + + case api.StateOpen: + fallthrough + + default: + n = repo.NumOpenMilestones + } + + assert.Len(t, milestones, n) + for _, milestone := range milestones { + assert.Equal(t, repoID, milestone.RepoID) + } + } + test(1, api.StateOpen) + test(1, api.StateAll) + test(1, api.StateClosed) + test(2, api.StateOpen) + test(2, api.StateAll) + test(2, api.StateClosed) + test(3, api.StateOpen) + test(3, api.StateClosed) + test(3, api.StateAll) + + milestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ + RepoID: unittest.NonexistentID, + IsClosed: optional.Some(false), + }) + assert.NoError(t, err) + assert.Empty(t, milestones) + }) + t.Run("GetMilestones", func(t *testing.T) { + test := func(t *testing.T, options issues_model.FindMilestoneOptions, numOpen, numClosed int) { + testSort := func(sortType string, sortCond func(*issues_model.Milestone) int) { + for _, page := range []int{0, 1} { + for _, isClosed := range []bool{false, true} { + options.ListOptions = db.ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + } + options.IsClosed = optional.Some(isClosed) + options.SortType = sortType + milestones, err := db.Find[issues_model.Milestone](t.Context(), options) + assert.NoError(t, err) + expectedCount := numOpen + if isClosed { + expectedCount = numClosed + } + assert.Len(t, milestones, expectedCount) + values := make([]int, len(milestones)) + for i, milestone := range milestones { + values[i] = sortCond(milestone) + } + assert.True(t, sort.IntsAreSorted(values)) + } + } + } + testSort("furthestduedate", func(milestone *issues_model.Milestone) int { + return -int(milestone.DeadlineUnix) + }) + testSort("leastcomplete", func(milestone *issues_model.Milestone) int { + return milestone.Completeness + }) + testSort("mostcomplete", func(milestone *issues_model.Milestone) int { + return -milestone.Completeness + }) + testSort("leastissues", func(milestone *issues_model.Milestone) int { + return milestone.NumIssues + }) + testSort("mostissues", func(milestone *issues_model.Milestone) int { + return -milestone.NumIssues + }) + testSort("soonestduedate", func(milestone *issues_model.Milestone) int { + return int(milestone.DeadlineUnix) + }) + } + t.Run("repo", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + test(t, issues_model.FindMilestoneOptions{RepoID: repo.ID}, repo.NumMilestones-repo.NumClosedMilestones, repo.NumClosedMilestones) + }) + t.Run("repos", func(t *testing.T) { + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + test(t, issues_model.FindMilestoneOptions{RepoIDs: []int64{repo1.ID, repo2.ID}}, + repo1.NumOpenMilestones+repo2.NumOpenMilestones, repo1.NumClosedMilestones+repo2.NumClosedMilestones) + }) + }) + t.Run("CountRepoMilestones", func(t *testing.T) { + test := func(repoID int64) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ + RepoID: repoID, + }) + assert.NoError(t, err) + assert.EqualValues(t, repo.NumMilestones, count) + } + test(1) + test(2) + test(3) + + count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ + RepoID: unittest.NonexistentID, + }) + assert.NoError(t, err) + assert.EqualValues(t, 0, count) + }) + t.Run("CountRepoClosedMilestones", func(t *testing.T) { + test := func(repoID int64) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ + RepoID: repoID, IsClosed: optional.Some(true), - Name: "", - SortType: sortType, }) assert.NoError(t, err) - assert.Len(t, milestones, repo.NumClosedMilestones) - values = make([]int, len(milestones)) - for i, milestone := range milestones { - values[i] = sortCond(milestone) - } - assert.True(t, sort.IntsAreSorted(values)) + assert.EqualValues(t, repo.NumClosedMilestones, count) } - } - test("furthestduedate", func(milestone *issues_model.Milestone) int { - return -int(milestone.DeadlineUnix) - }) - test("leastcomplete", func(milestone *issues_model.Milestone) int { - return milestone.Completeness - }) - test("mostcomplete", func(milestone *issues_model.Milestone) int { - return -milestone.Completeness - }) - test("leastissues", func(milestone *issues_model.Milestone) int { - return milestone.NumIssues - }) - test("mostissues", func(milestone *issues_model.Milestone) int { - return -milestone.NumIssues - }) - test("soonestduedate", func(milestone *issues_model.Milestone) int { - return int(milestone.DeadlineUnix) - }) -} + test(1) + test(2) + test(3) -func TestCountRepoMilestones(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(repoID int64) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - RepoID: repoID, - }) - assert.NoError(t, err) - assert.EqualValues(t, repo.NumMilestones, count) - } - test(1) - test(2) - test(3) - - count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - RepoID: unittest.NonexistentID, - }) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) -} - -func TestCountRepoClosedMilestones(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(repoID int64) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - RepoID: repoID, + RepoID: unittest.NonexistentID, IsClosed: optional.Some(true), }) assert.NoError(t, err) - assert.EqualValues(t, repo.NumClosedMilestones, count) - } - test(1) - test(2) - test(3) - - count, err := db.Count[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - RepoID: unittest.NonexistentID, - IsClosed: optional.Some(true), + assert.EqualValues(t, 0, count) }) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) -} + t.Run("CountMilestonesByRepoIDs", func(t *testing.T) { + milestonesCount := func(repoID int64) (int, int) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + return repo.NumOpenMilestones, repo.NumClosedMilestones + } + repo1OpenCount, repo1ClosedCount := milestonesCount(1) + repo2OpenCount, repo2ClosedCount := milestonesCount(2) -func TestCountMilestonesByRepoIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - milestonesCount := func(repoID int64) (int, int) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - return repo.NumOpenMilestones, repo.NumClosedMilestones - } - repo1OpenCount, repo1ClosedCount := milestonesCount(1) - repo2OpenCount, repo2ClosedCount := milestonesCount(2) - - openCounts, err := issues_model.CountMilestonesMap(t.Context(), issues_model.FindMilestoneOptions{ - RepoIDs: []int64{1, 2}, - IsClosed: optional.Some(false), - }) - assert.NoError(t, err) - assert.EqualValues(t, repo1OpenCount, openCounts[1]) - assert.EqualValues(t, repo2OpenCount, openCounts[2]) - - closedCounts, err := issues_model.CountMilestonesMap(t.Context(), - issues_model.FindMilestoneOptions{ + openCounts, err := issues_model.CountMilestonesMap(t.Context(), issues_model.FindMilestoneOptions{ RepoIDs: []int64{1, 2}, - IsClosed: optional.Some(true), + IsClosed: optional.Some(false), }) - assert.NoError(t, err) - assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) - assert.EqualValues(t, repo2ClosedCount, closedCounts[2]) -} + assert.NoError(t, err) + assert.EqualValues(t, repo1OpenCount, openCounts[1]) + assert.EqualValues(t, repo2OpenCount, openCounts[2]) -func TestGetMilestonesByRepoIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - test := func(sortType string, sortCond func(*issues_model.Milestone) int) { - for _, page := range []int{0, 1} { - openMilestones, err := db.Find[issues_model.Milestone](t.Context(), issues_model.FindMilestoneOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: setting.UI.IssuePagingNum, - }, - RepoIDs: []int64{repo1.ID, repo2.ID}, - IsClosed: optional.Some(false), - SortType: sortType, + closedCounts, err := issues_model.CountMilestonesMap(t.Context(), + issues_model.FindMilestoneOptions{ + RepoIDs: []int64{1, 2}, + IsClosed: optional.Some(true), }) - assert.NoError(t, err) - assert.Len(t, openMilestones, repo1.NumOpenMilestones+repo2.NumOpenMilestones) - values := make([]int, len(openMilestones)) - for i, milestone := range openMilestones { - values[i] = sortCond(milestone) - } - assert.True(t, sort.IntsAreSorted(values)) - - closedMilestones, err := db.Find[issues_model.Milestone](t.Context(), - issues_model.FindMilestoneOptions{ - ListOptions: db.ListOptions{ - Page: page, - PageSize: setting.UI.IssuePagingNum, - }, - RepoIDs: []int64{repo1.ID, repo2.ID}, - IsClosed: optional.Some(true), - SortType: sortType, - }) - assert.NoError(t, err) - assert.Len(t, closedMilestones, repo1.NumClosedMilestones+repo2.NumClosedMilestones) - values = make([]int, len(closedMilestones)) - for i, milestone := range closedMilestones { - values[i] = sortCond(milestone) - } - assert.True(t, sort.IntsAreSorted(values)) - } - } - test("furthestduedate", func(milestone *issues_model.Milestone) int { - return -int(milestone.DeadlineUnix) - }) - test("leastcomplete", func(milestone *issues_model.Milestone) int { - return milestone.Completeness - }) - test("mostcomplete", func(milestone *issues_model.Milestone) int { - return -milestone.Completeness - }) - test("leastissues", func(milestone *issues_model.Milestone) int { - return milestone.NumIssues - }) - test("mostissues", func(milestone *issues_model.Milestone) int { - return -milestone.NumIssues - }) - test("soonestduedate", func(milestone *issues_model.Milestone) int { - return int(milestone.DeadlineUnix) + assert.NoError(t, err) + assert.EqualValues(t, repo1ClosedCount, closedCounts[1]) + assert.EqualValues(t, repo2ClosedCount, closedCounts[2]) }) } diff --git a/models/issues/review_test.go b/models/issues/review_test.go index f52f2b9359e..29aff876df1 100644 --- a/models/issues/review_test.go +++ b/models/issues/review_test.go @@ -225,33 +225,14 @@ func TestDismissReview(t *testing.T) { assert.True(t, rejectReviewExample.Dismissed) assert.False(t, requestReviewExample.Dismissed) - assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, true)) - rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}) - requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}) - assert.True(t, rejectReviewExample.Dismissed) - assert.False(t, requestReviewExample.Dismissed) - assert.False(t, approveReviewExample.Dismissed) - - assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, true)) - rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}) - requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}) - assert.True(t, rejectReviewExample.Dismissed) - assert.False(t, requestReviewExample.Dismissed) - assert.False(t, approveReviewExample.Dismissed) - - assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, false)) - rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}) - requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}) - assert.True(t, rejectReviewExample.Dismissed) - assert.False(t, requestReviewExample.Dismissed) - assert.False(t, approveReviewExample.Dismissed) - - assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, false)) - rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}) - requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}) - assert.True(t, rejectReviewExample.Dismissed) - assert.False(t, requestReviewExample.Dismissed) - assert.False(t, approveReviewExample.Dismissed) + for _, dismissed := range []bool{true, true, false, false} { + assert.NoError(t, issues_model.DismissReview(t.Context(), requestReviewExample, dismissed)) + rejectReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 9}) + requestReviewExample = unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 11}) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + } assert.NoError(t, issues_model.DismissReview(t.Context(), rejectReviewExample, false)) assert.False(t, rejectReviewExample.Dismissed) diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 028b0c4434e..c134e8c9d68 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -435,79 +435,80 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) { testSuccess(4, []int64{}) } -func TestHasOrgVisibleTypePublic(t *testing.T) { +func TestHasOrgVisibleType(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - const newOrgName = "test-org-public" - org := &organization.Organization{ - Name: newOrgName, - Visibility: structs.VisibleTypePublic, - } + t.Run("Public", func(t *testing.T) { + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) - test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner) - test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3) - test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil) - assert.True(t, test1) // owner of org - assert.True(t, test2) // user not a part of org - assert.True(t, test3) // logged out user + const newOrgName = "test-org-public" + org := &organization.Organization{ + Name: newOrgName, + Visibility: structs.VisibleTypePublic, + } - restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true}) - require.True(t, restrictedUser.IsRestricted) - assert.True(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser)) - defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() - assert.False(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser)) -} + unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) + assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) + test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner) + test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3) + test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil) + assert.True(t, test1) // owner of org + assert.True(t, test2) // user not a part of org + assert.True(t, test3) // logged out user -func TestHasOrgVisibleTypeLimited(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29, IsRestricted: true}) + require.True(t, restrictedUser.IsRestricted) + assert.True(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser)) + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + assert.False(t, organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), restrictedUser)) + }) - const newOrgName = "test-org-limited" - org := &organization.Organization{ - Name: newOrgName, - Visibility: structs.VisibleTypeLimited, - } + t.Run("Limited", func(t *testing.T) { + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) - test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner) - test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3) - test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil) - assert.True(t, test1) // owner of org - assert.True(t, test2) // user not a part of org - assert.False(t, test3) // logged out user -} + const newOrgName = "test-org-limited" + org := &organization.Organization{ + Name: newOrgName, + Visibility: structs.VisibleTypeLimited, + } -func TestHasOrgVisibleTypePrivate(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) + assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) + test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner) + test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3) + test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil) + assert.True(t, test1) // owner of org + assert.True(t, test2) // user not a part of org + assert.False(t, test3) // logged out user + }) - const newOrgName = "test-org-private" - org := &organization.Organization{ - Name: newOrgName, - Visibility: structs.VisibleTypePrivate, - } + t.Run("Private", func(t *testing.T) { + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) - test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner) - test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3) - test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil) - assert.True(t, test1) // owner of org - assert.False(t, test2) // user not a part of org - assert.False(t, test3) // logged out user + const newOrgName = "test-org-private" + org := &organization.Organization{ + Name: newOrgName, + Visibility: structs.VisibleTypePrivate, + } + + unittest.AssertNotExistsBean(t, &user_model.User{Name: org.Name, Type: user_model.UserTypeOrganization}) + assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &organization.Organization{Name: org.Name, Type: user_model.UserTypeOrganization}) + test1 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), owner) + test2 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), org3) + test3 := organization.HasOrgOrUserVisible(t.Context(), org.AsUser(), nil) + assert.True(t, test1) // owner of org + assert.False(t, test2) // user not a part of org + assert.False(t, test3) // logged out user + }) } func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) { @@ -568,65 +569,65 @@ func TestUser_RemoveOrgRepo(t *testing.T) { } func TestCreateOrganization(t *testing.T) { - // successful creation of org assert.NoError(t, unittest.PrepareTestDatabase()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - const newOrgName = "neworg" - org := &organization.Organization{ - Name: newOrgName, - } + t.Run("Unauthorized", func(t *testing.T) { + // unauthorized creation of org - unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization}) - assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) - org = unittest.AssertExistsAndLoadBean(t, - &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) - ownerTeam := unittest.AssertExistsAndLoadBean(t, - &organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID}) - unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: owner.ID, TeamID: ownerTeam.ID}) - unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) -} + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + const newOrgName = "neworg" + org := &organization.Organization{ + Name: newOrgName, + } -func TestCreateOrganization2(t *testing.T) { - // unauthorized creation of org - assert.NoError(t, unittest.PrepareTestDatabase()) + unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) + err := organization.CreateOrganization(t.Context(), org, owner) + assert.Error(t, err) + assert.True(t, organization.IsErrUserNotAllowedCreateOrg(err)) + unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) + unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) + }) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - const newOrgName = "neworg" - org := &organization.Organization{ - Name: newOrgName, - } + t.Run("ExistingName", func(t *testing.T) { + // create org with same name as existent org - unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) - err := organization.CreateOrganization(t.Context(), org, owner) - assert.Error(t, err) - assert.True(t, organization.IsErrUserNotAllowedCreateOrg(err)) - unittest.AssertNotExistsBean(t, &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) - unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) -} + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := &organization.Organization{Name: "org3"} // should already exist + unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check + err := organization.CreateOrganization(t.Context(), org, owner) + assert.Error(t, err) + assert.True(t, user_model.IsErrUserAlreadyExist(err)) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) + }) -func TestCreateOrganization3(t *testing.T) { - // create org with same name as existent org - assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("ReservedName", func(t *testing.T) { + // create org with unusable name - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := &organization.Organization{Name: "org3"} // should already exist - unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: org.Name}) // sanity check - err := organization.CreateOrganization(t.Context(), org, owner) - assert.Error(t, err) - assert.True(t, user_model.IsErrUserAlreadyExist(err)) - unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) -} + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + err := organization.CreateOrganization(t.Context(), &organization.Organization{Name: "assets"}, owner) + assert.Error(t, err) + assert.True(t, db.IsErrNameReserved(err)) + unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) + }) -func TestCreateOrganization4(t *testing.T) { - // create org with unusable name - assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("Success", func(t *testing.T) { + // successful creation of org - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - err := organization.CreateOrganization(t.Context(), &organization.Organization{Name: "assets"}, owner) - assert.Error(t, err) - assert.True(t, db.IsErrNameReserved(err)) - unittest.CheckConsistencyFor(t, &organization.Organization{}, &organization.Team{}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + const newOrgName = "neworg" + org := &organization.Organization{ + Name: newOrgName, + } + + unittest.AssertNotExistsBean(t, &user_model.User{Name: newOrgName, Type: user_model.UserTypeOrganization}) + assert.NoError(t, organization.CreateOrganization(t.Context(), org, owner)) + org = unittest.AssertExistsAndLoadBean(t, + &organization.Organization{Name: newOrgName, Type: user_model.UserTypeOrganization}) + ownerTeam := unittest.AssertExistsAndLoadBean(t, + &organization.Team{Name: organization.OwnerTeamName, OrgID: org.ID}) + unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: owner.ID, TeamID: ownerTeam.ID}) + unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) + }) } func TestOrAnyRepoUnitPermission(t *testing.T) { diff --git a/models/organization/team_test.go b/models/organization/team_test.go index df63c746584..2c16831f249 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -82,7 +82,7 @@ func TestTeam_CanNonMemberReadMeta(t *testing.T) { func TestTeam_GetRepositories(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(teamID int64) { + for _, teamID := range []int64{1, 3} { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) repos, err := repo_model.GetTeamRepositories(t.Context(), &repo_model.SearchTeamRepoOptions{ TeamID: team.ID, @@ -93,14 +93,12 @@ func TestTeam_GetRepositories(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repo.ID}) } } - test(1) - test(3) } func TestTeam_GetMembers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(teamID int64) { + for _, teamID := range []int64{1, 3} { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) assert.NoError(t, team.LoadMembers(t.Context())) assert.Len(t, team.Members, team.NumMembers) @@ -108,8 +106,6 @@ func TestTeam_GetMembers(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: member.ID, TeamID: teamID}) } } - test(1) - test(3) } func TestGetTeam(t *testing.T) { @@ -133,15 +129,11 @@ func TestGetTeam(t *testing.T) { func TestGetTeamByID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - testSuccess := func(teamID int64) { + for _, teamID := range []int64{1, 2, 3, 4} { team, err := organization.GetTeamByID(t.Context(), teamID) assert.NoError(t, err) assert.Equal(t, teamID, team.ID) } - testSuccess(1) - testSuccess(2) - testSuccess(3) - testSuccess(4) _, err := organization.GetTeamByID(t.Context(), unittest.NonexistentID) assert.Error(t, err) @@ -169,7 +161,7 @@ func TestIsTeamMember(t *testing.T) { func TestGetTeamMembers(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(teamID int64) { + for _, teamID := range []int64{1, 3} { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) members, err := organization.GetTeamMembers(t.Context(), &organization.SearchMembersOptions{ TeamID: teamID, @@ -180,22 +172,17 @@ func TestGetTeamMembers(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: member.ID, TeamID: teamID}) } } - test(1) - test(3) } func TestGetUserTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(userID int64) { + for _, userID := range []int64{2, 5, unittest.NonexistentID} { teams, _, err := organization.SearchTeam(t.Context(), &organization.SearchTeamOptions{UserID: userID}) assert.NoError(t, err) for _, team := range teams { unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{TeamID: team.ID, UID: userID}) } } - test(2) - test(5) - test(unittest.NonexistentID) } func TestGetUserOrgTeams(t *testing.T) { diff --git a/models/packages/package_test.go b/models/packages/package_test.go index f6eb5b1184c..46da84e4c05 100644 --- a/models/packages/package_test.go +++ b/models/packages/package_test.go @@ -38,29 +38,21 @@ func TestHasOwnerPackages(t *testing.T) { assert.False(t, has) assert.NoError(t, err) - pv, err := packages_model.GetOrInsertVersion(t.Context(), &packages_model.PackageVersion{ - PackageID: p.ID, - LowerVersion: "internal", - IsInternal: true, - }) - assert.NotNil(t, pv) - assert.NoError(t, err) + for _, testCase := range []struct { + version packages_model.PackageVersion + assertHasPackages assert.BoolAssertionFunc + }{ + // A package with an internal package version gets automatically cleaned up and should return false + {packages_model.PackageVersion{PackageID: p.ID, LowerVersion: "internal", IsInternal: true}, assert.False}, + // A package with a normal package version should return true + {packages_model.PackageVersion{PackageID: p.ID, LowerVersion: "normal", IsInternal: false}, assert.True}, + } { + pv, err := packages_model.GetOrInsertVersion(t.Context(), &testCase.version) + assert.NotNil(t, pv) + assert.NoError(t, err) - // A package with an internal package version gets automatically cleaned up and should return false - has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID) - assert.False(t, has) - assert.NoError(t, err) - - pv, err = packages_model.GetOrInsertVersion(t.Context(), &packages_model.PackageVersion{ - PackageID: p.ID, - LowerVersion: "normal", - IsInternal: false, - }) - assert.NotNil(t, pv) - assert.NoError(t, err) - - // A package with a normal package version should return true - has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID) - assert.True(t, has) - assert.NoError(t, err) + has, err = packages_model.HasOwnerPackages(t.Context(), owner.ID) + testCase.assertHasPackages(t, has) + assert.NoError(t, err) + } } diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go index cb076ec98bb..bc96cc37c3f 100644 --- a/models/perm/access/repo_permission_test.go +++ b/models/perm/access/repo_permission_test.go @@ -56,43 +56,25 @@ func TestHasAnyUnitAccess(t *testing.T) { } func TestApplyPublicAccessRepoPermission(t *testing.T) { + for _, test := range []struct { + user *user_model.User + repoUnit *repo_model.RepoUnit + assertRead assert.BoolAssertionFunc + }{ + {nil, &repo_model.RepoUnit{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, assert.False}, + {nil, &repo_model.RepoUnit{Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead}, assert.True}, + {&user_model.User{ID: 0}, &repo_model.RepoUnit{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, assert.False}, + {&user_model.User{ID: 1}, &repo_model.RepoUnit{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, assert.True}, + } { + perm := Permission{ + AccessMode: perm_model.AccessModeNone, + units: []*repo_model.RepoUnit{test.repoUnit}, + } + finalProcessRepoUnitPermission(test.user, &perm) + test.assertRead(t, perm.CanRead(unit.TypeWiki)) + } + perm := Permission{ - AccessMode: perm_model.AccessModeNone, - units: []*repo_model.RepoUnit{ - {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, - }, - } - finalProcessRepoUnitPermission(nil, &perm) - assert.False(t, perm.CanRead(unit.TypeWiki)) - - perm = Permission{ - AccessMode: perm_model.AccessModeNone, - units: []*repo_model.RepoUnit{ - {Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead}, - }, - } - finalProcessRepoUnitPermission(nil, &perm) - assert.True(t, perm.CanRead(unit.TypeWiki)) - - perm = Permission{ - AccessMode: perm_model.AccessModeNone, - units: []*repo_model.RepoUnit{ - {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, - }, - } - finalProcessRepoUnitPermission(&user_model.User{ID: 0}, &perm) - assert.False(t, perm.CanRead(unit.TypeWiki)) - - perm = Permission{ - AccessMode: perm_model.AccessModeNone, - units: []*repo_model.RepoUnit{ - {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, - }, - } - finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm) - assert.True(t, perm.CanRead(unit.TypeWiki)) - - perm = Permission{ AccessMode: perm_model.AccessModeWrite, units: []*repo_model.RepoUnit{ {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, @@ -117,45 +99,27 @@ func TestApplyPublicAccessRepoPermission(t *testing.T) { } func TestUnitAccessMode(t *testing.T) { - perm := Permission{ - AccessMode: perm_model.AccessModeNone, + for _, test := range []struct { + accessMode perm_model.AccessMode + hasUnit, hasMap bool + expected perm_model.AccessMode + message string + }{ + {perm_model.AccessModeNone, false, false, perm_model.AccessModeNone, "no unit, no map, use AccessMode"}, + {perm_model.AccessModeRead, true, false, perm_model.AccessModeRead, "only unit, no map, use AccessMode"}, + {perm_model.AccessModeAdmin, false, true, perm_model.AccessModeAdmin, "no unit, only map, admin overrides map"}, + {perm_model.AccessModeNone, false, true, perm_model.AccessModeRead, "no unit, only map, use map"}, + {perm_model.AccessModeNone, true, true, perm_model.AccessModeRead, "has unit, and map, use map"}, + } { + perm := Permission{AccessMode: test.accessMode} + if test.hasUnit { + perm.units = []*repo_model.RepoUnit{{Type: unit.TypeWiki}} + } + if test.hasMap { + perm.unitsMode = map[unit.Type]perm_model.AccessMode{unit.TypeWiki: perm_model.AccessModeRead} + } + assert.Equal(t, test.expected, perm.UnitAccessMode(unit.TypeWiki), test.message) } - assert.Equal(t, perm_model.AccessModeNone, perm.UnitAccessMode(unit.TypeWiki), "no unit, no map, use AccessMode") - - perm = Permission{ - AccessMode: perm_model.AccessModeRead, - units: []*repo_model.RepoUnit{ - {Type: unit.TypeWiki}, - }, - } - assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "only unit, no map, use AccessMode") - - perm = Permission{ - AccessMode: perm_model.AccessModeAdmin, - unitsMode: map[unit.Type]perm_model.AccessMode{ - unit.TypeWiki: perm_model.AccessModeRead, - }, - } - assert.Equal(t, perm_model.AccessModeAdmin, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, admin overrides map") - - perm = Permission{ - AccessMode: perm_model.AccessModeNone, - unitsMode: map[unit.Type]perm_model.AccessMode{ - unit.TypeWiki: perm_model.AccessModeRead, - }, - } - assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "no unit, only map, use map") - - perm = Permission{ - AccessMode: perm_model.AccessModeNone, - units: []*repo_model.RepoUnit{ - {Type: unit.TypeWiki}, - }, - unitsMode: map[unit.Type]perm_model.AccessMode{ - unit.TypeWiki: perm_model.AccessModeRead, - }, - } - assert.Equal(t, perm_model.AccessModeRead, perm.UnitAccessMode(unit.TypeWiki), "has unit, and map, use map") } func TestGetRepoPermission(t *testing.T) { diff --git a/models/project/project_test.go b/models/project/project_test.go index b28bca92565..588c67e68fe 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -34,17 +34,13 @@ func TestIsProjectTypeValid(t *testing.T) { func TestGetProjects(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - projects, err := db.Find[Project](t.Context(), SearchOptions{RepoID: 1}) - assert.NoError(t, err) + for _, repoID := range []int64{1, 3} { + projects, err := db.Find[Project](t.Context(), SearchOptions{RepoID: repoID}) + assert.NoError(t, err) - // 1 value for this repo exists in the fixtures - assert.Len(t, projects, 1) - - projects, err = db.Find[Project](t.Context(), SearchOptions{RepoID: 3}) - assert.NoError(t, err) - - // 1 value for this repo exists in the fixtures - assert.Len(t, projects, 1) + // 1 value for this repo exists in the fixtures + assert.Len(t, projects, 1) + } } func TestProject(t *testing.T) { diff --git a/models/renderhelper/repo_file_test.go b/models/renderhelper/repo_file_test.go index 12e1275b2b0..61f1ecd6992 100644 --- a/models/renderhelper/repo_file_test.go +++ b/models/renderhelper/repo_file_test.go @@ -98,41 +98,40 @@ func TestRepoFile(t *testing.T) {

`), test.NormalizeHTMLSpaces(rendered)) }) -} -func TestRepoFileOrgMode(t *testing.T) { - unittest.PrepareTestEnv(t) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + t.Run("OrgMode", func(t *testing.T) { + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - t.Run("Links", func(t *testing.T) { - rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{ - CurrentRefSubURL: "/commit/1234", - CurrentTreePath: "my dir", - }).WithRelativePath("my dir/a.org") + t.Run("Links", func(t *testing.T) { + rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{ + CurrentRefSubURL: "/commit/1234", + CurrentTreePath: "my dir", + }).WithRelativePath("my dir/a.org") - rendered, err := testRenderString(rctx, ` + rendered, err := testRenderString(rctx, ` [[https://google.com/]] [[ImageLink.svg][The Image Desc]] `) - assert.NoError(t, err) - assert.Equal(t, `

+ assert.NoError(t, err) + assert.Equal(t, `

https://google.com/ The Image Desc

`, rendered) - }) + }) - t.Run("CodeHighlight", func(t *testing.T) { - rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{}).WithRelativePath("my-dir/a.org") + t.Run("CodeHighlight", func(t *testing.T) { + rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{}).WithRelativePath("my-dir/a.org") - rendered, err := testRenderString(rctx, ` + rendered, err := testRenderString(rctx, ` #+begin_src c int a = 1; #+end_src `) - assert.NoError(t, err) - assert.Equal(t, `
+ assert.NoError(t, err) + assert.Equal(t, `
int a = 1;
`, rendered) + }) }) } diff --git a/models/renderhelper/simple_document_test.go b/models/renderhelper/simple_document_test.go index b07c75a259c..5920eab231b 100644 --- a/models/renderhelper/simple_document_test.go +++ b/models/renderhelper/simple_document_test.go @@ -6,14 +6,12 @@ package renderhelper import ( "testing" - "gitea.dev/models/unittest" "gitea.dev/modules/markup/markdown" "github.com/stretchr/testify/assert" ) func TestSimpleDocument(t *testing.T) { - unittest.PrepareTestEnv(t) rctx := NewRenderContextSimpleDocument(t.Context(), "/base").WithMarkupType(markdown.MarkupName) rendered, err := testRenderString(rctx, ` 65f1bf27bc3bf70f64657658635e66094edbcb4d diff --git a/models/repo/archive_test.go b/models/repo/archive_test.go deleted file mode 100644 index bb6c1bf9bc2..00000000000 --- a/models/repo/archive_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestArchiveType(t *testing.T) { - name, archiveType := SplitArchiveNameType("test.tar.gz") - assert.Equal(t, "test", name) - assert.Equal(t, "tar.gz", archiveType.String()) - - name, archiveType = SplitArchiveNameType("a/b/test.zip") - assert.Equal(t, "a/b/test", name) - assert.Equal(t, "zip", archiveType.String()) - - name, archiveType = SplitArchiveNameType("1234.bundle") - assert.Equal(t, "1234", name) - assert.Equal(t, "bundle", archiveType.String()) - - name, archiveType = SplitArchiveNameType("test") - assert.Equal(t, "test", name) - assert.Equal(t, "unknown", archiveType.String()) - - name, archiveType = SplitArchiveNameType("test.xz") - assert.Equal(t, "test.xz", name) - assert.Equal(t, "unknown", archiveType.String()) -} diff --git a/models/repo/attachment_test.go b/models/repo/attachment_test.go index e5b43f9e5ee..7b1b2dad6ef 100644 --- a/models/repo/attachment_test.go +++ b/models/repo/attachment_test.go @@ -34,46 +34,73 @@ func TestIncreaseDownloadCount(t *testing.T) { func TestGetByCommentOrIssueID(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - // count of attachments from issue ID - attachments, err := repo_model.GetAttachmentsByIssueID(t.Context(), 1) - assert.NoError(t, err) - assert.Len(t, attachments, 1) + t.Run("GetByCommentOrIssueID", func(t *testing.T) { + // count of attachments from issue ID + attachments, err := repo_model.GetAttachmentsByIssueID(t.Context(), 1) + assert.NoError(t, err) + assert.Len(t, attachments, 1) - attachments, err = repo_model.GetAttachmentsByIssueID(t.Context(), 0) - assert.NoError(t, err) - assert.Empty(t, attachments) + attachments, err = repo_model.GetAttachmentsByIssueID(t.Context(), 0) + assert.NoError(t, err) + assert.Empty(t, attachments) - attachments, err = repo_model.GetAttachmentsByCommentID(t.Context(), 1) - assert.NoError(t, err) - assert.Len(t, attachments, 2) -} + attachments, err = repo_model.GetAttachmentsByCommentID(t.Context(), 1) + assert.NoError(t, err) + assert.Len(t, attachments, 2) + }) -func TestDeleteAttachments(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("GetAttachmentsByUUIDs", func(t *testing.T) { + attachList, err := repo_model.GetAttachmentsByUUIDs(t.Context(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) + assert.NoError(t, err) + assert.Len(t, attachList, 2) + assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID) + assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", attachList[1].UUID) + assert.Equal(t, int64(1), attachList[0].IssueID) + assert.Equal(t, int64(5), attachList[1].IssueID) + }) - count, err := repo_model.DeleteAttachmentsByIssue(t.Context(), 4, false) - assert.NoError(t, err) - assert.Equal(t, 2, count) + t.Run("GetUnlinkedAttachmentsByUserID", func(t *testing.T) { + attachments, err := repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 8) + assert.NoError(t, err) + assert.Len(t, attachments, 1) + assert.Equal(t, int64(10), attachments[0].ID) + assert.Zero(t, attachments[0].IssueID) + assert.Zero(t, attachments[0].ReleaseID) + assert.Zero(t, attachments[0].CommentID) - count, err = repo_model.DeleteAttachmentsByComment(t.Context(), 2, false) - assert.NoError(t, err) - assert.Equal(t, 2, count) + attachments, err = repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 1) + assert.NoError(t, err) + assert.Empty(t, attachments) + }) - err = repo_model.DeleteAttachment(t.Context(), &repo_model.Attachment{ID: 8}, false) - assert.NoError(t, err) + t.Run("DeleteAttachments", func(t *testing.T) { + count, err := repo_model.DeleteAttachmentsByIssue(t.Context(), 4, false) + assert.NoError(t, err) + assert.Equal(t, 2, count) - attachment, err := repo_model.GetAttachmentByUUID(t.Context(), "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") - assert.Error(t, err) - assert.True(t, repo_model.IsErrAttachmentNotExist(err)) - assert.Nil(t, attachment) -} + count, err = repo_model.DeleteAttachmentsByComment(t.Context(), 2, false) + assert.NoError(t, err) + assert.Equal(t, 2, count) -func TestGetAttachmentByID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + err = repo_model.DeleteAttachment(t.Context(), &repo_model.Attachment{ID: 8}, false) + assert.NoError(t, err) - attach, err := repo_model.GetAttachmentByID(t.Context(), 1) - assert.NoError(t, err) - assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) + attachment, err := repo_model.GetAttachmentByUUID(t.Context(), "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18") + assert.Error(t, err) + assert.True(t, repo_model.IsErrAttachmentNotExist(err)) + assert.Nil(t, attachment) + }) + + t.Run("UpdateAttachment", func(t *testing.T) { + attach, err := repo_model.GetAttachmentByID(t.Context(), 1) + assert.NoError(t, err) + assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) + + attach.Name = "new_name" + assert.NoError(t, repo_model.UpdateAttachment(t.Context(), attach)) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"}) + }) } func TestDeleteOrphanedAttachments(t *testing.T) { @@ -111,44 +138,3 @@ func TestAttachment_DownloadURL(t *testing.T) { } assert.Equal(t, "https://try.gitea.io/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.DownloadURL(t.Context())) } - -func TestUpdateAttachment(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - attach, err := repo_model.GetAttachmentByID(t.Context(), 1) - assert.NoError(t, err) - assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attach.UUID) - - attach.Name = "new_name" - assert.NoError(t, repo_model.UpdateAttachment(t.Context(), attach)) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{Name: "new_name"}) -} - -func TestGetAttachmentsByUUIDs(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - attachList, err := repo_model.GetAttachmentsByUUIDs(t.Context(), []string{"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", "not-existing-uuid"}) - assert.NoError(t, err) - assert.Len(t, attachList, 2) - assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", attachList[0].UUID) - assert.Equal(t, "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", attachList[1].UUID) - assert.Equal(t, int64(1), attachList[0].IssueID) - assert.Equal(t, int64(5), attachList[1].IssueID) -} - -func TestGetUnlinkedAttachmentsByUserID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - attachments, err := repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 8) - assert.NoError(t, err) - assert.Len(t, attachments, 1) - assert.Equal(t, int64(10), attachments[0].ID) - assert.Zero(t, attachments[0].IssueID) - assert.Zero(t, attachments[0].ReleaseID) - assert.Zero(t, attachments[0].CommentID) - - attachments, err = repo_model.GetUnlinkedAttachmentsByUserID(t.Context(), 1) - assert.NoError(t, err) - assert.Empty(t, attachments) -} diff --git a/models/repo/avatar_test.go b/models/repo/avatar_test.go index 9d895912d77..9f1bd610d09 100644 --- a/models/repo/avatar_test.go +++ b/models/repo/avatar_test.go @@ -25,3 +25,19 @@ func TestRepoAvatarLink(t *testing.T) { link = repo.AvatarLink(t.Context()) assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link) } + +func TestArchiveType(t *testing.T) { + for _, tc := range []struct { + input, name, archiveType string + }{ + {"test.tar.gz", "test", "tar.gz"}, + {"a/b/test.zip", "a/b/test", "zip"}, + {"1234.bundle", "1234", "bundle"}, + {"test", "test", "unknown"}, + {"test.xz", "test.xz", "unknown"}, + } { + name, archiveType := SplitArchiveNameType(tc.input) + assert.Equal(t, tc.name, name) + assert.Equal(t, tc.archiveType, archiveType.String()) + } +} diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index 5d2045524ae..6318b5b55af 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -15,107 +15,100 @@ import ( func TestRepository_GetCollaborators(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - test := func(repoID int64) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - collaborators, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{RepoID: repo.ID}) - assert.NoError(t, err) - expectedLen, err := db.GetEngine(t.Context()).Count(&repo_model.Collaboration{RepoID: repoID}) - assert.NoError(t, err) - assert.Len(t, collaborators, int(expectedLen)) - for _, collaborator := range collaborators { - assert.Equal(t, collaborator.User.ID, collaborator.Collaboration.UserID) - assert.Equal(t, repoID, collaborator.Collaboration.RepoID) + + t.Run("Repository_GetCollaborators", func(t *testing.T) { + for _, repoID := range []int64{1, 2, 3, 4} { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + collaborators, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{RepoID: repo.ID}) + assert.NoError(t, err) + expectedLen, err := db.GetEngine(t.Context()).Count(&repo_model.Collaboration{RepoID: repoID}) + assert.NoError(t, err) + assert.Len(t, collaborators, int(expectedLen)) + for _, collaborator := range collaborators { + assert.Equal(t, collaborator.User.ID, collaborator.Collaboration.UserID) + assert.Equal(t, repoID, collaborator.Collaboration.RepoID) + } } - } - test(1) - test(2) - test(3) - test(4) - // Test db.ListOptions - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) + // Test db.ListOptions + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) - collaborators1, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{ - ListOptions: db.ListOptions{PageSize: 1, Page: 1}, - RepoID: repo.ID, - }) - assert.NoError(t, err) - assert.Len(t, collaborators1, 1) - - collaborators2, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{ - ListOptions: db.ListOptions{PageSize: 1, Page: 2}, - RepoID: repo.ID, - }) - assert.NoError(t, err) - assert.Len(t, collaborators2, 1) - - assert.NotEqual(t, collaborators1[0].ID, collaborators2[0].ID) -} - -func TestRepository_IsCollaborator(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(repoID, userID int64, expected bool) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - actual, err := repo_model.IsCollaborator(t.Context(), repo.ID, userID) + collaborators1, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{ + ListOptions: db.ListOptions{PageSize: 1, Page: 1}, + RepoID: repo.ID, + }) assert.NoError(t, err) - assert.Equal(t, expected, actual) - } - test(3, 2, true) - test(3, unittest.NonexistentID, false) - test(4, 2, false) - test(4, 4, true) -} - -func TestRepository_IsOwnerMemberCollaborator(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - - // Organisation owner. - actual, err := repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 2) - assert.NoError(t, err) - assert.True(t, actual) - - // Team member. - actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 4) - assert.NoError(t, err) - assert.True(t, actual) - - // Normal user. - actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 1) - assert.NoError(t, err) - assert.False(t, actual) - - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - - // Collaborator. - actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo2, 4) - assert.NoError(t, err) - assert.True(t, actual) - - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) - - // Repository owner. - actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo3, 2) - assert.NoError(t, err) - assert.True(t, actual) -} - -func TestRepo_GetCollaboration(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - - // Existing collaboration. - collab, err := repo_model.GetCollaboration(t.Context(), repo.ID, 4) - assert.NoError(t, err) - assert.NotNil(t, collab) - assert.EqualValues(t, 4, collab.UserID) - assert.EqualValues(t, 4, collab.RepoID) - - // Non-existing collaboration. - collab, err = repo_model.GetCollaboration(t.Context(), repo.ID, 1) - assert.NoError(t, err) - assert.Nil(t, collab) + assert.Len(t, collaborators1, 1) + + collaborators2, _, err := repo_model.GetCollaborators(t.Context(), &repo_model.FindCollaborationOptions{ + ListOptions: db.ListOptions{PageSize: 1, Page: 2}, + RepoID: repo.ID, + }) + assert.NoError(t, err) + assert.Len(t, collaborators2, 1) + + assert.NotEqual(t, collaborators1[0].ID, collaborators2[0].ID) + }) + + t.Run("Repository_IsCollaborator", func(t *testing.T) { + test := func(repoID, userID int64, expected bool) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + actual, err := repo_model.IsCollaborator(t.Context(), repo.ID, userID) + assert.NoError(t, err) + assert.Equal(t, expected, actual) + } + test(3, 2, true) + test(3, unittest.NonexistentID, false) + test(4, 2, false) + test(4, 4, true) + }) + + t.Run("Repository_IsOwnerMemberCollaborator", func(t *testing.T) { + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + + // Organisation owner. + actual, err := repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 2) + assert.NoError(t, err) + assert.True(t, actual) + + // Team member. + actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 4) + assert.NoError(t, err) + assert.True(t, actual) + + // Normal user. + actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo1, 1) + assert.NoError(t, err) + assert.False(t, actual) + + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + + // Collaborator. + actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo2, 4) + assert.NoError(t, err) + assert.True(t, actual) + + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) + + // Repository owner. + actual, err = repo_model.HasAccessToRepoCodeUnit(t.Context(), repo3, 2) + assert.NoError(t, err) + assert.True(t, actual) + }) + + t.Run("Repo_GetCollaboration", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + + // Existing collaboration. + collab, err := repo_model.GetCollaboration(t.Context(), repo.ID, 4) + assert.NoError(t, err) + assert.NotNil(t, collab) + assert.EqualValues(t, 4, collab.UserID) + assert.EqualValues(t, 4, collab.RepoID) + + // Non-existing collaboration. + collab, err = repo_model.GetCollaboration(t.Context(), repo.ID, 1) + assert.NoError(t, err) + assert.Nil(t, collab) + }) } diff --git a/models/repo/pull_request_default_test.go b/models/repo/pull_request_default_test.go index 40be8d6573a..55401b05b9d 100644 --- a/models/repo/pull_request_default_test.go +++ b/models/repo/pull_request_default_test.go @@ -33,46 +33,37 @@ func TestDefaultTargetBranchSelection(t *testing.T) { } func TestPullRequestConfigFromDB(t *testing.T) { - cases := []struct { + for _, tc := range []struct { // name describes the row shape under test; the comments capture why each row matters. - name string - json string - wantMergeUpdate bool - wantRebaseUpdate bool - wantDefaultStyle UpdateStyle - wantValidatesPass bool + name string + json string + wantRebaseUpdate bool }{ { // Empty object exercises the all-defaults path (e.g. fresh repos created via low-level paths). name: "defaults", json: "{}", - wantMergeUpdate: true, wantRebaseUpdate: true, - wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true, + wantRebaseUpdate: true, }, { // Realistic upgrade case: pre-PR JSON lacks the new fields and has AllowRebaseUpdate=false. // Historical setting must be preserved while new fields take safe defaults. - name: "legacy without new fields", - json: `{"AllowMerge":true,"AllowRebase":true,"AllowRebaseMerge":true,"AllowSquash":true,"AllowRebaseUpdate":false}`, - wantMergeUpdate: true, wantRebaseUpdate: false, - wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true, + name: "legacy without new fields", + json: `{"AllowMerge":true,"AllowRebase":true,"AllowRebaseMerge":true,"AllowSquash":true,"AllowRebaseUpdate":false}`, + wantRebaseUpdate: false, }, { // Partially-migrated row with explicit empty string must normalize so ValidateUpdateSettings passes. name: "empty default style", json: `{"DefaultUpdateStyle":""}`, - wantMergeUpdate: true, wantRebaseUpdate: true, - wantDefaultStyle: UpdateStyleMerge, wantValidatesPass: true, + wantRebaseUpdate: true, }, - } - for _, tc := range cases { + } { t.Run(tc.name, func(t *testing.T) { cfg := new(PullRequestsConfig) assert.NoError(t, cfg.FromDB([]byte(tc.json))) - assert.Equal(t, tc.wantMergeUpdate, cfg.AllowMergeUpdate) + assert.True(t, cfg.AllowMergeUpdate) assert.Equal(t, tc.wantRebaseUpdate, cfg.AllowRebaseUpdate) - assert.Equal(t, tc.wantDefaultStyle, cfg.DefaultUpdateStyle) - if tc.wantValidatesPass { - assert.NoError(t, cfg.ValidateUpdateSettings()) - } + assert.Equal(t, UpdateStyleMerge, cfg.DefaultUpdateStyle) + assert.NoError(t, cfg.ValidateUpdateSettings()) }) } } diff --git a/models/repo/pushmirror_test.go b/models/repo/pushmirror_test.go index f4e25e67e52..5630e2c63b4 100644 --- a/models/repo/pushmirror_test.go +++ b/models/repo/pushmirror_test.go @@ -27,17 +27,12 @@ func TestPushMirrorsIterate(t *testing.T) { }) long, _ := time.ParseDuration("24h") - db.Insert(t.Context(), &repo_model.PushMirror{ - RemoteName: "test-2", - LastUpdateUnix: now, - Interval: long, - }) - - db.Insert(t.Context(), &repo_model.PushMirror{ - RemoteName: "test-3", - LastUpdateUnix: now, - Interval: 0, - }) + for _, mirror := range []*repo_model.PushMirror{ + {RemoteName: "test-2", LastUpdateUnix: now, Interval: long}, + {RemoteName: "test-3", LastUpdateUnix: now, Interval: 0}, + } { + db.Insert(t.Context(), mirror) + } repo_model.PushMirrorsIterate(t.Context(), 1, func(idx int, bean any) error { m, ok := bean.(*repo_model.PushMirror) diff --git a/models/repo/redirect_test.go b/models/repo/redirect_test.go index bceaf869216..c4bb27f6327 100644 --- a/models/repo/redirect_test.go +++ b/models/repo/redirect_test.go @@ -24,53 +24,37 @@ func TestLookupRedirect(t *testing.T) { } func TestNewRedirect(t *testing.T) { - // redirect to a completely new name - assert.NoError(t, unittest.PrepareTestDatabase()) + for _, tc := range []struct { + repoID int64 + newName string + }{ + {1, "newreponame"}, // redirect to a completely new name + {1, "oldrepo1"}, // redirect to previously used name + {2, "newreponame"}, // redirect for a previously-unredirected repo + } { + t.Run(tc.newName, func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, "newreponame")) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: tc.repoID}) + assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, tc.newName)) - unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ - OwnerID: repo.OwnerID, - LowerName: repo.LowerName, - RedirectRepoID: repo.ID, - }) - unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ - OwnerID: repo.OwnerID, - LowerName: "oldrepo1", - RedirectRepoID: repo.ID, - }) -} - -func TestNewRedirect2(t *testing.T) { - // redirect to previously used name - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, "oldrepo1")) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ - OwnerID: repo.OwnerID, - LowerName: repo.LowerName, - RedirectRepoID: repo.ID, - }) - unittest.AssertNotExistsBean(t, &repo_model.Redirect{ - OwnerID: repo.OwnerID, - LowerName: "oldrepo1", - RedirectRepoID: repo.ID, - }) -} - -func TestNewRedirect3(t *testing.T) { - // redirect for a previously-unredirected repo - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - assert.NoError(t, repo_model.NewRedirect(t.Context(), repo.OwnerID, repo.ID, repo.Name, "newreponame")) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ - OwnerID: repo.OwnerID, - LowerName: repo.LowerName, - RedirectRepoID: repo.ID, - }) + unittest.AssertExistsAndLoadBean(t, &repo_model.Redirect{ + OwnerID: repo.OwnerID, + LowerName: repo.LowerName, + RedirectRepoID: repo.ID, + }) + if tc.repoID == 1 { + redirect := &repo_model.Redirect{ + OwnerID: repo.OwnerID, + LowerName: "oldrepo1", + RedirectRepoID: repo.ID, + } + if tc.newName == "oldrepo1" { + unittest.AssertNotExistsBean(t, redirect) + } else { + unittest.AssertExistsAndLoadBean(t, redirect) + } + } + }) + } } diff --git a/models/repo/release_test.go b/models/repo/release_test.go index 888af26fea6..f3de84be468 100644 --- a/models/repo/release_test.go +++ b/models/repo/release_test.go @@ -30,75 +30,69 @@ func TestMigrate_InsertReleases(t *testing.T) { func Test_FindTagsByCommitIDs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - sha1Rels, err := FindTagsByCommitIDs(t.Context(), 1, "65f1bf27bc3bf70f64657658635e66094edbcb4d") - assert.NoError(t, err) - assert.Len(t, sha1Rels, 1) - rels := sha1Rels["65f1bf27bc3bf70f64657658635e66094edbcb4d"] - assert.Len(t, rels, 3) - assert.Equal(t, "v1.1", rels[0].TagName) - assert.Equal(t, "delete-tag", rels[1].TagName) - assert.Equal(t, "v1.0", rels[2].TagName) -} - -func TestAddReleaseAttachmentsRejectsDifferentRepo(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12" // attachment 2 belongs to repo 2 - err := AddReleaseAttachments(t.Context(), 1, []string{uuid}) - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrPermissionDenied) - - attach, err := GetAttachmentByUUID(t.Context(), uuid) - assert.NoError(t, err) - assert.Zero(t, attach.ReleaseID, "attachment should not be linked to release on failure") -} - -func TestAddReleaseAttachmentsRejectsIssueAttachment(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" - err := AddReleaseAttachments(t.Context(), 1, []string{uuid}) - assert.ErrorIs(t, err, util.ErrPermissionDenied) - - attach, err := GetAttachmentByUUID(t.Context(), uuid) - assert.NoError(t, err) - assert.Zero(t, attach.ReleaseID) -} - -func TestAddReleaseAttachmentsAllowsLegacyMissingRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - legacyUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20" // attachment 10 has repo_id 0 - err := AddReleaseAttachments(t.Context(), 1, []string{legacyUUID}) - assert.NoError(t, err) - - attach, err := GetAttachmentByUUID(t.Context(), legacyUUID) - assert.NoError(t, err) - assert.EqualValues(t, 1, attach.RepoID) - assert.EqualValues(t, 1, attach.ReleaseID) -} - -func TestAddReleaseAttachmentsRejectsRecentZeroRepoID(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - recentUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd3800aa" - attachment := &Attachment{ - UUID: recentUUID, - RepoID: 0, - IssueID: 0, - ReleaseID: 0, - CommentID: 0, - Name: "recent-zero", - CreatedUnix: LegacyAttachmentMissingRepoIDCutoff + 1, - } - assert.NoError(t, db.Insert(t.Context(), attachment)) - - err := AddReleaseAttachments(t.Context(), 1, []string{recentUUID}) - assert.Error(t, err) - assert.ErrorIs(t, err, util.ErrPermissionDenied) - - attach, err := GetAttachmentByUUID(t.Context(), recentUUID) - assert.NoError(t, err) - assert.Zero(t, attach.ReleaseID) - assert.Zero(t, attach.RepoID) + t.Run("_FindTagsByCommitIDs", func(t *testing.T) { + sha1Rels, err := FindTagsByCommitIDs(t.Context(), 1, "65f1bf27bc3bf70f64657658635e66094edbcb4d") + assert.NoError(t, err) + assert.Len(t, sha1Rels, 1) + rels := sha1Rels["65f1bf27bc3bf70f64657658635e66094edbcb4d"] + assert.Len(t, rels, 3) + assert.Equal(t, "v1.1", rels[0].TagName) + assert.Equal(t, "delete-tag", rels[1].TagName) + assert.Equal(t, "v1.0", rels[2].TagName) + }) + + t.Run("AddReleaseAttachmentsRejectsDifferentRepo", func(t *testing.T) { + uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12" // attachment 2 belongs to repo 2 + err := AddReleaseAttachments(t.Context(), 1, []string{uuid}) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrPermissionDenied) + + attach, err := GetAttachmentByUUID(t.Context(), uuid) + assert.NoError(t, err) + assert.Zero(t, attach.ReleaseID, "attachment should not be linked to release on failure") + }) + + t.Run("AddReleaseAttachmentsRejectsIssueAttachment", func(t *testing.T) { + uuid := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" + err := AddReleaseAttachments(t.Context(), 1, []string{uuid}) + assert.ErrorIs(t, err, util.ErrPermissionDenied) + + attach, err := GetAttachmentByUUID(t.Context(), uuid) + assert.NoError(t, err) + assert.Zero(t, attach.ReleaseID) + }) + + t.Run("AddReleaseAttachmentsRejectsRecentZeroRepoID", func(t *testing.T) { + recentUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd3800aa" + attachment := &Attachment{ + UUID: recentUUID, + RepoID: 0, + IssueID: 0, + ReleaseID: 0, + CommentID: 0, + Name: "recent-zero", + CreatedUnix: LegacyAttachmentMissingRepoIDCutoff + 1, + } + assert.NoError(t, db.Insert(t.Context(), attachment)) + + err := AddReleaseAttachments(t.Context(), 1, []string{recentUUID}) + assert.Error(t, err) + assert.ErrorIs(t, err, util.ErrPermissionDenied) + + attach, err := GetAttachmentByUUID(t.Context(), recentUUID) + assert.NoError(t, err) + assert.Zero(t, attach.ReleaseID) + assert.Zero(t, attach.RepoID) + }) + + t.Run("AddReleaseAttachmentsAllowsLegacyMissingRepoID", func(t *testing.T) { + legacyUUID := "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20" // attachment 10 has repo_id 0 + err := AddReleaseAttachments(t.Context(), 1, []string{legacyUUID}) + assert.NoError(t, err) + + attach, err := GetAttachmentByUUID(t.Context(), legacyUUID) + assert.NoError(t, err) + assert.EqualValues(t, 1, attach.RepoID) + assert.EqualValues(t, 1, attach.ReleaseID) + }) } diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index e36cfa2b517..a0709a66460 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -96,27 +96,20 @@ func TestMetas(t *testing.T) { }, } - testSuccess := func(expectedStyle string) { + for _, style := range []string{"", markup.IssueNameStyleAlphanumeric, markup.IssueNameStyleNumeric, markup.IssueNameStyleRegexp} { + externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = style repo.Units = []*RepoUnit{&externalTracker} repo.commonRenderingMetas = nil metas := repo.ComposeCommentMetas(t.Context()) - assert.Equal(t, expectedStyle, metas["style"]) + if style == "" { + style = markup.IssueNameStyleNumeric + } + assert.Equal(t, style, metas["style"]) assert.Equal(t, "testRepo", metas["repo"]) assert.Equal(t, "testOwner", metas["user"]) assert.Equal(t, "https://someurl.com/{user}/{repo}/{issue}", metas["externalTrackerLinkFormat"]) } - testSuccess(markup.IssueNameStyleNumeric) - - externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleAlphanumeric - testSuccess(markup.IssueNameStyleAlphanumeric) - - externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleNumeric - testSuccess(markup.IssueNameStyleNumeric) - - externalTracker.ExternalTrackerConfig().ExternalTrackerStyle = markup.IssueNameStyleRegexp - testSuccess(markup.IssueNameStyleRegexp) - repo, err := GetRepositoryByID(t.Context(), 3) assert.NoError(t, err) @@ -211,29 +204,21 @@ func TestComposeSSHCloneURL(t *testing.T) { } func TestIsUsableRepoName(t *testing.T) { - assert.NoError(t, IsUsableRepoName("a")) - assert.NoError(t, IsUsableRepoName("-1_.")) - assert.NoError(t, IsUsableRepoName(".profile")) + for _, name := range []string{"a", "-1_.", ".profile"} { + assert.NoError(t, IsUsableRepoName(name)) + } - assert.Error(t, IsUsableRepoName("-")) - assert.Error(t, IsUsableRepoName("🌞")) - assert.Error(t, IsUsableRepoName("the/repo")) - assert.Error(t, IsUsableRepoName("the..repo")) - assert.Error(t, IsUsableRepoName("foo.wiki")) - assert.Error(t, IsUsableRepoName("foo.git")) - assert.Error(t, IsUsableRepoName("foo.RSS")) + for _, name := range []string{"-", "🌞", "the/repo", "the..repo", "foo.wiki", "foo.git", "foo.RSS"} { + assert.Error(t, IsUsableRepoName(name)) + } } func TestIsValidSSHAccessRepoName(t *testing.T) { - assert.True(t, IsValidSSHAccessRepoName("a")) - assert.True(t, IsValidSSHAccessRepoName("-1_.")) - assert.True(t, IsValidSSHAccessRepoName(".profile")) - assert.True(t, IsValidSSHAccessRepoName("foo.wiki")) + for _, name := range []string{"a", "-1_.", ".profile", "foo.wiki"} { + assert.True(t, IsValidSSHAccessRepoName(name)) + } - assert.False(t, IsValidSSHAccessRepoName("-")) - assert.False(t, IsValidSSHAccessRepoName("🌞")) - assert.False(t, IsValidSSHAccessRepoName("the/repo")) - assert.False(t, IsValidSSHAccessRepoName("the..repo")) - assert.False(t, IsValidSSHAccessRepoName("foo.git")) - assert.False(t, IsValidSSHAccessRepoName("foo.RSS")) + for _, name := range []string{"-", "🌞", "the/repo", "the..repo", "foo.git", "foo.RSS"} { + assert.False(t, IsValidSSHAccessRepoName(name)) + } } diff --git a/models/repo/star_test.go b/models/repo/star_test.go index d81c3a1685b..df38abad20e 100644 --- a/models/repo/star_test.go +++ b/models/repo/star_test.go @@ -17,6 +17,29 @@ import ( func TestStarRepo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) + t.Run("IsStaring", func(t *testing.T) { + assert.True(t, repo_model.IsStaring(t.Context(), 2, 4)) + assert.False(t, repo_model.IsStaring(t.Context(), 3, 4)) + }) + + t.Run("Repository_GetStargazers", func(t *testing.T) { + // repo with stargazers + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0}) + assert.NoError(t, err) + if assert.Len(t, gazers, 1) { + assert.Equal(t, int64(2), gazers[0].ID) + } + }) + + t.Run("Repository_GetStargazers2", func(t *testing.T) { + // repo with stargazers + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0}) + assert.NoError(t, err) + assert.Empty(t, gazers) + }) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) @@ -27,49 +50,21 @@ func TestStarRepo(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, false)) unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) -} - -func TestIsStaring(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - assert.True(t, repo_model.IsStaring(t.Context(), 2, 4)) - assert.False(t, repo_model.IsStaring(t.Context(), 3, 4)) -} - -func TestRepository_GetStargazers(t *testing.T) { - // repo with stargazers - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0}) - assert.NoError(t, err) - if assert.Len(t, gazers, 1) { - assert.Equal(t, int64(2), gazers[0].ID) - } -} - -func TestRepository_GetStargazers2(t *testing.T) { - // repo with stargazers - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0}) - assert.NoError(t, err) - assert.Empty(t, gazers) -} - -func TestClearRepoStars(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) - assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, true)) - unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) - assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, false)) - unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) - assert.NoError(t, repo_model.ClearRepoStars(t.Context(), repo.ID)) - unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) - - gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0}) - assert.NoError(t, err) - assert.Empty(t, gazers) + + t.Run("ClearRepoStars", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) + assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, true)) + unittest.AssertExistsAndLoadBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) + assert.NoError(t, repo_model.StarRepo(t.Context(), user, repo, false)) + unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) + assert.NoError(t, repo_model.ClearRepoStars(t.Context(), repo.ID)) + unittest.AssertNotExistsBean(t, &repo_model.Star{UID: user.ID, RepoID: repo.ID}) + + gazers, err := repo_model.GetStargazers(t.Context(), repo, db.ListOptions{Page: 0}) + assert.NoError(t, err) + assert.Empty(t, gazers) + }) } diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index bf1388f985b..614a733c622 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -72,47 +72,26 @@ func TestWatchIfAuto(t *testing.T) { assert.NoError(t, err) assert.Len(t, watchers, repo.NumWatches) - setting.Service.AutoWatchOnChanges = false - prevCount := repo.NumWatches - - // Must not add watch - assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 8, 1, true)) - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Len(t, watchers, prevCount) - - // Should not add watch - assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 10, 1, true)) - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Len(t, watchers, prevCount) - - setting.Service.AutoWatchOnChanges = true - - // Must not add watch (user 8 has "don't watch" set) - assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 8, 1, true)) - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Len(t, watchers, prevCount) - - // Should not add watch (read operation) - assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 12, 1, false)) - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Len(t, watchers, prevCount) - - // Should add watch (write operation) - assert.NoError(t, repo_model.WatchIfAuto(t.Context(), 12, 1, true)) - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Len(t, watchers, prevCount+1) - - // Should not add watch (system user should not be able to watch) - assert.NoError(t, repo_model.WatchIfAuto(t.Context(), user_model.ActionsUserID, 1, true)) - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Len(t, watchers, prevCount+1) + for _, test := range []struct { + autoWatch bool + userID int64 + isWrite bool + count int + }{ + {false, 8, true, prevCount}, // Must not add watch + {false, 10, true, prevCount}, // Should not add watch + {true, 8, true, prevCount}, // Must not add watch (user 8 has "don't watch" set) + {true, 12, false, prevCount}, // Should not add watch (read operation) + {true, 12, true, prevCount + 1}, // Should add watch (write operation) + {true, user_model.ActionsUserID, true, prevCount + 1}, // Should not add watch (system user should not be able to watch) + } { + setting.Service.AutoWatchOnChanges = test.autoWatch + assert.NoError(t, repo_model.WatchIfAuto(t.Context(), test.userID, 1, test.isWrite)) + watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, err) + assert.Len(t, watchers, test.count) + } // Should remove watch, inhibit from adding auto assert.NoError(t, repo_model.WatchRepoAuto(t.Context(), user12, repo, false)) diff --git a/models/secret/secret_test.go b/models/secret/secret_test.go index 11dd0f750cd..8d1d2d1e371 100644 --- a/models/secret/secret_test.go +++ b/models/secret/secret_test.go @@ -40,135 +40,80 @@ func TestGetScopedSecretsForJob(t *testing.T) { return job } - t.Run("TopLevelJob_ReturnsBaseUnchanged", func(t *testing.T) { - const runID = 9001 - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: 0} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, base, got, "top-level jobs should see the full base scope") - }) - - t.Run("CallerInherit_PassesParentScopeThrough", func(t *testing.T) { - const runID = 9002 - caller := insertCaller(t, runID, 0, "inherit") - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, base, got, "secrets: inherit forwards everything from parent scope") - }) - - t.Run("CallerEmptySecrets_ExposesOnlyAutoTokens", func(t *testing.T) { - const runID = 9003 - caller := insertCaller(t, runID, 0, "") - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, map[string]string{ - "GITHUB_TOKEN": "tok", - "GITEA_TOKEN": "tok", - }, got) - }) - - t.Run("CallerMapping_OnlyMappedAliasesPlusTokens", func(t *testing.T) { - const runID = 9004 + for _, testCase := range []struct { + name string + runID int64 + callSecrets []string + check func(*testing.T, map[string]string) + }{ + {"TopLevelJob_ReturnsBaseUnchanged", 9001, nil, func(t *testing.T, got map[string]string) { + assert.Equal(t, base, got, "top-level jobs should see the full base scope") + }}, + {"CallerInherit_PassesParentScopeThrough", 9002, []string{"inherit"}, func(t *testing.T, got map[string]string) { + assert.Equal(t, base, got, "secrets: inherit forwards everything from parent scope") + }}, + {"CallerEmptySecrets_ExposesOnlyAutoTokens", 9003, []string{""}, func(t *testing.T, got map[string]string) { + assert.Equal(t, map[string]string{ + "GITHUB_TOKEN": "tok", + "GITEA_TOKEN": "tok", + }, got) + }}, // {alias: source} - the called workflow sees `secrets.MY_KEY` resolved to PROD_API_KEY's value. - caller := insertCaller(t, runID, 0, `{"MY_KEY":"PROD_API_KEY"}`) - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, map[string]string{ - "GITHUB_TOKEN": "tok", - "GITEA_TOKEN": "tok", - "MY_KEY": "prod-secret", - // no "dev-secret" - }, got) - }) - - t.Run("CallerMapping_CaseInsensitiveSource", func(t *testing.T) { - const runID = 9005 - caller := insertCaller(t, runID, 0, `{"alias":"prod_api_key"}`) - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, "prod-secret", got["alias"]) - }) - - t.Run("CallerMapping_UnknownSourceDropsAlias", func(t *testing.T) { - const runID = 9006 + {"CallerMapping_OnlyMappedAliasesPlusTokens", 9004, []string{`{"MY_KEY":"PROD_API_KEY"}`}, func(t *testing.T, got map[string]string) { + assert.Equal(t, map[string]string{ + "GITHUB_TOKEN": "tok", + "GITEA_TOKEN": "tok", + "MY_KEY": "prod-secret", + // no "dev-secret" + }, got) + }}, + {"CallerMapping_CaseInsensitiveSource", 9005, []string{`{"alias":"prod_api_key"}`}, func(t *testing.T, got map[string]string) { + assert.Equal(t, "prod-secret", got["alias"]) + }}, // alias points at a non-existent secret name, so it must be dropped. - caller := insertCaller(t, runID, 0, `{"MAPPED_ALIAS":"DOES_NOT_EXIST"}`) - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: caller.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - _, present := got["MAPPED_ALIAS"] - assert.False(t, present) - }) - - t.Run("Nested_InheritThenInherit_FullScope", func(t *testing.T) { - const runID = 9007 - outer := insertCaller(t, runID, 0, "inherit") - inner := insertCaller(t, runID, outer.ID, "inherit") - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, base, got, "inherit-then-inherit should pass the full base scope through") - }) - - t.Run("Nested_InheritThenMapping_InnerNarrows", func(t *testing.T) { - const runID = 9008 + {"CallerMapping_UnknownSourceDropsAlias", 9006, []string{`{"MAPPED_ALIAS":"DOES_NOT_EXIST"}`}, func(t *testing.T, got map[string]string) { + _, present := got["MAPPED_ALIAS"] + assert.False(t, present) + }}, + {"Nested_InheritThenInherit_FullScope", 9007, []string{"inherit", "inherit"}, func(t *testing.T, got map[string]string) { + assert.Equal(t, base, got, "inherit-then-inherit should pass the full base scope through") + }}, // inner mapping narrows the full scope it inherited from outer. - outer := insertCaller(t, runID, 0, "inherit") - inner := insertCaller(t, runID, outer.ID, `{"ALIAS_OUT":"PROD_API_KEY"}`) - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, map[string]string{ - "GITHUB_TOKEN": "tok", - "GITEA_TOKEN": "tok", - "ALIAS_OUT": "prod-secret", - // no "dev-secret" - }, got) - }) - - t.Run("Nested_MappingThenInherit_OuterNarrows", func(t *testing.T) { - const runID = 9009 + {"Nested_InheritThenMapping_InnerNarrows", 9008, []string{"inherit", `{"ALIAS_OUT":"PROD_API_KEY"}`}, func(t *testing.T, got map[string]string) { + assert.Equal(t, map[string]string{ + "GITHUB_TOKEN": "tok", + "GITEA_TOKEN": "tok", + "ALIAS_OUT": "prod-secret", + // no "dev-secret" + }, got) + }}, // inner inherits outer's already-narrowed scope, so leaf sees only auto-tokens + OUTER_ALIAS. - outer := insertCaller(t, runID, 0, `{"OUTER_ALIAS":"PROD_API_KEY"}`) - inner := insertCaller(t, runID, outer.ID, "inherit") - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, map[string]string{ - "GITHUB_TOKEN": "tok", - "GITEA_TOKEN": "tok", - "OUTER_ALIAS": "prod-secret", - // no "dev-secret" - }, got) - }) - - t.Run("Nested_MappingThenMapping_InnerSourceMustExistInOuterScope", func(t *testing.T) { - const runID = 9010 + {"Nested_MappingThenInherit_OuterNarrows", 9009, []string{`{"OUTER_ALIAS":"PROD_API_KEY"}`, "inherit"}, func(t *testing.T, got map[string]string) { + assert.Equal(t, map[string]string{ + "GITHUB_TOKEN": "tok", + "GITEA_TOKEN": "tok", + "OUTER_ALIAS": "prod-secret", + // no "dev-secret" + }, got) + }}, // inner can rename ALIAS_A (in outer's scope) to ALIAS_C, but cannot forward DEV_API_KEY, which outer dropped. - outer := insertCaller(t, runID, 0, `{"ALIAS_A":"PROD_API_KEY"}`) - inner := insertCaller(t, runID, outer.ID, `{"ALIAS_B":"DEV_API_KEY","ALIAS_C":"ALIAS_A"}`) - leaf := &actions_model.ActionRunJob{RunID: runID, ParentJobID: inner.ID} - - got, err := getScopedSecretsForJob(ctx, leaf, base) - require.NoError(t, err) - assert.Equal(t, map[string]string{ - "GITHUB_TOKEN": "tok", - "GITEA_TOKEN": "tok", - "ALIAS_C": "prod-secret", - // no "dev-secret" - }, got) - }) + {"Nested_MappingThenMapping_InnerSourceMustExistInOuterScope", 9010, []string{`{"ALIAS_A":"PROD_API_KEY"}`, `{"ALIAS_B":"DEV_API_KEY","ALIAS_C":"ALIAS_A"}`}, func(t *testing.T, got map[string]string) { + assert.Equal(t, map[string]string{ + "GITHUB_TOKEN": "tok", + "GITEA_TOKEN": "tok", + "ALIAS_C": "prod-secret", + // no "dev-secret" + }, got) + }}, + } { + t.Run(testCase.name, func(t *testing.T) { + leaf := &actions_model.ActionRunJob{RunID: testCase.runID, ParentJobID: 0} + for _, callSecrets := range testCase.callSecrets { + leaf.ParentJobID = insertCaller(t, testCase.runID, leaf.ParentJobID, callSecrets).ID + } + got, err := getScopedSecretsForJob(ctx, leaf, base) + require.NoError(t, err) + testCase.check(t, got) + }) + } } diff --git a/models/system/setting_test.go b/models/system/setting_test.go index c319e2b54dd..cccbb46dfa3 100644 --- a/models/system/setting_test.go +++ b/models/system/setting_test.go @@ -24,21 +24,15 @@ func TestSettings(t *testing.T) { assert.Equal(t, 1, rev) assert.Len(t, settings, 1) // there is only one "revision" key - err = system.SetSettings(t.Context(), map[string]string{keyName: "true"}) - assert.NoError(t, err) - rev, settings, err = system.GetAllSettings(t.Context()) - assert.NoError(t, err) - assert.Equal(t, 2, rev) - assert.Len(t, settings, 2) - assert.Equal(t, "true", settings[keyName]) - - err = system.SetSettings(t.Context(), map[string]string{keyName: "false"}) - assert.NoError(t, err) - rev, settings, err = system.GetAllSettings(t.Context()) - assert.NoError(t, err) - assert.Equal(t, 3, rev) - assert.Len(t, settings, 2) - assert.Equal(t, "false", settings[keyName]) + for index, value := range []string{"true", "false"} { + err = system.SetSettings(t.Context(), map[string]string{keyName: value}) + assert.NoError(t, err) + rev, settings, err = system.GetAllSettings(t.Context()) + assert.NoError(t, err) + assert.Equal(t, index+2, rev) + assert.Len(t, settings, 2) + assert.Equal(t, value, settings[keyName]) + } // setting the same value should not trigger DuplicateKey error, and the "version" should be increased err = system.SetSettings(t.Context(), map[string]string{keyName: "false"}) diff --git a/models/unit/unit_test.go b/models/unit/unit_test.go index 5c69ec21ba1..c96b831c287 100644 --- a/models/unit/unit_test.go +++ b/models/unit/unit_test.go @@ -24,39 +24,26 @@ func mockUnitConfig(t *testing.T) { } func TestLoadUnitConfig(t *testing.T) { - t.Run("regular", func(t *testing.T) { - mockUnitConfig(t) + for _, testCase := range []struct { + name string + disabled, defaults, forks []string + }{ + {"regular", []string{"repo.issues"}, []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"}, []string{"repo.releases"}}, + {"invalid", []string{"repo.issues", "invalid.1"}, []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"}, []string{"invalid.3", "repo.releases"}}, + {"duplicate", []string{"repo.issues", "repo.issues"}, []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"}, []string{"repo.releases", "repo.releases"}}, + } { + t.Run(testCase.name, func(t *testing.T) { + mockUnitConfig(t) - setting.Repository.DisabledRepoUnits = []string{"repo.issues"} - setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls"} - setting.Repository.DefaultForkRepoUnits = []string{"repo.releases"} - assert.NoError(t, LoadUnitConfig()) - assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet()) - assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) - assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) - }) - t.Run("invalid", func(t *testing.T) { - mockUnitConfig(t) - - setting.Repository.DisabledRepoUnits = []string{"repo.issues", "invalid.1"} - setting.Repository.DefaultRepoUnits = []string{"repo.code", "invalid.2", "repo.releases", "repo.issues", "repo.pulls"} - setting.Repository.DefaultForkRepoUnits = []string{"invalid.3", "repo.releases"} - assert.NoError(t, LoadUnitConfig()) - assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet()) - assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) - assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) - }) - t.Run("duplicate", func(t *testing.T) { - mockUnitConfig(t) - - setting.Repository.DisabledRepoUnits = []string{"repo.issues", "repo.issues"} - setting.Repository.DefaultRepoUnits = []string{"repo.code", "repo.releases", "repo.issues", "repo.pulls", "repo.code"} - setting.Repository.DefaultForkRepoUnits = []string{"repo.releases", "repo.releases"} - assert.NoError(t, LoadUnitConfig()) - assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet()) - assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) - assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) - }) + setting.Repository.DisabledRepoUnits = testCase.disabled + setting.Repository.DefaultRepoUnits = testCase.defaults + setting.Repository.DefaultForkRepoUnits = testCase.forks + assert.NoError(t, LoadUnitConfig()) + assert.Equal(t, []Type{TypeIssues}, DisabledRepoUnitsGet()) + assert.Equal(t, []Type{TypeCode, TypeReleases, TypePullRequests}, DefaultRepoUnits) + assert.Equal(t, []Type{TypeReleases}, DefaultForkRepoUnits) + }) + } t.Run("empty_default", func(t *testing.T) { mockUnitConfig(t) diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go index 15e2d84b2e9..e54fe09dd41 100644 --- a/models/user/email_address_test.go +++ b/models/user/email_address_test.go @@ -15,138 +15,117 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetEmailAddresses(t *testing.T) { +func TestEmailAddresses(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1)) - if assert.Len(t, emails, 3) { + t.Run("GetEmailAddresses", func(t *testing.T) { + emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1)) + if assert.Len(t, emails, 3) { + assert.True(t, emails[0].IsPrimary) + assert.True(t, emails[2].IsActivated) + assert.False(t, emails[2].IsPrimary) + } + + emails, _ = user_model.GetEmailAddresses(t.Context(), int64(2)) + if assert.Len(t, emails, 2) { + assert.True(t, emails[0].IsPrimary) + assert.True(t, emails[0].IsActivated) + } + }) + + t.Run("IsEmailUsed", func(t *testing.T) { + isExist, _ := user_model.IsEmailUsed(t.Context(), "") + assert.True(t, isExist) + isExist, _ = user_model.IsEmailUsed(t.Context(), "user11@example.com") + assert.True(t, isExist) + isExist, _ = user_model.IsEmailUsed(t.Context(), "user1234567890@example.com") + assert.False(t, isExist) + }) + + t.Run("ListEmails", func(t *testing.T) { + search := func(opts *user_model.SearchEmailOptions) ([]*user_model.SearchEmailResult, int64) { + emails, count, err := user_model.SearchEmails(t.Context(), opts) + assert.NoError(t, err) + return emails, count + } + + // Must find all users and their emails + emails, count := search(&user_model.SearchEmailOptions{ListOptions: db.ListOptions{PageSize: 10000}}) + assert.Greater(t, count, int64(5)) + + contains := func(match func(s *user_model.SearchEmailResult) bool) bool { + return slices.ContainsFunc(emails, match) + } + + assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 })) + // 'org3' is an organization + assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 3 })) + + // Must find no records + emails, count = search(&user_model.SearchEmailOptions{Keyword: "NOTFOUND"}) + assert.Equal(t, int64(0), count) + + // Must find users 'user2', 'user28', etc. + emails, count = search(&user_model.SearchEmailOptions{Keyword: "user2"}) + assert.NotEqual(t, int64(0), count) + assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 2 })) + assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 })) + + // Must find only primary addresses (i.e. from the `user` table) + emails, _ = search(&user_model.SearchEmailOptions{IsPrimary: optional.Some(true)}) + assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary })) + assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary })) + + // Must find only inactive addresses (i.e. not validated) + emails, _ = search(&user_model.SearchEmailOptions{IsActivated: optional.Some(false)}) + assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated })) + assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsActivated })) + + // Must find more than one page, but retrieve only one + emails, count = search(&user_model.SearchEmailOptions{ListOptions: db.ListOptions{PageSize: 5, Page: 1}}) + assert.Len(t, emails, 5) + assert.Greater(t, count, int64(len(emails))) + }) + + t.Run("MakeEmailPrimary", func(t *testing.T) { + err := user_model.MakeActiveEmailPrimary(t.Context(), 1, 9999999) + assert.Error(t, err) + assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) + + email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user11@example.com"}) + err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID) + assert.Error(t, err) + assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) // inactive email is considered as not exist for "MakeActiveEmailPrimary" + + email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user9999999@example.com"}) + err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID) + assert.Error(t, err) + assert.True(t, user_model.IsErrUserNotExist(err)) + + email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user101@example.com"}) + err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID) + assert.NoError(t, err) + + user, _ := user_model.GetUserByID(t.Context(), int64(10)) + assert.Equal(t, "user101@example.com", user.Email) + }) + + t.Run("Activate", func(t *testing.T) { + email := &user_model.EmailAddress{ + ID: int64(1), + UID: int64(1), + Email: "user11@example.com", + } + assert.NoError(t, user_model.ActivateEmail(t.Context(), email)) + + emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1)) + assert.Len(t, emails, 3) + assert.True(t, emails[0].IsActivated) assert.True(t, emails[0].IsPrimary) + assert.False(t, emails[1].IsPrimary) assert.True(t, emails[2].IsActivated) assert.False(t, emails[2].IsPrimary) - } - - emails, _ = user_model.GetEmailAddresses(t.Context(), int64(2)) - if assert.Len(t, emails, 2) { - assert.True(t, emails[0].IsPrimary) - assert.True(t, emails[0].IsActivated) - } -} - -func TestIsEmailUsed(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - isExist, _ := user_model.IsEmailUsed(t.Context(), "") - assert.True(t, isExist) - isExist, _ = user_model.IsEmailUsed(t.Context(), "user11@example.com") - assert.True(t, isExist) - isExist, _ = user_model.IsEmailUsed(t.Context(), "user1234567890@example.com") - assert.False(t, isExist) -} - -func TestMakeEmailPrimary(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - err := user_model.MakeActiveEmailPrimary(t.Context(), 1, 9999999) - assert.Error(t, err) - assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) - - email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user11@example.com"}) - err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID) - assert.Error(t, err) - assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) // inactive email is considered as not exist for "MakeActiveEmailPrimary" - - email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user9999999@example.com"}) - err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID) - assert.Error(t, err) - assert.True(t, user_model.IsErrUserNotExist(err)) - - email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user101@example.com"}) - err = user_model.MakeActiveEmailPrimary(t.Context(), email.UID, email.ID) - assert.NoError(t, err) - - user, _ := user_model.GetUserByID(t.Context(), int64(10)) - assert.Equal(t, "user101@example.com", user.Email) -} - -func TestActivate(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - email := &user_model.EmailAddress{ - ID: int64(1), - UID: int64(1), - Email: "user11@example.com", - } - assert.NoError(t, user_model.ActivateEmail(t.Context(), email)) - - emails, _ := user_model.GetEmailAddresses(t.Context(), int64(1)) - assert.Len(t, emails, 3) - assert.True(t, emails[0].IsActivated) - assert.True(t, emails[0].IsPrimary) - assert.False(t, emails[1].IsPrimary) - assert.True(t, emails[2].IsActivated) - assert.False(t, emails[2].IsPrimary) -} - -func TestListEmails(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - // Must find all users and their emails - opts := &user_model.SearchEmailOptions{ - ListOptions: db.ListOptions{ - PageSize: 10000, - }, - } - emails, count, err := user_model.SearchEmails(t.Context(), opts) - assert.NoError(t, err) - assert.Greater(t, count, int64(5)) - - contains := func(match func(s *user_model.SearchEmailResult) bool) bool { - return slices.ContainsFunc(emails, match) - } - - assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 })) - // 'org3' is an organization - assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 3 })) - - // Must find no records - opts = &user_model.SearchEmailOptions{Keyword: "NOTFOUND"} - emails, count, err = user_model.SearchEmails(t.Context(), opts) - assert.NoError(t, err) - assert.Equal(t, int64(0), count) - - // Must find users 'user2', 'user28', etc. - opts = &user_model.SearchEmailOptions{Keyword: "user2"} - emails, count, err = user_model.SearchEmails(t.Context(), opts) - assert.NoError(t, err) - assert.NotEqual(t, int64(0), count) - assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 2 })) - assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 })) - - // Must find only primary addresses (i.e. from the `user` table) - opts = &user_model.SearchEmailOptions{IsPrimary: optional.Some(true)} - emails, _, err = user_model.SearchEmails(t.Context(), opts) - assert.NoError(t, err) - assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary })) - assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary })) - - // Must find only inactive addresses (i.e. not validated) - opts = &user_model.SearchEmailOptions{IsActivated: optional.Some(false)} - emails, _, err = user_model.SearchEmails(t.Context(), opts) - assert.NoError(t, err) - assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated })) - assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsActivated })) - - // Must find more than one page, but retrieve only one - opts = &user_model.SearchEmailOptions{ - ListOptions: db.ListOptions{ - PageSize: 5, - Page: 1, - }, - } - emails, count, err = user_model.SearchEmails(t.Context(), opts) - assert.NoError(t, err) - assert.Len(t, emails, 5) - assert.Greater(t, count, int64(len(emails))) + }) } func TestEmailAddressValidate(t *testing.T) { diff --git a/models/user/openid_test.go b/models/user/openid_test.go index b9a2572adb2..fcb2752b562 100644 --- a/models/user/openid_test.go +++ b/models/user/openid_test.go @@ -14,57 +14,57 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetUserOpenIDs(t *testing.T) { +func TestUserOpenIDs(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - oids, err := user_model.GetUserOpenIDs(t.Context(), int64(1)) - if assert.NoError(t, err) && assert.Len(t, oids, 2) { - assert.Equal(t, "https://user1.domain1.tld/", oids[0].URI) + t.Run("GetUserOpenIDs", func(t *testing.T) { + oids, err := user_model.GetUserOpenIDs(t.Context(), int64(1)) + if assert.NoError(t, err) && assert.Len(t, oids, 2) { + assert.Equal(t, "https://user1.domain1.tld/", oids[0].URI) + assert.False(t, oids[0].Show) + assert.Equal(t, "http://user1.domain2.tld/", oids[1].URI) + assert.True(t, oids[1].Show) + } + + oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) + if assert.NoError(t, err) && assert.Len(t, oids, 1) { + assert.Equal(t, "https://domain1.tld/user2/", oids[0].URI) + assert.True(t, oids[0].Show) + } + }) + + t.Run("ToggleUserOpenIDVisibilityRequiresOwnership", func(t *testing.T) { + unauthorizedUser, err := user_model.GetUserByID(t.Context(), int64(2)) + require.NoError(t, err) + + err = user_model.ToggleUserOpenIDVisibility(t.Context(), int64(1), unauthorizedUser) + require.Error(t, err) + assert.ErrorIs(t, err, util.ErrNotExist) + }) + + t.Run("ToggleUserOpenIDVisibility", func(t *testing.T) { + user, err := user_model.GetUserByID(t.Context(), int64(2)) + require.NoError(t, err) + oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2)) + require.NoError(t, err) + require.Len(t, oids, 1) + assert.True(t, oids[0].Show) + + err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user) + require.NoError(t, err) + + oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) + require.NoError(t, err) + require.Len(t, oids, 1) + assert.False(t, oids[0].Show) - assert.Equal(t, "http://user1.domain2.tld/", oids[1].URI) - assert.True(t, oids[1].Show) - } + err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user) + require.NoError(t, err) - oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) - if assert.NoError(t, err) && assert.Len(t, oids, 1) { - assert.Equal(t, "https://domain1.tld/user2/", oids[0].URI) - assert.True(t, oids[0].Show) - } -} - -func TestToggleUserOpenIDVisibility(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user, err := user_model.GetUserByID(t.Context(), int64(2)) - require.NoError(t, err) - oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2)) - require.NoError(t, err) - require.Len(t, oids, 1) - assert.True(t, oids[0].Show) - - err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user) - require.NoError(t, err) - - oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) - require.NoError(t, err) - require.Len(t, oids, 1) - - assert.False(t, oids[0].Show) - err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user) - require.NoError(t, err) - - oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) - require.NoError(t, err) - if assert.Len(t, oids, 1) { - assert.True(t, oids[0].Show) - } -} - -func TestToggleUserOpenIDVisibilityRequiresOwnership(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - unauthorizedUser, err := user_model.GetUserByID(t.Context(), int64(2)) - require.NoError(t, err) - - err = user_model.ToggleUserOpenIDVisibility(t.Context(), int64(1), unauthorizedUser) - require.Error(t, err) - assert.ErrorIs(t, err, util.ErrNotExist) + oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2)) + require.NoError(t, err) + if assert.Len(t, oids, 1) { + assert.True(t, oids[0].Show) + } + }) } diff --git a/models/user/user_test.go b/models/user/user_test.go index 7d8f57ed0ce..a720d9425df 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -28,18 +28,13 @@ import ( ) func TestIsUsableUsername(t *testing.T) { - assert.NoError(t, user_model.IsUsableUsername("a")) - assert.NoError(t, user_model.IsUsableUsername("foo.wiki")) - assert.NoError(t, user_model.IsUsableUsername("foo.git")) + for _, username := range []string{"a", "foo.wiki", "foo.git"} { + assert.NoError(t, user_model.IsUsableUsername(username)) + } - assert.Error(t, user_model.IsUsableUsername("a--b")) - assert.Error(t, user_model.IsUsableUsername("-1_.")) - assert.Error(t, user_model.IsUsableUsername(".profile")) - assert.Error(t, user_model.IsUsableUsername("-")) - assert.Error(t, user_model.IsUsableUsername("🌞")) - assert.Error(t, user_model.IsUsableUsername("the..repo")) - assert.Error(t, user_model.IsUsableUsername("foo.RSS")) - assert.Error(t, user_model.IsUsableUsername("foo.PnG")) + for _, username := range []string{"a--b", "-1_.", ".profile", "-", "🌞", "the..repo", "foo.RSS", "foo.PnG"} { + assert.Error(t, user_model.IsUsableUsername(username)) + } } func TestOAuth2Application_LoadUser(t *testing.T) { @@ -161,28 +156,17 @@ func TestSearchUsers(t *testing.T) { } // test orgs - testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) { - opts.Types = []user_model.UserType{user_model.UserTypeOrganization} - testSuccess(opts, expectedOrgIDs) + for page, expectedOrgIDs := range [][]int64{{3, 6}, {7, 17}, {19, 25}, {26, 41}, {42}} { + testSuccess(user_model.SearchUserOptions{ + Types: []user_model.UserType{user_model.UserTypeOrganization}, + OrderBy: "id ASC", + ListOptions: db.ListOptions{Page: page + 1, PageSize: 2}, + }, expectedOrgIDs) } - - testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1, PageSize: 2}}, - []int64{3, 6}) - - testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 2, PageSize: 2}}, - []int64{7, 17}) - - testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 3, PageSize: 2}}, - []int64{19, 25}) - - testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 4, PageSize: 2}}, - []int64{26, 41}) - - testOrgSuccess(user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 5, PageSize: 2}}, - []int64{42}) - - testOrgSuccess(user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 6, PageSize: 2}}, - []int64{}) + testSuccess(user_model.SearchUserOptions{ + Types: []user_model.UserType{user_model.UserTypeOrganization}, + ListOptions: db.ListOptions{Page: 6, PageSize: 2}, + }, []int64{}) // test users testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) { @@ -403,50 +387,34 @@ func TestGetMaileableUsersByIDs(t *testing.T) { } func TestNewUserRedirect(t *testing.T) { - // redirect to a completely new name - assert.NoError(t, unittest.PrepareTestDatabase()) + for _, testCase := range []struct { + userID int64 + newName string + }{ + {1, "newusername"}, // redirect to a completely new name + {1, "olduser1"}, // redirect to previously used name + {2, "newusername"}, // redirect for a previously-unredirected user + } { + t.Run(fmt.Sprintf("%d/%s", testCase.userID, testCase.newName), func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, "newusername")) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.userID}) + assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, testCase.newName)) - unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{ - LowerName: user.LowerName, - RedirectUserID: user.ID, - }) - unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{ - LowerName: "olduser1", - RedirectUserID: user.ID, - }) -} - -func TestNewUserRedirect2(t *testing.T) { - // redirect to previously used name - assert.NoError(t, unittest.PrepareTestDatabase()) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, "olduser1")) - - unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{ - LowerName: user.LowerName, - RedirectUserID: user.ID, - }) - unittest.AssertNotExistsBean(t, &user_model.Redirect{ - LowerName: "olduser1", - RedirectUserID: user.ID, - }) -} - -func TestNewUserRedirect3(t *testing.T) { - // redirect for a previously-unredirected user - assert.NoError(t, unittest.PrepareTestDatabase()) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - assert.NoError(t, user_model.NewUserRedirect(t.Context(), user.ID, user.Name, "newusername")) - - unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{ - LowerName: user.LowerName, - RedirectUserID: user.ID, - }) + unittest.AssertExistsAndLoadBean(t, &user_model.Redirect{ + LowerName: user.LowerName, + RedirectUserID: user.ID, + }) + if testCase.userID == 1 { + redirect := &user_model.Redirect{LowerName: "olduser1", RedirectUserID: user.ID} + if testCase.newName == "olduser1" { + unittest.AssertNotExistsBean(t, redirect) + } else { + unittest.AssertExistsAndLoadBean(t, redirect) + } + } + }) + } } func TestGetUserByOpenID(t *testing.T) { @@ -546,13 +514,11 @@ func TestIsUserVisibleToViewer(t *testing.T) { // limited user test(user33, user33, true) - test(user33, user4, true) test(user33, user29, false) test(user33, nil, false) // private user test(user31, user31, true) - test(user31, user4, false) test(user31, user20, true) test(user31, user29, false) test(user31, user33, true) diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index ef8294565b4..0248ef56ede 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -322,120 +322,51 @@ func TestUpdateHookTask(t *testing.T) { unittest.AssertExistsAndLoadBean(t, hookTask) } -func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) +func TestCleanupHookTaskTable(t *testing.T) { + for _, test := range []struct { + name string + url string + cleanupType HookTaskCleanupType + isDelivered bool + delivered func() timeutil.TimeStampNano + numberToKeep int + deleted bool + }{ + {"PerWebhook_DeletesDelivered", "https://www.example.com/cleanup1", PerWebhook, true, timeutil.TimeStampNanoNow, 0, true}, + {"PerWebhook_LeavesUndelivered", "https://www.example.com/cleanup2", PerWebhook, false, nil, 0, false}, + {"PerWebhook_LeavesMostRecentTask", "https://www.example.com/cleanup3", PerWebhook, true, timeutil.TimeStampNanoNow, 1, false}, + {"OlderThan_DeletesDelivered", "https://www.example.com/cleanup4", OlderThan, true, func() timeutil.TimeStampNano { + return timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()) + }, 0, true}, + {"OlderThan_LeavesUndelivered", "https://www.example.com/cleanup5", OlderThan, false, nil, 0, false}, + {"OlderThan_LeavesTaskEarlierThanAgeToDelete", "https://www.example.com/cleanup6", OlderThan, true, func() timeutil.TimeStampNano { + return timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()) + }, 0, false}, + } { + t.Run(test.name, func(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) - hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup1", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} - require.NoError(t, db.Insert(t.Context(), hook)) - hookTask := &HookTask{ - HookID: hook.ID, - IsDelivered: true, - Delivered: timeutil.TimeStampNanoNow(), - PayloadVersion: 2, + hook := &Webhook{RepoID: 3, URL: test.url, ContentType: ContentTypeJSON, Events: `{"push_only":true}`} + require.NoError(t, db.Insert(t.Context(), hook)) + hookTask := &HookTask{ + HookID: hook.ID, + IsDelivered: test.isDelivered, + PayloadVersion: 2, + } + if test.delivered != nil { + hookTask.Delivered = test.delivered() + } + unittest.AssertNotExistsBean(t, hookTask) + _, err := CreateHookTask(t.Context(), hookTask) + assert.NoError(t, err) + unittest.AssertExistsAndLoadBean(t, hookTask) + + assert.NoError(t, CleanupHookTaskTable(t.Context(), test.cleanupType, 168*time.Hour, test.numberToKeep)) + if test.deleted { + unittest.AssertNotExistsBean(t, hookTask) + } else { + unittest.AssertExistsAndLoadBean(t, hookTask) + } + }) } - unittest.AssertNotExistsBean(t, hookTask) - _, err := CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, hookTask) - - assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 0)) - unittest.AssertNotExistsBean(t, hookTask) -} - -func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup2", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} - require.NoError(t, db.Insert(t.Context(), hook)) - hookTask := &HookTask{ - HookID: hook.ID, - IsDelivered: false, - PayloadVersion: 2, - } - unittest.AssertNotExistsBean(t, hookTask) - _, err := CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, hookTask) - - assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 0)) - unittest.AssertExistsAndLoadBean(t, hookTask) -} - -func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup3", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} - require.NoError(t, db.Insert(t.Context(), hook)) - hookTask := &HookTask{ - HookID: hook.ID, - IsDelivered: true, - Delivered: timeutil.TimeStampNanoNow(), - PayloadVersion: 2, - } - unittest.AssertNotExistsBean(t, hookTask) - _, err := CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, hookTask) - - assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 1)) - unittest.AssertExistsAndLoadBean(t, hookTask) -} - -func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup4", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} - require.NoError(t, db.Insert(t.Context(), hook)) - hookTask := &HookTask{ - HookID: hook.ID, - IsDelivered: true, - Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()), - PayloadVersion: 2, - } - unittest.AssertNotExistsBean(t, hookTask) - _, err := CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, hookTask) - - assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0)) - unittest.AssertNotExistsBean(t, hookTask) -} - -func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup5", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} - require.NoError(t, db.Insert(t.Context(), hook)) - hookTask := &HookTask{ - HookID: hook.ID, - IsDelivered: false, - PayloadVersion: 2, - } - unittest.AssertNotExistsBean(t, hookTask) - _, err := CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, hookTask) - - assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0)) - unittest.AssertExistsAndLoadBean(t, hookTask) -} - -func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - hook := &Webhook{RepoID: 3, URL: "https://www.example.com/cleanup6", ContentType: ContentTypeJSON, Events: `{"push_only":true}`} - require.NoError(t, db.Insert(t.Context(), hook)) - hookTask := &HookTask{ - HookID: hook.ID, - IsDelivered: true, - Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()), - PayloadVersion: 2, - } - unittest.AssertNotExistsBean(t, hookTask) - _, err := CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, hookTask) - - assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0)) - unittest.AssertExistsAndLoadBean(t, hookTask) } diff --git a/modules/actions/github_test.go b/modules/actions/github_test.go index 26d05b8560c..b558bdd765b 100644 --- a/modules/actions/github_test.go +++ b/modules/actions/github_test.go @@ -77,12 +77,6 @@ func TestCanGithubEventMatch(t *testing.T) { webhook_module.HookEventPullRequest, true, }, - { - "pull_request_target cannot match", - GithubEventPullRequest, - webhook_module.HookEventPullRequestComment, - false, - }, // pull_request_review event { "pull_request_review matches", diff --git a/modules/actions/jobparser/jobparser_test.go b/modules/actions/jobparser/jobparser_test.go index 0a80219da6e..64e46b693a1 100644 --- a/modules/actions/jobparser/jobparser_test.go +++ b/modules/actions/jobparser/jobparser_test.go @@ -18,72 +18,21 @@ import ( ) func TestParse(t *testing.T) { - tests := []struct { - name string - options []ParseOption - wantErr bool - }{ - { - name: "multiple_jobs", - options: nil, - wantErr: false, - }, - { - name: "multiple_matrix", - options: nil, - wantErr: false, - }, - { - name: "has_needs", - options: nil, - wantErr: false, - }, - { - name: "has_with", - options: nil, - wantErr: false, - }, - { - name: "has_secrets", - options: nil, - wantErr: false, - }, - { - name: "empty_step", - options: nil, - wantErr: false, - }, - { - name: "job_name_with_matrix", - options: nil, - wantErr: false, - }, - { - name: "prefixed_newline", - options: nil, - wantErr: false, - }, - { - name: "continue_on_error_expr", - options: nil, - wantErr: false, - }, - } - invalidFileTests := []struct { - name string - }{ - {name: "null_job_implicit"}, - {name: "null_job_explicit"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - content := ReadTestdata(t, tt.name+".in.yaml") - want := ReadTestdata(t, tt.name+".out.yaml") - got, err := Parse(content, append([]ParseOption{WithGitContext(&model.GithubContext{})}, tt.options...)...) - if tt.wantErr { - require.Error(t, err) - } + for _, name := range []string{ + "multiple_jobs", + "multiple_matrix", + "has_needs", + "has_with", + "has_secrets", + "empty_step", + "job_name_with_matrix", + "prefixed_newline", + "continue_on_error_expr", + } { + t.Run(name, func(t *testing.T) { + content := ReadTestdata(t, name+".in.yaml") + want := ReadTestdata(t, name+".out.yaml") + got, err := Parse(content, WithGitContext(&model.GithubContext{})) require.NoError(t, err) builder := &strings.Builder{} @@ -102,9 +51,9 @@ func TestParse(t *testing.T) { }) } - for _, tt := range invalidFileTests { - t.Run(tt.name, func(t *testing.T) { - content := ReadTestdata(t, tt.name+".in.yaml") + for _, name := range []string{"null_job_implicit", "null_job_explicit"} { + t.Run(name, func(t *testing.T) { + content := ReadTestdata(t, name+".in.yaml") require.NotPanics(t, func() { _, err := Parse(content) require.Error(t, err) @@ -460,6 +409,8 @@ func TestExpandMatrixWithNeeds(t *testing.T) { {"empty vector", "\n version: ${{ fromJson(needs.setup.outputs.empty) }}\n", `Matrix vector "version" does not contain any values`}, {"empty include", "\n include: ${{ fromJson(needs.setup.outputs.empty) }}\n", "Matrix must define at least one vector"}, {"whole matrix not a mapping", " ${{ fromJson(needs.setup.outputs.empty) }}\n", `Matrix "" is not a map of matrix keys to values`}, + {"unresolved need", "\n v: ${{ fromJson(needs.missing.outputs.v) }}\n", "evaluate matrix"}, + {"invalid strategy field", "\n os: [linux]\nmax-parallel: ${{ fromJSON('bad') }}\n", "evaluate strategy"}, } { t.Run(tt.name+" errors", func(t *testing.T) { _, err := expand(t, tt.matrix) @@ -475,16 +426,6 @@ func TestExpandMatrixWithNeeds(t *testing.T) { assert.Equal(t, 1, got[0].Strategy.JobTotal) }) - t.Run("unresolved need errors", func(t *testing.T) { - _, err := expand(t, "\n v: ${{ fromJson(needs.missing.outputs.v) }}\n") - require.ErrorContains(t, err, "evaluate matrix") - }) - - t.Run("invalid strategy field errors", func(t *testing.T) { - _, err := expand(t, "\n os: [linux]\nmax-parallel: ${{ fromJSON('bad') }}\n") - require.ErrorContains(t, err, "evaluate strategy") - }) - // The combination count comes from a runtime output, so it must be rejected before one Job per // combination is built rather than after. t.Run("too many combinations errors", func(t *testing.T) { diff --git a/modules/actions/jobparser/model_test.go b/modules/actions/jobparser/model_test.go index af708b3cc95..3ed27a4426c 100644 --- a/modules/actions/jobparser/model_test.go +++ b/modules/actions/jobparser/model_test.go @@ -332,29 +332,17 @@ func TestSingleWorkflow_SetJob(t *testing.T) { func TestGetContinueOnError(t *testing.T) { tests := []struct { - name string - yaml string - want bool + name string + continueOnError string + want bool }{ - { - name: "absent", - yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n steps:\n - run: echo hi\n", - want: false, - }, - { - name: "static true", - yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n continue-on-error: true\n steps:\n - run: echo hi\n", - want: true, - }, - { - name: "static false", - yaml: "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n continue-on-error: false\n steps:\n - run: echo hi\n", - want: false, - }, + {name: "absent", want: false}, + {name: "static true", continueOnError: " continue-on-error: true\n", want: true}, + {name: "static false", continueOnError: " continue-on-error: false\n", want: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Parse([]byte(tt.yaml)) + got, err := Parse(fmt.Appendf(nil, "name: test\non: push\njobs:\n job1:\n runs-on: ubuntu-22.04\n%s steps:\n - run: echo hi\n", tt.continueOnError)) require.NoError(t, err) require.Len(t, got, 1) _, job := got[0].Job() diff --git a/modules/actions/workflowpattern/workflow_pattern_test.go b/modules/actions/workflowpattern/workflow_pattern_test.go index 59a38326342..84cc96943f0 100644 --- a/modules/actions/workflowpattern/workflow_pattern_test.go +++ b/modules/actions/workflowpattern/workflow_pattern_test.go @@ -13,404 +13,214 @@ import ( func TestMatchPattern(t *testing.T) { kases := []struct { - inputs []string + inputs [][]string patterns []string skipResult bool filterResult bool }{ { patterns: []string{"*"}, - inputs: []string{"path/with/slash"}, + inputs: [][]string{{"path/with/slash"}}, skipResult: true, filterResult: false, }, { patterns: []string{"path/a", "path/b", "path/c"}, - inputs: []string{"meta", "path/b", "otherfile"}, + inputs: [][]string{{"meta", "path/b", "otherfile"}}, skipResult: false, filterResult: false, }, { patterns: []string{"path/a", "path/b", "path/c"}, - inputs: []string{"path/b"}, + inputs: [][]string{{"path/b"}, {"path/c", "path/b"}, {"path/c", "path/b", "path/a"}}, skipResult: false, filterResult: true, }, { patterns: []string{"path/a", "path/b", "path/c"}, - inputs: []string{"path/c", "path/b"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"path/a", "path/b", "path/c"}, - inputs: []string{"path/c", "path/b", "path/a"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"path/a", "path/b", "path/c"}, - inputs: []string{"path/c", "path/b", "path/d", "path/a"}, + inputs: [][]string{{"path/c", "path/b", "path/d", "path/a"}}, skipResult: false, filterResult: false, }, { patterns: []string{}, - inputs: []string{}, + inputs: [][]string{{}}, skipResult: false, filterResult: false, }, { patterns: []string{"\\!file"}, - inputs: []string{"!file"}, + inputs: [][]string{{"!file"}}, skipResult: false, filterResult: true, }, { patterns: []string{"escape\\\\backslash"}, - inputs: []string{"escape\\backslash"}, + inputs: [][]string{{"escape\\backslash"}}, skipResult: false, filterResult: true, }, { patterns: []string{".yml"}, - inputs: []string{"fyml"}, + inputs: [][]string{{"fyml"}}, skipResult: true, filterResult: false, }, // https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#patterns-to-match-branches-and-tags { patterns: []string{"feature/*"}, - inputs: []string{"feature/my-branch"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"feature/*"}, - inputs: []string{"feature/your-branch"}, + inputs: [][]string{{"feature/my-branch"}, {"feature/your-branch"}}, skipResult: false, filterResult: true, }, { patterns: []string{"feature/**"}, - inputs: []string{"feature/beta-a/my-branch"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"feature/**"}, - inputs: []string{"feature/beta-a/my-branch"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"feature/**"}, - inputs: []string{"feature/mona/the/octocat"}, + inputs: [][]string{{"feature/beta-a/my-branch"}, {"feature/mona/the/octocat"}}, skipResult: false, filterResult: true, }, { patterns: []string{"main", "releases/mona-the-octocat"}, - inputs: []string{"main"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"main", "releases/mona-the-octocat"}, - inputs: []string{"releases/mona-the-octocat"}, + inputs: [][]string{{"main"}, {"releases/mona-the-octocat"}}, skipResult: false, filterResult: true, }, { patterns: []string{"*"}, - inputs: []string{"main"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*"}, - inputs: []string{"releases"}, + inputs: [][]string{{"main"}, {"releases"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**"}, - inputs: []string{"all/the/branches"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**"}, - inputs: []string{"every/tag"}, + inputs: [][]string{{"all/the/branches"}, {"every/tag"}}, skipResult: false, filterResult: true, }, { patterns: []string{"*feature"}, - inputs: []string{"mona-feature"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*feature"}, - inputs: []string{"feature"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*feature"}, - inputs: []string{"ver-10-feature"}, + inputs: [][]string{{"mona-feature"}, {"feature"}, {"ver-10-feature"}}, skipResult: false, filterResult: true, }, { patterns: []string{"v2*"}, - inputs: []string{"v2"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"v2*"}, - inputs: []string{"v2.0"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"v2*"}, - inputs: []string{"v2.9"}, + inputs: [][]string{{"v2"}, {"v2.0"}, {"v2.9"}}, skipResult: false, filterResult: true, }, { patterns: []string{"v[12].[0-9]+.[0-9]+"}, - inputs: []string{"v1.10.1"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"v[12].[0-9]+.[0-9]+"}, - inputs: []string{"v2.0.0"}, + inputs: [][]string{{"v1.10.1"}, {"v2.0.0"}}, skipResult: false, filterResult: true, }, // https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#patterns-to-match-file-paths { patterns: []string{"*"}, - inputs: []string{"README.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*"}, - inputs: []string{"server.rb"}, + inputs: [][]string{{"README.md"}, {"server.rb"}}, skipResult: false, filterResult: true, }, { patterns: []string{"*.jsx?"}, - inputs: []string{"page.js"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*.jsx?"}, - inputs: []string{"page.jsx"}, + inputs: [][]string{{"page.js"}, {"page.jsx"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**"}, - inputs: []string{"all/the/files.md"}, + inputs: [][]string{{"all/the/files.md"}}, skipResult: false, filterResult: true, }, { patterns: []string{"*.js"}, - inputs: []string{"app.js"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*.js"}, - inputs: []string{"index.js"}, + inputs: [][]string{{"app.js"}, {"index.js"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**.js"}, - inputs: []string{"index.js"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**.js"}, - inputs: []string{"js/index.js"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**.js"}, - inputs: []string{"src/js/app.js"}, + inputs: [][]string{{"index.js"}, {"js/index.js"}, {"src/js/app.js"}}, skipResult: false, filterResult: true, }, { patterns: []string{"docs/*"}, - inputs: []string{"docs/README.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"docs/*"}, - inputs: []string{"docs/file.txt"}, + inputs: [][]string{{"docs/README.md"}, {"docs/file.txt"}}, skipResult: false, filterResult: true, }, { patterns: []string{"docs/**"}, - inputs: []string{"docs/README.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"docs/**"}, - inputs: []string{"docs/mona/octocat.txt"}, + inputs: [][]string{{"docs/README.md"}, {"docs/mona/octocat.txt"}}, skipResult: false, filterResult: true, }, { patterns: []string{"docs/**/*.md"}, - inputs: []string{"docs/README.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"docs/**/*.md"}, - inputs: []string{"docs/mona/hello-world.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"docs/**/*.md"}, - inputs: []string{"docs/a/markdown/file.md"}, + inputs: [][]string{{"docs/README.md"}, {"docs/mona/hello-world.md"}, {"docs/a/markdown/file.md"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**/docs/**"}, - inputs: []string{"docs/hello.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/docs/**"}, - inputs: []string{"dir/docs/my-file.txt"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/docs/**"}, - inputs: []string{"space/docs/plan/space.doc"}, + inputs: [][]string{{"docs/hello.md"}, {"dir/docs/my-file.txt"}, {"space/docs/plan/space.doc"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**/README.md"}, - inputs: []string{"README.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/README.md"}, - inputs: []string{"js/README.md"}, + inputs: [][]string{{"README.md"}, {"js/README.md"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**/*src/**"}, - inputs: []string{"a/src/app.js"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/*src/**"}, - inputs: []string{"my-src/code/js/app.js"}, + inputs: [][]string{{"a/src/app.js"}, {"my-src/code/js/app.js"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**/*-post.md"}, - inputs: []string{"my-post.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/*-post.md"}, - inputs: []string{"path/their-post.md"}, + inputs: [][]string{{"my-post.md"}, {"path/their-post.md"}}, skipResult: false, filterResult: true, }, { patterns: []string{"**/migrate-*.sql"}, - inputs: []string{"migrate-10909.sql"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/migrate-*.sql"}, - inputs: []string{"db/migrate-v1.0.sql"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"**/migrate-*.sql"}, - inputs: []string{"db/sept/migrate-v1.sql"}, + inputs: [][]string{{"migrate-10909.sql"}, {"db/migrate-v1.0.sql"}, {"db/sept/migrate-v1.sql"}}, skipResult: false, filterResult: true, }, { patterns: []string{"*.md", "!README.md"}, - inputs: []string{"hello.md"}, + inputs: [][]string{{"hello.md"}}, skipResult: false, filterResult: true, }, { patterns: []string{"*.md", "!README.md"}, - inputs: []string{"README.md"}, - skipResult: true, - filterResult: true, - }, - { - patterns: []string{"*.md", "!README.md"}, - inputs: []string{"docs/hello.md"}, + inputs: [][]string{{"README.md"}, {"docs/hello.md"}}, skipResult: true, filterResult: true, }, { patterns: []string{"*.md", "!README.md", "README*"}, - inputs: []string{"hello.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*.md", "!README.md", "README*"}, - inputs: []string{"README.md"}, - skipResult: false, - filterResult: true, - }, - { - patterns: []string{"*.md", "!README.md", "README*"}, - inputs: []string{"README.doc"}, + inputs: [][]string{{"hello.md"}, {"README.md"}, {"README.doc"}}, skipResult: false, filterResult: true, }, } for _, kase := range kases { - msg := fmt.Sprintf("patterns=%s, input=%s", strings.Join(kase.patterns, ","), strings.Join(kase.inputs, ",")) - patterns, err := CompilePatterns(kase.patterns...) - assert.NoError(t, err, "compile error: %s", msg) - assert.Equal(t, kase.skipResult, Skip(patterns, kase.inputs), "unexpected skip result: %s", msg) - assert.Equal(t, kase.filterResult, Filter(patterns, kase.inputs), "unexpected filter result: %s", msg) + for _, inputs := range kase.inputs { + msg := fmt.Sprintf("patterns=%s, input=%s", strings.Join(kase.patterns, ","), strings.Join(inputs, ",")) + patterns, err := CompilePatterns(kase.patterns...) + assert.NoError(t, err, "compile error: %s", msg) + assert.Equal(t, kase.skipResult, Skip(patterns, inputs), "unexpected skip result: %s", msg) + assert.Equal(t, kase.filterResult, Filter(patterns, inputs), "unexpected filter result: %s", msg) + } } } diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 8672d3205ac..a2536f9affd 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -281,6 +281,24 @@ func TestDetectMatched(t *testing.T) { } func TestMatchIssuesEvent(t *testing.T) { + labelChangesPayload := func() *api.IssuePayload { + return &api.IssuePayload{ + Action: api.HookIssueLabelUpdated, + Issue: &api.Issue{ + Labels: []*api.Label{ + {ID: 789, Name: "new-label"}, + }, + }, + Changes: &api.ChangesPayload{ + AddedLabels: []*api.Label{ + {ID: 789, Name: "new-label"}, + }, + RemovedLabels: []*api.Label{ + {ID: 123, Name: "deleted-label"}, + }, + }, + } + } testCases := []struct { desc string payload *api.IssuePayload @@ -358,67 +376,22 @@ func TestMatchIssuesEvent(t *testing.T) { eventType: "unlabeled", }, { - desc: "Both adding and removing labels should trigger labeled event", - payload: &api.IssuePayload{ - Action: api.HookIssueLabelUpdated, - Issue: &api.Issue{ - Labels: []*api.Label{ - {ID: 789, Name: "new-label"}, - }, - }, - Changes: &api.ChangesPayload{ - AddedLabels: []*api.Label{ - {ID: 789, Name: "new-label"}, - }, - RemovedLabels: []*api.Label{ - {ID: 123, Name: "deleted-label"}, - }, - }, - }, + desc: "Both adding and removing labels should trigger labeled event", + payload: labelChangesPayload(), yamlOn: "on:\n issues:\n types: [labeled]", expected: true, eventType: "labeled", }, { - desc: "Both adding and removing labels should trigger unlabeled event", - payload: &api.IssuePayload{ - Action: api.HookIssueLabelUpdated, - Issue: &api.Issue{ - Labels: []*api.Label{ - {ID: 789, Name: "new-label"}, - }, - }, - Changes: &api.ChangesPayload{ - AddedLabels: []*api.Label{ - {ID: 789, Name: "new-label"}, - }, - RemovedLabels: []*api.Label{ - {ID: 123, Name: "deleted-label"}, - }, - }, - }, + desc: "Both adding and removing labels should trigger unlabeled event", + payload: labelChangesPayload(), yamlOn: "on:\n issues:\n types: [unlabeled]", expected: true, eventType: "unlabeled", }, { - desc: "Both adding and removing labels should trigger both events", - payload: &api.IssuePayload{ - Action: api.HookIssueLabelUpdated, - Issue: &api.Issue{ - Labels: []*api.Label{ - {ID: 789, Name: "new-label"}, - }, - }, - Changes: &api.ChangesPayload{ - AddedLabels: []*api.Label{ - {ID: 789, Name: "new-label"}, - }, - RemovedLabels: []*api.Label{ - {ID: 123, Name: "deleted-label"}, - }, - }, - }, + desc: "Both adding and removing labels should trigger both events", + payload: labelChangesPayload(), yamlOn: "on:\n issues:\n types: [labeled, unlabeled]", expected: true, eventType: "multiple", diff --git a/modules/assetfs/layered_test.go b/modules/assetfs/layered_test.go index b549815ea5c..c311c6116d7 100644 --- a/modules/assetfs/layered_test.go +++ b/modules/assetfs/layered_test.go @@ -74,34 +74,22 @@ func TestLayered(t *testing.T) { assertRead("", fs.ErrNotExist, "no-such") - files, err := assets.ListFiles(".", true) - assert.NoError(t, err) - assert.Equal(t, []string{"f1", "f2", "fa"}, files) - - files, err = assets.ListFiles(".", false) - assert.NoError(t, err) - assert.Equal(t, []string{"d1", "d2", "da"}, files) - - files, err = assets.ListFiles(".") - assert.NoError(t, err) - assert.Equal(t, []string{"d1", "d2", "da", "f1", "f2", "fa"}, files) - - files, err = assets.ListAllFiles(".", true) - assert.NoError(t, err) - assert.Equal(t, []string{"d1/f", "d2/f", "da/f", "f1", "f2", "fa"}, files) - - files, err = assets.ListAllFiles(".", false) - assert.NoError(t, err) - assert.Equal(t, []string{"d1", "d2", "da", "da/sub1", "da/sub2"}, files) - - files, err = assets.ListAllFiles(".") - assert.NoError(t, err) - assert.Equal(t, []string{ + assertList := func(listFiles func(string, ...bool) ([]string, error), expected []string, fileMode ...bool) { + files, err := listFiles(".", fileMode...) + assert.NoError(t, err) + assert.Equal(t, expected, files) + } + assertList(assets.ListFiles, []string{"f1", "f2", "fa"}, true) + assertList(assets.ListFiles, []string{"d1", "d2", "da"}, false) + assertList(assets.ListFiles, []string{"d1", "d2", "da", "f1", "f2", "fa"}) + assertList(assets.ListAllFiles, []string{"d1/f", "d2/f", "da/f", "f1", "f2", "fa"}, true) + assertList(assets.ListAllFiles, []string{"d1", "d2", "da", "da/sub1", "da/sub2"}, false) + assertList(assets.ListAllFiles, []string{ "d1", "d1/f", "d2", "d2/f", "da", "da/f", "da/sub1", "da/sub2", "f1", "f2", "fa", - }, files) + }) assert.Empty(t, assets.GetFileLayerName("no-such")) assert.Equal(t, "l1", assets.GetFileLayerName("f1")) diff --git a/modules/auth/password/password_test.go b/modules/auth/password/password_test.go index 0fea593c859..36c02e7781f 100644 --- a/modules/auth/password/password_test.go +++ b/modules/auth/password/password_test.go @@ -48,7 +48,7 @@ func TestComplexity_Generate(t *testing.T) { const maxCount = 50 const pwdLen = 50 - test := func(t *testing.T, modes []string) { + for _, modes := range [][]string{{"lower"}, {"upper"}, {"lower", "upper", "spec"}, {"off"}, {""}} { testComplextity(modes) for range maxCount { pwd, err := Generate(pwdLen) @@ -58,12 +58,6 @@ func TestComplexity_Generate(t *testing.T) { } } - test(t, []string{"lower"}) - test(t, []string{"upper"}) - test(t, []string{"lower", "upper", "spec"}) - test(t, []string{"off"}) - test(t, []string{""}) - // Remove settings for other tests testComplextity([]string{"off"}) } diff --git a/modules/auth/password/pwn/pwn_test.go b/modules/auth/password/pwn/pwn_test.go index 3462a397a58..6600d10813c 100644 --- a/modules/auth/password/pwn/pwn_test.go +++ b/modules/auth/password/pwn/pwn_test.go @@ -47,25 +47,21 @@ func TestPassword(t *testing.T) { assert.ErrorContains(t, err, "password cannot be empty") assert.EqualValues(t, -1, count) - count, err = client.CheckPassword(ctx, "pwned", false) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - count, err = client.CheckPassword(ctx, "notpwned", false) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) - - count, err = client.CheckPassword(ctx, "paddedpwned", true) - assert.NoError(t, err) - assert.EqualValues(t, 1, count) - - count, err = client.CheckPassword(ctx, "paddednotpwned", true) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) - - count, err = client.CheckPassword(ctx, "paddednotpwnedzero", true) - assert.NoError(t, err) - assert.EqualValues(t, 0, count) + for _, test := range []struct { + password string + padding bool + count int + }{ + {"pwned", false, 1}, + {"notpwned", false, 0}, + {"paddedpwned", true, 1}, + {"paddednotpwned", true, 0}, + {"paddednotpwnedzero", true, 0}, + } { + count, err = client.CheckPassword(ctx, test.password, test.padding) + assert.NoError(t, err) + assert.EqualValues(t, test.count, count) + } count, err = client.CheckPassword(ctx, "ratelimited", false) assert.Error(t, err) diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index f2e2879e0ab..6231f075d8f 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -16,26 +16,19 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_ProcessAvatarPNG(t *testing.T) { - setting.Avatar.MaxWidth = 4096 - setting.Avatar.MaxHeight = 4096 +func Test_ProcessAvatarFormats(t *testing.T) { + for _, format := range []string{"png", "jpeg"} { + t.Run(format, func(t *testing.T) { + setting.Avatar.MaxWidth = 4096 + setting.Avatar.MaxHeight = 4096 - data, err := os.ReadFile("testdata/avatar.png") - assert.NoError(t, err) + data, err := os.ReadFile("testdata/avatar." + format) + assert.NoError(t, err) - _, err = processAvatarImage(data, 262144) - assert.NoError(t, err) -} - -func Test_ProcessAvatarJPEG(t *testing.T) { - setting.Avatar.MaxWidth = 4096 - setting.Avatar.MaxHeight = 4096 - - data, err := os.ReadFile("testdata/avatar.jpeg") - assert.NoError(t, err) - - _, err = processAvatarImage(data, 262144) - assert.NoError(t, err) + _, err = processAvatarImage(data, 262144) + assert.NoError(t, err) + }) + } } func Test_ProcessAvatarInvalidData(t *testing.T) { diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go index 0731b0c0e15..ec31c4c80e8 100644 --- a/modules/charset/charset_test.go +++ b/modules/charset/charset_test.go @@ -87,90 +87,64 @@ func TestToUTF8(t *testing.T) { } func TestToUTF8WithFallback(t *testing.T) { - // "ABC" - res := ToUTF8WithFallback([]byte{0x41, 0x42, 0x43}, ConvertOpts{}) - assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) + for _, converter := range []struct { + name string + convert func([]byte) []byte + }{ + {"fallback", func(content []byte) []byte { return ToUTF8WithFallback(content, ConvertOpts{}) }}, + {"drop errors", ToUTF8DropErrors}, + } { + t.Run(converter.name, func(t *testing.T) { + // "ABC" + res := converter.convert([]byte{0x41, 0x42, 0x43}) + assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) - // "áéíóú" - res = ToUTF8WithFallback([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) - assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + // "áéíóú" + res = converter.convert([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) - // UTF8 BOM + "áéíóú" - res = ToUTF8WithFallback([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, ConvertOpts{}) - assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) + // UTF8 BOM + "áéíóú" + res = converter.convert([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) + assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) - // "Hola, así cómo ños" - res = ToUTF8WithFallback([]byte{ - 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, - 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, - }, ConvertOpts{}) - assert.Equal(t, []byte{ - 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, - 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73, - }, res) + // "Hola, así cómo ños" + res = converter.convert([]byte{ + 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, + 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73, + }) + if converter.name == "fallback" { + assert.Equal(t, []byte{ + 0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, + 0xC3, 0xB3, 0x6D, 0x6F, 0x20, 0xC3, 0xB1, 0x6F, 0x73, + }, res) + } else { + assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8]) + assert.Equal(t, []byte{0x73}, res[len(res)-1:]) + } - // "Hola, así cómo " - minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} + // "Hola, así cómo " + minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} - res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}, ConvertOpts{}) - // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those - assert.Equal(t, minmatch, res[0:len(minmatch)]) + res = converter.convert([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}) + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + assert.Equal(t, minmatch, res[0:len(minmatch)]) - res = ToUTF8WithFallback([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}, ConvertOpts{}) - // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those - assert.Equal(t, minmatch, res[0:len(minmatch)]) + res = converter.convert([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}) + // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those + assert.Equal(t, minmatch, res[0:len(minmatch)]) - // Japanese (Shift-JIS) - // "日属秘ぞしちゅ。" - res = ToUTF8WithFallback([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}, ConvertOpts{}) - assert.Equal(t, []byte{ - 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, - 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82, - }, res) + // Japanese (Shift-JIS) + // "日属秘ぞしちゅ。" + res = converter.convert([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}) + assert.Equal(t, []byte{ + 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, + 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82, + }, res) - res = ToUTF8WithFallback([]byte{0x00, 0x00, 0x00, 0x00}, ConvertOpts{}) - assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) -} - -func TestToUTF8DropErrors(t *testing.T) { - // "ABC" - res := ToUTF8DropErrors([]byte{0x41, 0x42, 0x43}) - assert.Equal(t, []byte{0x41, 0x42, 0x43}, res) - - // "áéíóú" - res = ToUTF8DropErrors([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) - assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) - - // UTF8 BOM + "áéíóú" - res = ToUTF8DropErrors([]byte{0xef, 0xbb, 0xbf, 0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}) - assert.Equal(t, []byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}, res) - - // "Hola, así cómo ños" - res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0xF1, 0x6F, 0x73}) - assert.Equal(t, []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73}, res[:8]) - assert.Equal(t, []byte{0x73}, res[len(res)-1:]) - - // "Hola, así cómo " - minmatch := []byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xC3, 0xAD, 0x20, 0x63, 0xC3, 0xB3, 0x6D, 0x6F, 0x20} - - res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x07, 0xA4, 0x6F, 0x73}) - // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those - assert.Equal(t, minmatch, res[0:len(minmatch)]) - - res = ToUTF8DropErrors([]byte{0x48, 0x6F, 0x6C, 0x61, 0x2C, 0x20, 0x61, 0x73, 0xED, 0x20, 0x63, 0xF3, 0x6D, 0x6F, 0x20, 0x81, 0xA4, 0x6F, 0x73}) - // Do not fail for differences in invalid cases, as the library might change the conversion criteria for those - assert.Equal(t, minmatch, res[0:len(minmatch)]) - - // Japanese (Shift-JIS) - // "日属秘ぞしちゅ。" - res = ToUTF8DropErrors([]byte{0x93, 0xFA, 0x91, 0xAE, 0x94, 0xE9, 0x82, 0xBC, 0x82, 0xB5, 0x82, 0xBF, 0x82, 0xE3, 0x81, 0x42}) - assert.Equal(t, []byte{ - 0xE6, 0x97, 0xA5, 0xE5, 0xB1, 0x9E, 0xE7, 0xA7, 0x98, 0xE3, - 0x81, 0x9E, 0xE3, 0x81, 0x97, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x85, 0xE3, 0x80, 0x82, - }, res) - - res = ToUTF8DropErrors([]byte{0x00, 0x00, 0x00, 0x00}) - assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) + res = converter.convert([]byte{0x00, 0x00, 0x00, 0x00}) + assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, res) + }) + } assert.Equal(t, "caf ", string(ToUTF8DropErrors([]byte("caf\xe9")))) } diff --git a/modules/commitstatus/commit_status_test.go b/modules/commitstatus/commit_status_test.go index dc8f9ce1a5e..a5fffd914f3 100644 --- a/modules/commitstatus/commit_status_test.go +++ b/modules/commitstatus/commit_status_test.go @@ -173,11 +173,6 @@ func TestCombine(t *testing.T) { states: CommitStatusStates{CommitStatusSuccess, CommitStatusError, CommitStatusFailure, CommitStatusWarning}, expected: CommitStatusFailure, }, - { - name: "mixed states", - states: CommitStatusStates{CommitStatusPending, CommitStatusSuccess, CommitStatusError, CommitStatusWarning}, - expected: CommitStatusFailure, - }, { name: "mixed states with all success", states: CommitStatusStates{CommitStatusSuccess, CommitStatusSuccess, CommitStatusPending, CommitStatusWarning}, diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go index d217d57e537..899156ea2a0 100644 --- a/modules/csv/csv_test.go +++ b/modules/csv/csv_test.go @@ -307,6 +307,7 @@ func TestGuessDelimiter(t *testing.T) { cases := []struct { csv string expectedDelimiter rune + checkQuotes bool }{ // case 0 - single cell, comma delmited { @@ -373,12 +374,14 @@ quoted, quoted, text," d`, expectedDelimiter: '\t', + checkQuotes: true, }, // case 10 - semicolon delmited with quotes and semicolon in value { csv: `col1;col2 "this has a literal "" in the text";"and an ; in the text"`, expectedDelimiter: ';', + checkQuotes: true, }, // case 11 - pipe delimited with quotes { @@ -389,6 +392,7 @@ World"|123 | f" | 4.56 | 789`, expectedDelimiter: '|', + checkQuotes: true, }, // case 12 - a tab delimited 6 column CSV, but the values are not quoted and have lots of commas. // In the previous bestScore algorithm, this would have picked comma as the delimiter, but now it should guess tab @@ -455,6 +459,10 @@ jkl`, for n, c := range cases { delimiter := guessDelimiter([]byte(decodeSlashes(t, c.csv))) assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) + if c.checkQuotes { + delimiter = guessFromBeforeAfterQuotes([]byte(decodeSlashes(t, c.csv))) + assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) + } } } @@ -463,39 +471,6 @@ func TestGuessFromBeforeAfterQuotes(t *testing.T) { csv string expectedDelimiter rune }{ - // case 0 - tab delimited with new lines in values, commas in values - { - csv: `1 "some,""more -"" - quoted, -text," a -2 "some, -quoted,\t - text," b -3 "some, -quoted, - text" c -4 "some, -quoted, -text," d`, - expectedDelimiter: '\t', - }, - // case 1 - semicolon delmited with quotes and semicolon in value - { - csv: `col1;col2 -"this has a literal "" in the text";"and an ; in the text"`, - expectedDelimiter: ';', - }, - // case 2 - pipe delimited with quotes - { - csv: `Col1 | Col2 | Col3 -abc | "Hello -World"|123 -"de -| -f" | 4.56 | 789`, - expectedDelimiter: '|', - }, // case 3 - a complicated quoted CSV that is semicolon delmiited { csv: `he; she diff --git a/modules/fileicon/render_test.go b/modules/fileicon/render_test.go index ac03fc49286..c1c5edd6eb7 100644 --- a/modules/fileicon/render_test.go +++ b/modules/fileicon/render_test.go @@ -16,60 +16,36 @@ import ( func TestRenderEntryIconHTML_WithDifferentThemes(t *testing.T) { // Test that folder icons use the folder theme - t.Run("FolderUsesBasicTheme", func(t *testing.T) { - defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")() - defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")() - - folderEntry := &fileicon.EntryInfo{ - BaseName: "testfolder", - EntryMode: git.EntryModeTree, - } - - html := fileicon.RenderEntryIconHTML(nil, folderEntry) + for _, testCase := range []struct { + name, folderTheme, expected string + entry fileicon.EntryInfo + }{ // Basic theme renders octicon classes - assert.Contains(t, string(html), "octicon-file-directory-fill") - }) - - t.Run("FileUsesMaterialTheme", func(t *testing.T) { - defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")() - defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")() - - fileEntry := &fileicon.EntryInfo{ - BaseName: "test.js", - EntryMode: git.EntryModeBlob, - } - - html := fileicon.RenderEntryIconHTML(nil, fileEntry) + { + "FolderUsesBasicTheme", "basic", "octicon-file-directory-fill", + fileicon.EntryInfo{BaseName: "testfolder", EntryMode: git.EntryModeTree}, + }, // Material theme for files renders material icons - assert.Contains(t, string(html), "svg-mfi-") - }) - - t.Run("SymlinkToFolderUsesBasicTheme", func(t *testing.T) { - defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")() - defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")() - - symlinkEntry := &fileicon.EntryInfo{ - BaseName: "link", - EntryMode: git.EntryModeSymlink, - SymlinkToMode: git.EntryModeTree, - } - - html := fileicon.RenderEntryIconHTML(nil, symlinkEntry) + { + "FileUsesMaterialTheme", "basic", "svg-mfi-", + fileicon.EntryInfo{BaseName: "test.js", EntryMode: git.EntryModeBlob}, + }, // Symlinks to folders should use folder theme - assert.Contains(t, string(html), "octicon-file-directory-symlink") - }) - - t.Run("BothMaterialTheme", func(t *testing.T) { - defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")() - defer test.MockVariableValue(&setting.UI.FolderIconTheme, "material")() - - folderEntry := &fileicon.EntryInfo{ - BaseName: "testfolder", - EntryMode: git.EntryModeTree, - } - - html := fileicon.RenderEntryIconHTML(nil, folderEntry) + { + "SymlinkToFolderUsesBasicTheme", "basic", "octicon-file-directory-symlink", + fileicon.EntryInfo{BaseName: "link", EntryMode: git.EntryModeSymlink, SymlinkToMode: git.EntryModeTree}, + }, // Material theme for folders renders material folder icons - assert.Contains(t, string(html), "svg-mfi-") - }) + { + "BothMaterialTheme", "material", "svg-mfi-", + fileicon.EntryInfo{BaseName: "testfolder", EntryMode: git.EntryModeTree}, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")() + defer test.MockVariableValue(&setting.UI.FolderIconTheme, testCase.folderTheme)() + + assert.Contains(t, string(fileicon.RenderEntryIconHTML(nil, &testCase.entry)), testCase.expected) + }) + } } diff --git a/modules/git/attribute/batch_test.go b/modules/git/attribute/batch_test.go index 8336970e344..89d1525bf87 100644 --- a/modules/git/attribute/batch_test.go +++ b/modules/git/attribute/batch_test.go @@ -23,50 +23,31 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00" - n, err := wr.Write([]byte(testStr)) - - assert.Len(t, testStr, n) - assert.NoError(t, err) - select { - case attr := <-wr.ReadAttribute(): - assert.Equal(t, ".gitignore\"\n", attr.Filename) - assert.Equal(t, LinguistVendored, attr.Attribute) - assert.Equal(t, "unspecified", attr.Value) - case <-time.After(100 * time.Millisecond): - assert.FailNow(t, "took too long to read an attribute from the list") - } - // Write a second attribute again - n, err = wr.Write([]byte(testStr)) - - assert.Len(t, testStr, n) - assert.NoError(t, err) - - select { - case attr := <-wr.ReadAttribute(): - assert.Equal(t, ".gitignore\"\n", attr.Filename) - assert.Equal(t, LinguistVendored, attr.Attribute) - assert.Equal(t, "unspecified", attr.Value) - case <-time.After(100 * time.Millisecond): - assert.FailNow(t, "took too long to read an attribute from the list") + for range 2 { + n, err := wr.Write([]byte(testStr)) + assert.Len(t, testStr, n) + assert.NoError(t, err) + select { + case attr := <-wr.ReadAttribute(): + assert.Equal(t, ".gitignore\"\n", attr.Filename) + assert.Equal(t, LinguistVendored, attr.Attribute) + assert.Equal(t, "unspecified", attr.Value) + case <-time.After(100 * time.Millisecond): + assert.FailNow(t, "took too long to read an attribute from the list") + } } // Write a partial attribute - _, err = wr.Write([]byte("incomplete-file")) + _, err := wr.Write([]byte("incomplete-file")) assert.NoError(t, err) - _, err = wr.Write([]byte("name\x00")) - assert.NoError(t, err) - - select { - case <-wr.ReadAttribute(): - assert.FailNow(t, "There should not be an attribute ready to read") - case <-time.After(100 * time.Millisecond): - } - _, err = wr.Write([]byte("attribute\x00")) - assert.NoError(t, err) - select { - case <-wr.ReadAttribute(): - assert.FailNow(t, "There should not be an attribute ready to read") - case <-time.After(100 * time.Millisecond): + for _, part := range []string{"name\x00", "attribute\x00"} { + _, err = wr.Write([]byte(part)) + assert.NoError(t, err) + select { + case <-wr.ReadAttribute(): + assert.FailNow(t, "There should not be an attribute ready to read") + case <-time.After(100 * time.Millisecond): + } } _, err = wr.Write([]byte("value\x00")) @@ -79,27 +60,15 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { _, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00")) assert.NoError(t, err) - attr = <-wr.ReadAttribute() - assert.NoError(t, err) - assert.Equal(t, attributeTriple{ - Filename: "shouldbe.vendor", - Attribute: LinguistVendored, - Value: "set", - }, attr) - attr = <-wr.ReadAttribute() - assert.NoError(t, err) - assert.Equal(t, attributeTriple{ - Filename: "shouldbe.vendor", - Attribute: LinguistGenerated, - Value: "unspecified", - }, attr) - attr = <-wr.ReadAttribute() - assert.NoError(t, err) - assert.Equal(t, attributeTriple{ - Filename: "shouldbe.vendor", - Attribute: LinguistLanguage, - Value: "unspecified", - }, attr) + for _, expected := range []attributeTriple{ + {"shouldbe.vendor", LinguistVendored, "set"}, + {"shouldbe.vendor", LinguistGenerated, "unspecified"}, + {"shouldbe.vendor", LinguistLanguage, "unspecified"}, + } { + attr = <-wr.ReadAttribute() + assert.NoError(t, err) + assert.Equal(t, expected, attr) + } } func expectedAttrs() *Attributes { diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go deleted file mode 100644 index 5b6004cff20..00000000000 --- a/modules/git/blame_sha256_test.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "testing" - - "gitea.dev/modules/setting" - - "github.com/stretchr/testify/assert" -) - -func TestReadingBlameOutputSha256(t *testing.T) { - setting.AppDataPath = t.TempDir() - ctx := t.Context() - - t.Run("Without .git-blame-ignore-revs", func(t *testing.T) { - storage := mockRepository("repo5_pulls_sha256") - repo, err := OpenRepository(ctx, storage) - assert.NoError(t, err) - defer repo.Close() - - commit, err := repo.GetCommit(t.Context(), "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345") - assert.NoError(t, err) - - parts := []*BlamePart{ - { - Sha: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca", - Lines: []string{ - "# test_repo", - "Test repository for testing migration from github to gitea", - }, - }, - { - Sha: "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345", - Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"}, - PreviousSha: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca", - PreviousPath: "README.md", - }, - } - - for _, bypass := range []bool{false, true} { - blameReader, err := CreateBlameReader(ctx, Sha256ObjectFormat, storage, repo, commit, "README.md", bypass) - assert.NoError(t, err) - assert.NotNil(t, blameReader) - defer blameReader.Close() - - assert.False(t, blameReader.UsesIgnoreRevs()) - - for _, part := range parts { - actualPart, err := blameReader.NextPart() - assert.NoError(t, err) - assert.Equal(t, part, actualPart) - } - - // make sure all parts have been read - actualPart, err := blameReader.NextPart() - assert.Nil(t, actualPart) - assert.NoError(t, err) - } - }) - - t.Run("With .git-blame-ignore-revs", func(t *testing.T) { - storage := mockRepository("repo6_blame_sha256") - repo, err := OpenRepository(ctx, storage) - assert.NoError(t, err) - defer repo.Close() - - full := []*BlamePart{ - { - Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc", - Lines: []string{"line", "line"}, - }, - { - Sha: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe", - Lines: []string{"changed line"}, - PreviousSha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc", - PreviousPath: "blame.txt", - }, - { - Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc", - Lines: []string{"line", "line", ""}, - }, - } - - cases := []struct { - CommitID string - UsesIgnoreRevs bool - Bypass bool - Parts []*BlamePart - }{ - { - CommitID: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3", - UsesIgnoreRevs: true, - Bypass: false, - Parts: []*BlamePart{ - { - Sha: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc", - Lines: []string{"line", "line", "changed line", "line", "line", ""}, - }, - }, - }, - { - CommitID: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3", - UsesIgnoreRevs: false, - Bypass: true, - Parts: full, - }, - { - CommitID: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe", - UsesIgnoreRevs: false, - Bypass: false, - Parts: full, - }, - { - CommitID: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe", - UsesIgnoreRevs: false, - Bypass: false, - Parts: full, - }, - } - - objectFormat, err := repo.GetObjectFormat(t.Context()) - assert.NoError(t, err) - for _, c := range cases { - commit, err := repo.GetCommit(t.Context(), c.CommitID) - assert.NoError(t, err) - blameReader, err := CreateBlameReader(ctx, objectFormat, storage, repo, commit, "blame.txt", c.Bypass) - assert.NoError(t, err) - assert.NotNil(t, blameReader) - defer blameReader.Close() - - assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs()) - - for _, part := range c.Parts { - actualPart, err := blameReader.NextPart() - assert.NoError(t, err) - assert.Equal(t, part, actualPart) - } - - // make sure all parts have been read - actualPart, err := blameReader.NextPart() - assert.Nil(t, actualPart) - assert.NoError(t, err) - } - }) -} diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 7cf8d92e919..83d1f07b66f 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -24,137 +24,170 @@ func TestLineBlameCommitID(t *testing.T) { } func TestReadingBlameOutput(t *testing.T) { - setting.AppDataPath = t.TempDir() - ctx := t.Context() + for _, scenario := range []struct { + name string + repositorySuffix string + objectFormat ObjectFormat + readmeCommit string + readmePrevious string + blameCommit string + blameChanged string + blameOriginal string + }{ + { + name: "sha1", + objectFormat: Sha1ObjectFormat, + readmeCommit: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + readmePrevious: "72866af952e98d02a73003501836074b286a78f6", + blameCommit: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", + blameChanged: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + blameOriginal: "af7486bd54cfc39eea97207ca666aa69c9d6df93", + }, + { + name: "sha256", + repositorySuffix: "_sha256", + objectFormat: Sha256ObjectFormat, + readmeCommit: "0b69b7bb649b5d46e14cabb6468685e5dd721290acc7ffe604d37cde57927345", + readmePrevious: "1e35a51dc00fd7de730344c07061acfe80e8117e075ac979b6a29a3a045190ca", + blameCommit: "e2f5660e15159082902960af0ed74fc144921d2b0c80e069361853b3ece29ba3", + blameChanged: "9347b0198cd1f25017579b79d0938fa89dba34ad2514f0dd92f6bc975ed1a2fe", + blameOriginal: "ab2b57a4fa476fb2edb74dafa577caf918561abbaa8fba0c8dc63c412e17a7cc", + }, + } { + t.Run(scenario.name, func(t *testing.T) { + setting.AppDataPath = t.TempDir() + ctx := t.Context() - t.Run("Without .git-blame-ignore-revs", func(t *testing.T) { - storage := mockRepository("repo5_pulls") - repo, err := OpenRepository(ctx, storage) - assert.NoError(t, err) - defer repo.Close() - commit, err := repo.GetCommit(t.Context(), "f32b0a9dfd09a60f616f29158f772cedd89942d2") - assert.NoError(t, err) - - parts := []*BlamePart{ - { - Sha: "72866af952e98d02a73003501836074b286a78f6", - Lines: []string{ - "# test_repo", - "Test repository for testing migration from github to gitea", - }, - }, - { - Sha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", - Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"}, - PreviousSha: "72866af952e98d02a73003501836074b286a78f6", - PreviousPath: "README.md", - }, - } - - for _, bypass := range []bool{false, true} { - blameReader, err := CreateBlameReader(ctx, Sha1ObjectFormat, storage, repo, commit, "README.md", bypass) - assert.NoError(t, err) - assert.NotNil(t, blameReader) - defer blameReader.Close() - - assert.False(t, blameReader.UsesIgnoreRevs()) - - for _, part := range parts { - actualPart, err := blameReader.NextPart() + t.Run("Without .git-blame-ignore-revs", func(t *testing.T) { + storage := mockRepository("repo5_pulls" + scenario.repositorySuffix) + repo, err := OpenRepository(ctx, storage) + assert.NoError(t, err) + defer repo.Close() + commit, err := repo.GetCommit(t.Context(), scenario.readmeCommit) assert.NoError(t, err) - assert.Equal(t, part, actualPart) - } - // make sure all parts have been read - actualPart, err := blameReader.NextPart() - assert.Nil(t, actualPart) - assert.NoError(t, err) - } - }) - - t.Run("With .git-blame-ignore-revs", func(t *testing.T) { - storage := mockRepository("repo6_blame") - repo, err := OpenRepository(ctx, storage) - assert.NoError(t, err) - defer repo.Close() - - full := []*BlamePart{ - { - Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", - Lines: []string{"line", "line"}, - }, - { - Sha: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", - Lines: []string{"changed line"}, - PreviousSha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", - PreviousPath: "blame.txt", - }, - { - Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", - Lines: []string{"line", "line", ""}, - }, - } - - cases := []struct { - CommitID string - UsesIgnoreRevs bool - Bypass bool - Parts []*BlamePart - }{ - { - CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", - UsesIgnoreRevs: true, - Bypass: false, - Parts: []*BlamePart{ + parts := []*BlamePart{ { - Sha: "af7486bd54cfc39eea97207ca666aa69c9d6df93", - Lines: []string{"line", "line", "changed line", "line", "line", ""}, + Sha: scenario.readmePrevious, + Lines: []string{ + "# test_repo", + "Test repository for testing migration from github to gitea", + }, }, - }, - }, - { - CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", - UsesIgnoreRevs: false, - Bypass: true, - Parts: full, - }, - { - CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", - UsesIgnoreRevs: false, - Bypass: false, - Parts: full, - }, - { - CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", - UsesIgnoreRevs: false, - Bypass: false, - Parts: full, - }, - } + { + Sha: scenario.readmeCommit, + Lines: []string{"", "Do not make any changes to this repo it is used for unit testing"}, + PreviousSha: scenario.readmePrevious, + PreviousPath: "README.md", + }, + } - objectFormat, err := repo.GetObjectFormat(t.Context()) - assert.NoError(t, err) - for _, c := range cases { - commit, err := repo.GetCommit(t.Context(), c.CommitID) - assert.NoError(t, err) + for _, bypass := range []bool{false, true} { + blameReader, err := CreateBlameReader(ctx, scenario.objectFormat, storage, repo, commit, "README.md", bypass) + assert.NoError(t, err) + assert.NotNil(t, blameReader) + defer blameReader.Close() - blameReader, err := CreateBlameReader(ctx, objectFormat, storage, repo, commit, "blame.txt", c.Bypass) - assert.NoError(t, err) - assert.NotNil(t, blameReader) - defer blameReader.Close() + assert.False(t, blameReader.UsesIgnoreRevs()) - assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs()) + for _, part := range parts { + actualPart, err := blameReader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part, actualPart) + } - for _, part := range c.Parts { - actualPart, err := blameReader.NextPart() + // make sure all parts have been read + actualPart, err := blameReader.NextPart() + assert.Nil(t, actualPart) + assert.NoError(t, err) + } + }) + + t.Run("With .git-blame-ignore-revs", func(t *testing.T) { + storage := mockRepository("repo6_blame" + scenario.repositorySuffix) + repo, err := OpenRepository(ctx, storage) assert.NoError(t, err) - assert.Equal(t, part, actualPart) - } + defer repo.Close() - // make sure all parts have been read - actualPart, err := blameReader.NextPart() - assert.Nil(t, actualPart) - assert.NoError(t, err) - } - }) + full := []*BlamePart{ + { + Sha: scenario.blameOriginal, + Lines: []string{"line", "line"}, + }, + { + Sha: scenario.blameChanged, + Lines: []string{"changed line"}, + PreviousSha: scenario.blameOriginal, + PreviousPath: "blame.txt", + }, + { + Sha: scenario.blameOriginal, + Lines: []string{"line", "line", ""}, + }, + } + + cases := []struct { + CommitID string + UsesIgnoreRevs bool + Bypass bool + Parts []*BlamePart + }{ + { + CommitID: scenario.blameCommit, + UsesIgnoreRevs: true, + Bypass: false, + Parts: []*BlamePart{ + { + Sha: scenario.blameOriginal, + Lines: []string{"line", "line", "changed line", "line", "line", ""}, + }, + }, + }, + { + CommitID: scenario.blameCommit, + UsesIgnoreRevs: false, + Bypass: true, + Parts: full, + }, + { + CommitID: scenario.blameChanged, + UsesIgnoreRevs: false, + Bypass: false, + Parts: full, + }, + { + CommitID: scenario.blameChanged, + UsesIgnoreRevs: false, + Bypass: false, + Parts: full, + }, + } + + objectFormat, err := repo.GetObjectFormat(t.Context()) + assert.NoError(t, err) + for _, c := range cases { + commit, err := repo.GetCommit(t.Context(), c.CommitID) + assert.NoError(t, err) + + blameReader, err := CreateBlameReader(ctx, objectFormat, storage, repo, commit, "blame.txt", c.Bypass) + assert.NoError(t, err) + assert.NotNil(t, blameReader) + defer blameReader.Close() + + assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs()) + + for _, part := range c.Parts { + actualPart, err := blameReader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part, actualPart) + } + + // make sure all parts have been read + actualPart, err := blameReader.NextPart() + assert.Nil(t, actualPart) + assert.NoError(t, err) + } + }) + }) + } } diff --git a/modules/git/commit2_test.go b/modules/git/commit2_test.go index eb5bee4808e..8f73303ff9e 100644 --- a/modules/git/commit2_test.go +++ b/modules/git/commit2_test.go @@ -11,60 +11,29 @@ import ( func TestCommitsCount(t *testing.T) { bareRepo1 := mockRepository("repo1_bare") - - commitsCount, err := CommitsCount(t.Context(), bareRepo1, - CommitsCountOptions{ - Revision: []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"}, - }) - - assert.NoError(t, err) - assert.Equal(t, int64(3), commitsCount) -} - -func TestCommitsCountWithSinceUntil(t *testing.T) { - bareRepo1 := mockRepository("repo1_bare") revision := []string{"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"} // The three commits on this revision are dated 2018-04-18, 2017-12-19 and 2017-12-19. - cases := []struct { + for _, tc := range []struct { name string - since string - until string + options CommitsCountOptions expected int64 }{ - {name: "no filter", expected: 3}, - {name: "since keeps newer commits", since: "2018-01-01", expected: 1}, - {name: "until keeps older commits", until: "2018-01-01", expected: 2}, - {name: "since and until bound the range", since: "2017-12-19T22:16:00-08:00", until: "2018-01-01", expected: 1}, - } - for _, tc := range cases { + {"revision", CommitsCountOptions{Revision: revision}, 3}, + {"no filter", CommitsCountOptions{Revision: revision}, 3}, + {"since keeps newer commits", CommitsCountOptions{Revision: revision, Since: "2018-01-01"}, 1}, + {"until keeps older commits", CommitsCountOptions{Revision: revision, Until: "2018-01-01"}, 2}, + {"since and until bound the range", CommitsCountOptions{Revision: revision, Since: "2017-12-19T22:16:00-08:00", Until: "2018-01-01"}, 1}, + {"without base", CommitsCountOptions{Not: "master", Revision: []string{"branch1"}}, 2}, + } { t.Run(tc.name, func(t *testing.T) { - commitsCount, err := CommitsCount(t.Context(), bareRepo1, - CommitsCountOptions{ - Revision: revision, - Since: tc.since, - Until: tc.until, - }) - + commitsCount, err := CommitsCount(t.Context(), bareRepo1, tc.options) assert.NoError(t, err) assert.Equal(t, tc.expected, commitsCount) }) } } -func TestCommitsCountWithoutBase(t *testing.T) { - bareRepo1 := mockRepository("repo1_bare") - - commitsCount, err := CommitsCount(t.Context(), bareRepo1, - CommitsCountOptions{ - Not: "master", - Revision: []string{"branch1"}, - }) - - assert.NoError(t, err) - assert.Equal(t, int64(2), commitsCount) -} - func TestGetLatestCommitTime(t *testing.T) { bareRepo1 := mockRepository("repo1_bare") lct, err := GetLatestCommitTime(t.Context(), bareRepo1) diff --git a/modules/git/commit_file_test.go b/modules/git/commit_file_test.go index d595958c13d..19466e13ead 100644 --- a/modules/git/commit_file_test.go +++ b/modules/git/commit_file_test.go @@ -12,7 +12,7 @@ import ( func TestParseCommitFileStatus(t *testing.T) { type testcase struct { - output string + outputs []string added []string removed []string modified []string @@ -21,7 +21,11 @@ func TestParseCommitFileStatus(t *testing.T) { kases := []testcase{ { // Merge commit - output: "MM\x00options/locale/locale_en-US.ini\x00", + outputs: []string{ + "MM\x00options/locale/locale_en-US.ini\x00", + // git 1.7.2 adds an unnecessary \x00 on merge commit + "\x00MM\x00options/locale/locale_en-US.ini\x00", + }, modified: []string{ "options/locale/locale_en-US.ini", }, @@ -30,7 +34,11 @@ func TestParseCommitFileStatus(t *testing.T) { }, { // Spaces commit - output: "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", + outputs: []string{ + "D\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", + // git 1.7.2 adds an unnecessary \n on normal commit + "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", + }, removed: []string{ "b", "b b/b", @@ -43,7 +51,7 @@ func TestParseCommitFileStatus(t *testing.T) { }, { // larger commit - output: "M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00", + outputs: []string{"M\x00go.mod\x00M\x00go.sum\x00M\x00modules/ssh/ssh.go\x00M\x00vendor/github.com/gliderlabs/ssh/circle.yml\x00M\x00vendor/github.com/gliderlabs/ssh/context.go\x00A\x00vendor/github.com/gliderlabs/ssh/go.mod\x00A\x00vendor/github.com/gliderlabs/ssh/go.sum\x00M\x00vendor/github.com/gliderlabs/ssh/server.go\x00M\x00vendor/github.com/gliderlabs/ssh/session.go\x00M\x00vendor/github.com/gliderlabs/ssh/ssh.go\x00M\x00vendor/golang.org/x/sys/unix/mkerrors.sh\x00M\x00vendor/golang.org/x/sys/unix/syscall_darwin.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/zerrors_linux.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go\x00M\x00vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go\x00M\x00vendor/modules.txt\x00"}, modified: []string{ "go.mod", "go.sum", @@ -81,37 +89,17 @@ func TestParseCommitFileStatus(t *testing.T) { }, removed: []string{}, }, - { - // git 1.7.2 adds an unnecessary \x00 on merge commit - output: "\x00MM\x00options/locale/locale_en-US.ini\x00", - modified: []string{ - "options/locale/locale_en-US.ini", - }, - added: []string{}, - removed: []string{}, - }, - { - // git 1.7.2 adds an unnecessary \n on normal commit - output: "\nD\x00b\x00D\x00b b/b\x00A\x00b b/b b/b b/b\x00A\x00b b/b b/b b/b b/b\x00", - removed: []string{ - "b", - "b b/b", - }, - modified: []string{}, - added: []string{ - "b b/b b/b b/b", - "b b/b b/b b/b b/b", - }, - }, } for _, kase := range kases { - fileStatus := NewCommitFileStatus() - parseCommitFileStatus(fileStatus, strings.NewReader(kase.output)) + for _, output := range kase.outputs { + fileStatus := NewCommitFileStatus() + parseCommitFileStatus(fileStatus, strings.NewReader(output)) - assert.Equal(t, kase.added, fileStatus.Added) - assert.Equal(t, kase.removed, fileStatus.Removed) - assert.Equal(t, kase.modified, fileStatus.Modified) + assert.Equal(t, kase.added, fileStatus.Added) + assert.Equal(t, kase.removed, fileStatus.Removed) + assert.Equal(t, kase.modified, fileStatus.Modified) + } } } @@ -141,35 +129,23 @@ func TestGetCommitFileStatusMerges(t *testing.T) { func TestGetCommitFileStatusMergesSha256(t *testing.T) { bareRepo6Sha256 := mockRepository("repo6_merge_sha256") - commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1") - assert.NoError(t, err) - - expected := CommitFileStatus{ - []string{ - "add_file.txt", + for _, tc := range []struct { + commitID string + expected CommitFileStatus + }{ + { + "d2e5609f630dd8db500f5298d05d16def282412e3e66ed68cc7d0833b29129a1", + CommitFileStatus{[]string{"add_file.txt"}, []string{}, []string{"to_modify.txt"}}, }, - []string{}, - []string{ - "to_modify.txt", + { + "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172", + CommitFileStatus{[]string{}, []string{"to_remove.txt"}, []string{}}, }, + } { + commitFileStatus, err := GetCommitFileStatus(t.Context(), bareRepo6Sha256, tc.commitID) + assert.NoError(t, err) + assert.Equal(t, tc.expected.Added, commitFileStatus.Added) + assert.Equal(t, tc.expected.Removed, commitFileStatus.Removed) + assert.Equal(t, tc.expected.Modified, commitFileStatus.Modified) } - - assert.Equal(t, expected.Added, commitFileStatus.Added) - assert.Equal(t, expected.Removed, commitFileStatus.Removed) - assert.Equal(t, expected.Modified, commitFileStatus.Modified) - - expected = CommitFileStatus{ - []string{}, - []string{ - "to_remove.txt", - }, - []string{}, - } - - commitFileStatus, err = GetCommitFileStatus(t.Context(), bareRepo6Sha256, "da1ded40dc8e5b7c564171f4bf2fc8370487decfb1cb6a99ef28f3ed73d09172") - assert.NoError(t, err) - - assert.Equal(t, expected.Added, commitFileStatus.Added) - assert.Equal(t, expected.Removed, commitFileStatus.Removed) - assert.Equal(t, expected.Modified, commitFileStatus.Modified) } diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go index e725eb2b38e..dcdc1deb629 100644 --- a/modules/git/commit_sha256_test.go +++ b/modules/git/commit_sha256_test.go @@ -5,11 +5,9 @@ package git import ( "path/filepath" - "strings" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestGetFullCommitIDSha256(t *testing.T) { @@ -26,74 +24,6 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) { } } -func TestCommitFromReaderSha256(t *testing.T) { - commitString := `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e -parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 -author Adam Majer 1698676906 +0100 -committer Adam Majer 1698676906 +0100 -gpgsig-sha256 -----BEGIN PGP SIGNATURE----- -` + " " + ` - iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz - dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd - aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK - WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx - 1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4 - JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP - oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6 - U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy - zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI - VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X - HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR - 8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6 - =xybZ - -----END PGP SIGNATURE----- - -signed commit` - - sha := &Sha256Hash{ - 0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59, - 0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0, - } - gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare_sha256")) - assert.NoError(t, err) - assert.NotNil(t, gitRepo) - defer gitRepo.Close() - - commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString)) - assert.NoError(t, err) - require.NotNil(t, commitFromReader) - assert.EqualValues(t, sha, commitFromReader.ID) - assert.Equal(t, `-----BEGIN PGP SIGNATURE----- - -iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz -dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd -aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK -WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx -1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4 -JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP -oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6 -U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy -zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI -VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X -HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR -8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6 -=xybZ ------END PGP SIGNATURE-----`, commitFromReader.Signature.Signature) - assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e -parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 -author Adam Majer 1698676906 +0100 -committer Adam Majer 1698676906 +0100 - -signed commit`, commitFromReader.Signature.Payload) - assert.Equal(t, "Adam Majer ", commitFromReader.Author.String()) - - commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n")) - assert.NoError(t, err) - commitFromReader.CommitMessage.MessageRaw += "\n\n" - commitFromReader.Signature.Payload += "\n\n" - assert.Equal(t, commitFromReader, commitFromReader2) -} - func TestHasPreviousCommitSha256(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare_sha256") diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index 6896ce15564..15c2e845706 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -28,7 +28,15 @@ func TestGetFullCommitIDError(t *testing.T) { } func TestCommitFromReader(t *testing.T) { - commitString := `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 + for _, testCase := range []struct { + name, repo string + sha ObjectID + commitString, signature, payload, author string + }{ + { + "TestCommitFromReader", "repo1_bare", + &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}, + `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 parent 37991dec2c8e592043f47155ce4808d4580f9123 author silverwind 1563741793 +0200 committer silverwind 1563741793 +0200 @@ -49,19 +57,8 @@ gpgsig -----BEGIN PGP SIGNATURE----- =FRsO -----END PGP SIGNATURE----- -empty commit` - - sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2} - gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare")) - assert.NoError(t, err) - assert.NotNil(t, gitRepo) - defer gitRepo.Close() - - commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString)) - assert.NoError(t, err) - require.NotNil(t, commitFromReader) - assert.EqualValues(t, sha, commitFromReader.ID) - assert.Equal(t, `-----BEGIN PGP SIGNATURE----- +empty commit`, + `-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEWPb2jX6FS2mqyJRQLmK0HJOGlEMFAl00zmEACgkQLmK0HJOG lEMDFBAAhQKKqLD1VICygJMEB8t1gBmNLgvziOLfpX4KPWdPtBk3v/QJ7OrfMrVK @@ -76,24 +73,19 @@ sD53z/f0J+We4VZjY+pidvA9BGZPFVdR3wd3xGs8/oH6UWaLJAMGkLG6dDb3qDLm mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i 1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs= =FRsO ------END PGP SIGNATURE-----`, commitFromReader.Signature.Signature) - assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 +-----END PGP SIGNATURE-----`, + `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 parent 37991dec2c8e592043f47155ce4808d4580f9123 author silverwind 1563741793 +0200 committer silverwind 1563741793 +0200 -empty commit`, commitFromReader.Signature.Payload) - assert.Equal(t, "silverwind ", commitFromReader.Author.String()) - - commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n")) - assert.NoError(t, err) - commitFromReader.CommitMessage.MessageRaw += "\n\n" - commitFromReader.Signature.Payload += "\n\n" - assert.Equal(t, commitFromReader, commitFromReader2) -} - -func TestCommitWithEncodingFromReader(t *testing.T) { - commitString := `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 +empty commit`, + "silverwind ", + }, + { + "TestCommitWithEncodingFromReader", "repo1_bare", + &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2}, + strings.ReplaceAll(`tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 parent 47b24e7ab977ed31c5a39989d570847d6d0052af author KN4CK3R 1711702962 +0100 committer KN4CK3R 1711702962 +0100 @@ -113,19 +105,8 @@ gpgsig -----BEGIN PGP SIGNATURE----- =r3UU -----END PGP SIGNATURE----- -ISO-8859-1` - commitString = strings.ReplaceAll(commitString, "", " ") - sha := &Sha1Hash{0xfe, 0xaf, 0x4b, 0xa6, 0xbc, 0x63, 0x5f, 0xec, 0x44, 0x2f, 0x46, 0xdd, 0xd4, 0x51, 0x24, 0x16, 0xec, 0x43, 0xc2, 0xc2} - gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare")) - assert.NoError(t, err) - assert.NotNil(t, gitRepo) - defer gitRepo.Close() - - commitFromReader, err := CommitFromReader(sha, strings.NewReader(commitString)) - assert.NoError(t, err) - require.NotNil(t, commitFromReader) - assert.EqualValues(t, sha, commitFromReader.ID) - assert.Equal(t, `-----BEGIN PGP SIGNATURE----- +ISO-8859-1`, "", " "), + `-----BEGIN PGP SIGNATURE----- iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR @@ -138,21 +119,90 @@ SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz jw4YcO5u =r3UU ------END PGP SIGNATURE-----`, commitFromReader.Signature.Signature) - assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 +-----END PGP SIGNATURE-----`, + `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 parent 47b24e7ab977ed31c5a39989d570847d6d0052af author KN4CK3R 1711702962 +0100 committer KN4CK3R 1711702962 +0100 encoding ISO-8859-1 -ISO-8859-1`, commitFromReader.Signature.Payload) - assert.Equal(t, "KN4CK3R ", commitFromReader.Author.String()) +ISO-8859-1`, + "KN4CK3R ", + }, + { + "TestCommitFromReaderSha256", "repo1_bare_sha256", + &Sha256Hash{ + 0x94, 0x33, 0xb2, 0xa6, 0x2b, 0x96, 0x4c, 0x17, 0xa4, 0x48, 0x5a, 0xe1, 0x80, 0xf4, 0x5f, 0x59, + 0x5d, 0x3e, 0x69, 0xd3, 0x1b, 0x78, 0x60, 0x87, 0x77, 0x5e, 0x28, 0xc6, 0xb6, 0x39, 0x9d, 0xf0, + }, + `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e +parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 +author Adam Majer 1698676906 +0100 +committer Adam Majer 1698676906 +0100 +gpgsig-sha256 -----BEGIN PGP SIGNATURE----- +` + " " + ` + iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz + dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd + aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK + WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx + 1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4 + JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP + oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6 + U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy + zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI + VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X + HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR + 8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6 + =xybZ + -----END PGP SIGNATURE----- - commitFromReader2, err := CommitFromReader(sha, strings.NewReader(commitString+"\n\n")) - assert.NoError(t, err) - commitFromReader.CommitMessage.MessageRaw += "\n\n" - commitFromReader.Signature.Payload += "\n\n" - assert.Equal(t, commitFromReader, commitFromReader2) +signed commit`, + `-----BEGIN PGP SIGNATURE----- + +iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz +dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd +aTybplzT7QAL7h2to0QszGabtzMJPIA39xSFZNYNN30voK5YyyYibXluPKgjemfK +WNXwF+gkwgZI38gSvKf+vlqI+EYyIFe19wOhiju0m8SIlB5NEPiWHa17q2mqmqqx +1FWa2JdqLPYjAtSLFXeSZegrY5V1FxdemyMUONkg8YO9OSIMZiE0GsnnOXQ3xcT4 +JTCnmlUxIKw689UiEY80JopUIq+Wl7+qq9507IYYSUCyB6JazL42AKMzVCbD+qBP +oOzh/hafYgk9H9qCQXaLbmvs17zXRpicig1bAzqgAy1FDelvpERyRTydEajSLIG6 +U1cRCkgXCZ0NfsYNPPmBa8b3+rnstypXYTbyMwTln7FfUAaGo6o9JYiPMkzxlmsy +zfp/tcaY8+LlBL9aOJjtv+a0p+HrpCGd6CCa4ARfphTLq8QRSSh8uzlB9N+6HnRI +VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X +HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR +8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6 +=xybZ +-----END PGP SIGNATURE-----`, + `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e +parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 +author Adam Majer 1698676906 +0100 +committer Adam Majer 1698676906 +0100 + +signed commit`, + "Adam Majer ", + }, + } { + t.Run(testCase.name, func(t *testing.T) { + gitRepo, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, testCase.repo)) + assert.NoError(t, err) + assert.NotNil(t, gitRepo) + defer gitRepo.Close() + + commitFromReader, err := CommitFromReader(testCase.sha, strings.NewReader(testCase.commitString)) + assert.NoError(t, err) + require.NotNil(t, commitFromReader) + assert.Equal(t, testCase.sha, commitFromReader.ID) + assert.Equal(t, testCase.signature, commitFromReader.Signature.Signature) + assert.Equal(t, testCase.payload, commitFromReader.Signature.Payload) + assert.Equal(t, testCase.author, commitFromReader.Author.String()) + + commitFromReader2, err := CommitFromReader(testCase.sha, strings.NewReader(testCase.commitString+"\n\n")) + assert.NoError(t, err) + commitFromReader.CommitMessage.MessageRaw += "\n\n" + commitFromReader.Signature.Payload += "\n\n" + assert.Equal(t, commitFromReader, commitFromReader2) + }) + } } func TestHasPreviousCommit(t *testing.T) { diff --git a/modules/git/compare_test.go b/modules/git/compare_test.go index 64ac71f2858..ac4fe894afa 100644 --- a/modules/git/compare_test.go +++ b/modules/git/compare_test.go @@ -43,96 +43,47 @@ Hello from 2 func TestRepoGetDivergingCommits(t *testing.T) { repo := mockRepository("repo1_bare") - do, err := GetDivergingCommits(t.Context(), repo, "master", "branch2") - assert.NoError(t, err) - assert.Equal(t, &DivergeObject{ - Ahead: 1, - Behind: 5, - }, do) - - do, err = GetDivergingCommits(t.Context(), repo, "master", "master") - assert.NoError(t, err) - assert.Equal(t, &DivergeObject{ - Ahead: 0, - Behind: 0, - }, do) - - do, err = GetDivergingCommits(t.Context(), repo, "master", "test") - assert.NoError(t, err) - assert.Equal(t, &DivergeObject{ - Ahead: 0, - Behind: 2, - }, do) + for _, testcase := range []struct { + branch string + expected DivergeObject + }{ + {"branch2", DivergeObject{Ahead: 1, Behind: 5}}, + {"master", DivergeObject{Ahead: 0, Behind: 0}}, + {"test", DivergeObject{Ahead: 0, Behind: 2}}, + } { + do, err := GetDivergingCommits(t.Context(), repo, "master", testcase.branch) + assert.NoError(t, err) + assert.Equal(t, &testcase.expected, do) + } } func TestGetCommitIDsBetweenReverse(t *testing.T) { repo := mockRepository("repo1_bare") - - // tests raw commit IDs - commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo, - "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", - "ce064814f4a0d337b333e646ece456cd39fab612", - "", - 100, - ) - assert.NoError(t, err) - assert.Equal(t, []string{ + startRef := "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2" + endRef := "ce064814f4a0d337b333e646ece456cd39fab612" + expectedCommitIDs := []string{ "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1", "37991dec2c8e592043f47155ce4808d4580f9123", "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", "ce064814f4a0d337b333e646ece456cd39fab612", - }, commitIDs) - - commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, - "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", - "ce064814f4a0d337b333e646ece456cd39fab612", - "6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1", - 100, - ) - assert.NoError(t, err) - assert.Equal(t, []string{ - "37991dec2c8e592043f47155ce4808d4580f9123", - "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", - "ce064814f4a0d337b333e646ece456cd39fab612", - }, commitIDs) - - commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, - "8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", - "ce064814f4a0d337b333e646ece456cd39fab612", - "", - 3, - ) - assert.NoError(t, err) - assert.Equal(t, []string{ - "37991dec2c8e592043f47155ce4808d4580f9123", - "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", - "ce064814f4a0d337b333e646ece456cd39fab612", - }, commitIDs) - - // test branch names instead of raw commit IDs. - commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, - "test", - "master", - "", - 100, - ) - assert.NoError(t, err) - assert.Equal(t, []string{ - "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", - "ce064814f4a0d337b333e646ece456cd39fab612", - }, commitIDs) - - // add notref to exclude test - commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo, - "test", - "master", - "test", - 100, - ) - assert.NoError(t, err) - assert.Equal(t, []string{ - "feaf4ba6bc635fec442f46ddd4512416ec43c2c2", - "ce064814f4a0d337b333e646ece456cd39fab612", - }, commitIDs) + } + for _, testcase := range []struct { + startRef, endRef, notRef string + limit int + expected []string + }{ + // tests raw commit IDs + {startRef, endRef, "", 100, expectedCommitIDs}, + {startRef, endRef, expectedCommitIDs[1], 100, expectedCommitIDs[2:]}, + {startRef, endRef, "", 3, expectedCommitIDs[2:]}, + // test branch names instead of raw commit IDs. + {"test", "master", "", 100, expectedCommitIDs[3:]}, + // add notref to exclude test + {"test", "master", "test", 100, expectedCommitIDs[3:]}, + } { + commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo, testcase.startRef, testcase.endRef, testcase.notRef, testcase.limit) + assert.NoError(t, err) + assert.Equal(t, testcase.expected, commitIDs) + } } diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go index 3b6e64038b2..01fcabe98f8 100644 --- a/modules/git/grep_test.go +++ b/modules/git/grep_test.go @@ -20,9 +20,7 @@ func TestGrepSearch(t *testing.T) { assert.NoError(t, err) defer repo.Close() - res, err := GrepSearch(t.Context(), repo, "void", GrepOptions{}) - assert.NoError(t, err) - assert.Equal(t, []*GrepResult{ + expectedResults := []*GrepResult{ { Filename: "java-hello/main.java", LineNumbers: []int{3}, @@ -33,49 +31,29 @@ func TestGrepSearch(t *testing.T) { LineNumbers: []int{3}, LineCodes: []string{" public static void main(String[] args)"}, }, - }, res) + } + for _, testcase := range []struct { + options GrepOptions + expected []*GrepResult + }{ + {GrepOptions{}, expectedResults}, + {GrepOptions{PathspecList: []string{":(glob)java-hello/*"}}, expectedResults[:1]}, + {GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}}, expectedResults[1:]}, + {GrepOptions{MaxResultLimit: 1}, expectedResults[:1]}, + {GrepOptions{MaxResultLimit: 1, MaxLineLength: 39}, []*GrepResult{ + { + Filename: "java-hello/main.java", + LineNumbers: []int{3}, + LineCodes: []string{" public static void main(String[] arg"}, + }, + }}, + } { + res, err := GrepSearch(t.Context(), repo, "void", testcase.options) + assert.NoError(t, err) + assert.Equal(t, testcase.expected, res) + } - res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}}) - assert.NoError(t, err) - assert.Equal(t, []*GrepResult{ - { - Filename: "java-hello/main.java", - LineNumbers: []int{3}, - LineCodes: []string{" public static void main(String[] args)"}, - }, - }, res) - - res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}}) - assert.NoError(t, err) - assert.Equal(t, []*GrepResult{ - { - Filename: "main.vendor.java", - LineNumbers: []int{3}, - LineCodes: []string{" public static void main(String[] args)"}, - }, - }, res) - - res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{MaxResultLimit: 1}) - assert.NoError(t, err) - assert.Equal(t, []*GrepResult{ - { - Filename: "java-hello/main.java", - LineNumbers: []int{3}, - LineCodes: []string{" public static void main(String[] args)"}, - }, - }, res) - - res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{MaxResultLimit: 1, MaxLineLength: 39}) - assert.NoError(t, err) - assert.Equal(t, []*GrepResult{ - { - Filename: "java-hello/main.java", - LineNumbers: []int{3}, - LineCodes: []string{" public static void main(String[] arg"}, - }, - }, res) - - res, err = GrepSearch(t.Context(), repo, "no-such-content", GrepOptions{}) + res, err := GrepSearch(t.Context(), repo, "no-such-content", GrepOptions{}) assert.NoError(t, err) assert.Empty(t, res) diff --git a/modules/git/parse_treeentry_test.go b/modules/git/parse_treeentry_test.go index 5b81b49edda..93cbb999915 100644 --- a/modules/git/parse_treeentry_test.go +++ b/modules/git/parse_treeentry_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestParseTreeEntriesLong(t *testing.T) { +func TestParseTreeEntries(t *testing.T) { testCases := []struct { Input string Expected []*TreeEntry @@ -53,22 +53,6 @@ func TestParseTreeEntriesLong(t *testing.T) { }, }, }, - } - for _, testCase := range testCases { - entries, err := ParseTreeEntries([]byte(testCase.Input)) - assert.NoError(t, err) - assert.Len(t, entries, len(testCase.Expected)) - for i, entry := range entries { - assert.Equal(t, testCase.Expected[i], entry) - } - } -} - -func TestParseTreeEntriesShort(t *testing.T) { - testCases := []struct { - Input string - Expected []*TreeEntry - }{ { Input: `100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af README.md 040000 tree 84b90550547016f73c5dd3f50dea662389e67b6d assets diff --git a/modules/git/repo_blob_test.go b/modules/git/repo_blob_test.go index 9b4a40d2935..5abecd0ab6c 100644 --- a/modules/git/repo_blob_test.go +++ b/modules/git/repo_blob_test.go @@ -40,30 +40,23 @@ func TestRepository_GetBlob_Found(t *testing.T) { } } -func TestRepository_GetBlob_NotExist(t *testing.T) { - repoPath := filepath.Join(testReposDir, "repo1_bare") - r, err := OpenRepositoryLocal(t.Context(), repoPath) - assert.NoError(t, err) - defer r.Close() +func TestRepository_GetBlob_Errors(t *testing.T) { + for _, testCase := range []struct { + name string + id string + err error + }{ + {"NotExist", "0000000000000000000000000000000000000000", ErrNotExist{"0000000000000000000000000000000000000000", ""}}, + {"NoId", "", fmt.Errorf("length %d has no matched object format: %s", len(""), "")}, + } { + t.Run(testCase.name, func(t *testing.T) { + r, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare")) + assert.NoError(t, err) + defer r.Close() - testCase := "0000000000000000000000000000000000000000" - testError := ErrNotExist{testCase, ""} - - blob, err := r.GetBlob(testCase) - assert.Nil(t, blob) - assert.EqualError(t, err, testError.Error()) -} - -func TestRepository_GetBlob_NoId(t *testing.T) { - repoPath := filepath.Join(testReposDir, "repo1_bare") - r, err := OpenRepositoryLocal(t.Context(), repoPath) - assert.NoError(t, err) - defer r.Close() - - testCase := "" - testError := fmt.Errorf("length %d has no matched object format: %s", len(testCase), testCase) - - blob, err := r.GetBlob(testCase) - assert.Nil(t, blob) - assert.EqualError(t, err, testError.Error()) + blob, err := r.GetBlob(testCase.id) + assert.Nil(t, blob) + assert.EqualError(t, err, testCase.err.Error()) + }) + } } diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index f7335d1e688..5a7e314e99e 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -4,6 +4,7 @@ package git import ( + "context" "path/filepath" "testing" @@ -96,100 +97,37 @@ func BenchmarkGetRefsBySha(b *testing.B) { _, _ = bareRepo5.GetRefsBySha(b.Context(), "58a4bcc53ac13e7ff76127e0fb518b5262bf09af", "") } -func TestRepository_IsObjectExist(t *testing.T) { - ctx := t.Context() - repo, err := OpenRepositoryLocal(ctx, filepath.Join(testReposDir, "repo1_bare")) - require.NoError(t, err) - defer repo.Close() - - tests := []struct { - name string - arg string - want bool +func TestRepository_Existence(t *testing.T) { + for _, method := range []struct { + name string + exists func(*Repository, context.Context, string) bool + branchExists bool }{ - { - name: "empty", - arg: "", - want: false, - }, - { - name: "branch", - arg: "master", - want: false, - }, - { - name: "commit hash", - arg: "ce064814f4a0d337b333e646ece456cd39fab612", - want: true, - }, - { - name: "short commit hash", - arg: "ce06481", - want: true, - }, - { - name: "blob hash", - arg: "153f451b9ee7fa1da317ab17a127e9fd9d384310", - want: true, - }, - { - name: "short blob hash", - arg: "153f451", - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, repo.IsObjectExist(ctx, tt.arg)) - }) - } -} - -func TestRepository_IsReferenceExist(t *testing.T) { - ctx := t.Context() - repo, err := OpenRepositoryLocal(ctx, filepath.Join(testReposDir, "repo1_bare")) - require.NoError(t, err) - defer repo.Close() - - tests := []struct { - name string - arg string - want bool - }{ - { - name: "empty", - arg: "", - want: false, - }, - { - name: "branch", - arg: "master", - want: true, - }, - { - name: "commit hash", - arg: "ce064814f4a0d337b333e646ece456cd39fab612", - want: true, - }, - { - name: "short commit hash", - arg: "ce06481", - want: true, - }, - { - name: "blob hash", - arg: "153f451b9ee7fa1da317ab17a127e9fd9d384310", - want: true, - }, - { - name: "short blob hash", - arg: "153f451", - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, repo.IsReferenceExist(ctx, tt.arg)) + {"IsObjectExist", (*Repository).IsObjectExist, false}, + {"IsReferenceExist", (*Repository).IsReferenceExist, true}, + } { + t.Run(method.name, func(t *testing.T) { + ctx := t.Context() + repo, err := OpenRepositoryLocal(ctx, filepath.Join(testReposDir, "repo1_bare")) + require.NoError(t, err) + defer repo.Close() + + for _, testCase := range []struct { + name string + arg string + want bool + }{ + {"empty", "", false}, + {"branch", "master", method.branchExists}, + {"commit hash", "ce064814f4a0d337b333e646ece456cd39fab612", true}, + {"short commit hash", "ce06481", true}, + {"blob hash", "153f451b9ee7fa1da317ab17a127e9fd9d384310", true}, + {"short blob hash", "153f451", true}, + } { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.want, method.exists(repo, ctx, testCase.arg)) + }) + } }) } } diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index b89ad03ad27..377d27f459a 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -16,91 +16,96 @@ import ( "github.com/stretchr/testify/require" ) -func TestRepository_GetCommitBranches(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path) - assert.NoError(t, err) - defer bareRepo1.Close() - - // these test case are specific to the repo1_bare test repo - testCases := []struct { - CommitID string - ExpectedBranches []string +func TestRepository_CommitQueries(t *testing.T) { + for _, testCase := range []struct { + name string + run func(*testing.T, *Repository) }{ - {"2839944139e0de9737a044f78b0e4b40d989a9e3", []string{"branch1"}}, - {"5c80b0245c1c6f8343fa418ec374b13b5d4ee658", []string{"branch2"}}, - {"37991dec2c8e592043f47155ce4808d4580f9123", []string{"master"}}, - {"95bb4d39648ee7e325106df01a621c530863a653", []string{"branch1", "branch2"}}, - {"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", []string{"branch2", "master"}}, - {"master", []string{"master"}}, + {"TestRepository_GetCommitBranches", func(t *testing.T, bareRepo1 *Repository) { + // these test case are specific to the repo1_bare test repo + testCases := []struct { + CommitID string + ExpectedBranches []string + }{ + {"2839944139e0de9737a044f78b0e4b40d989a9e3", []string{"branch1"}}, + {"5c80b0245c1c6f8343fa418ec374b13b5d4ee658", []string{"branch2"}}, + {"37991dec2c8e592043f47155ce4808d4580f9123", []string{"master"}}, + {"95bb4d39648ee7e325106df01a621c530863a653", []string{"branch1", "branch2"}}, + {"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2", []string{"branch2", "master"}}, + {"master", []string{"master"}}, + } + for _, testCase := range testCases { + commit, err := bareRepo1.GetCommit(t.Context(), testCase.CommitID) + assert.NoError(t, err) + branches, err := bareRepo1.getBranches(t.Context(), nil, commit.ID.String(), 2) + assert.NoError(t, err) + assert.Equal(t, testCase.ExpectedBranches, branches) + } + }}, + {"TestGetTagCommitWithSignature", func(t *testing.T, bareRepo1 *Repository) { + // both the tag and the commit are signed here, this validates only the commit signature + commit, err := bareRepo1.GetCommit(t.Context(), "28b55526e7100924d864dd89e35c1ea62e7a5a32") + assert.NoError(t, err) + assert.NotNil(t, commit) + assert.NotNil(t, commit.Signature) + // test that signature is not in message + assert.Equal(t, "signed-commit\n", commit.CommitMessage.MessageRaw) + }}, + {"TestGetCommitWithBadCommitID", func(t *testing.T, bareRepo1 *Repository) { + commit, err := bareRepo1.GetCommit(t.Context(), "bad_branch") + assert.Nil(t, commit) + assert.Error(t, err) + assert.True(t, IsErrNotExist(err)) + }}, + {"TestIsCommitInRepo", func(t *testing.T, bareRepo1 *Repository) { + exist, err := IsCommitInRepo(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3") + assert.NoError(t, err) + assert.True(t, exist) + + exist, err = IsCommitInRepo(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + assert.NoError(t, err) + assert.False(t, exist) + }}, + {"TestIsCommitInBranch", func(t *testing.T, bareRepo1 *Repository) { + result, err := IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch1") + assert.NoError(t, err) + assert.True(t, result) + + result, err = IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch2") + assert.NoError(t, err) + assert.False(t, result) + + result, err = IsCommitInBranch(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "branch1") + assert.NoError(t, err) + assert.False(t, result) + }}, + {"TestGetRefCommitID", func(t *testing.T, bareRepo1 *Repository) { + // these test case are specific to the repo1_bare test repo + testCases := []struct { + Ref string + ExpectedCommitID string + }{ + {RefNameFromBranch("master").String(), "ce064814f4a0d337b333e646ece456cd39fab612"}, + {RefNameFromBranch("branch1").String(), "2839944139e0de9737a044f78b0e4b40d989a9e3"}, + {RefNameFromTag("test").String(), "3ad28a9149a2864384548f3d17ed7f38014c9e8a"}, + {"ce064814f4a0d337b333e646ece456cd39fab612", "ce064814f4a0d337b333e646ece456cd39fab612"}, + } + + for _, testCase := range testCases { + commitID, err := bareRepo1.GetRefCommitID(t.Context(), testCase.Ref) + if assert.NoError(t, err) { + assert.Equal(t, testCase.ExpectedCommitID, commitID) + } + } + }}, + } { + t.Run(testCase.name, func(t *testing.T) { + bareRepo1, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare")) + assert.NoError(t, err) + defer bareRepo1.Close() + testCase.run(t, bareRepo1) + }) } - for _, testCase := range testCases { - commit, err := bareRepo1.GetCommit(t.Context(), testCase.CommitID) - assert.NoError(t, err) - branches, err := bareRepo1.getBranches(t.Context(), nil, commit.ID.String(), 2) - assert.NoError(t, err) - assert.Equal(t, testCase.ExpectedBranches, branches) - } -} - -func TestGetTagCommitWithSignature(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path) - assert.NoError(t, err) - defer bareRepo1.Close() - - // both the tag and the commit are signed here, this validates only the commit signature - commit, err := bareRepo1.GetCommit(t.Context(), "28b55526e7100924d864dd89e35c1ea62e7a5a32") - assert.NoError(t, err) - assert.NotNil(t, commit) - assert.NotNil(t, commit.Signature) - // test that signature is not in message - assert.Equal(t, "signed-commit\n", commit.CommitMessage.MessageRaw) -} - -func TestGetCommitWithBadCommitID(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path) - assert.NoError(t, err) - defer bareRepo1.Close() - - commit, err := bareRepo1.GetCommit(t.Context(), "bad_branch") - assert.Nil(t, commit) - assert.Error(t, err) - assert.True(t, IsErrNotExist(err)) -} - -func TestIsCommitInRepo(t *testing.T) { - bareRepo1, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare")) - assert.NoError(t, err) - defer bareRepo1.Close() - - exist, err := IsCommitInRepo(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3") - assert.NoError(t, err) - assert.True(t, exist) - - exist, err = IsCommitInRepo(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") - assert.NoError(t, err) - assert.False(t, exist) -} - -func TestIsCommitInBranch(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path) - assert.NoError(t, err) - defer bareRepo1.Close() - - result, err := IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch1") - assert.NoError(t, err) - assert.True(t, result) - - result, err = IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch2") - assert.NoError(t, err) - assert.False(t, result) - - result, err = IsCommitInBranch(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "branch1") - assert.NoError(t, err) - assert.False(t, result) } func TestRepository_CommitsBetween(t *testing.T) { @@ -125,31 +130,6 @@ func TestRepository_CommitsBetween(t *testing.T) { } } -func TestGetRefCommitID(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path) - assert.NoError(t, err) - defer bareRepo1.Close() - - // these test case are specific to the repo1_bare test repo - testCases := []struct { - Ref string - ExpectedCommitID string - }{ - {RefNameFromBranch("master").String(), "ce064814f4a0d337b333e646ece456cd39fab612"}, - {RefNameFromBranch("branch1").String(), "2839944139e0de9737a044f78b0e4b40d989a9e3"}, - {RefNameFromTag("test").String(), "3ad28a9149a2864384548f3d17ed7f38014c9e8a"}, - {"ce064814f4a0d337b333e646ece456cd39fab612", "ce064814f4a0d337b333e646ece456cd39fab612"}, - } - - for _, testCase := range testCases { - commitID, err := bareRepo1.GetRefCommitID(t.Context(), testCase.Ref) - if assert.NoError(t, err) { - assert.Equal(t, testCase.ExpectedCommitID, commitID) - } - } -} - func TestCommitsByFileAndRange(t *testing.T) { defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)() diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 6c9f8e199ce..565e8150ed4 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -185,6 +185,22 @@ func TestRepository_GetAnnotatedTag(t *testing.T) { } func TestRepository_parseTagRef(t *testing.T) { + signature := `-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +` tests := []struct { name string @@ -273,38 +289,8 @@ func TestRepository_parseTagRef(t *testing.T) { * add changelog of v1.9.1 * Update CHANGELOG.md ------BEGIN PGP SIGNATURE----- - -aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 -3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT -T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU -REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE -slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G -1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt -f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx -yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 -kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg -qbHDASXl -=2yGi ------END PGP SIGNATURE----- - -`, - "contents:signature": `-----BEGIN PGP SIGNATURE----- - -aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 -3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT -T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU -REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE -slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G -1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt -f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx -yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 -kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg -qbHDASXl -=2yGi ------END PGP SIGNATURE----- - -`, +` + signature, + "contents:signature": signature, }, want: &Tag{ @@ -315,22 +301,7 @@ qbHDASXl Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), CommitMessage: CommitMessage{MessageRaw: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n"}, Signature: &CommitSignature{ - Signature: `-----BEGIN PGP SIGNATURE----- - -aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 -3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT -T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU -REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE -slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G -1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt -f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx -yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 -kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg -qbHDASXl -=2yGi ------END PGP SIGNATURE----- - -`, + Signature: signature, Payload: `object 3325fd8a973321fd59455492976c042dde3fd1ca type commit tag v0.0.1 diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go index b9b5aff61b1..f9c2b58edad 100644 --- a/modules/git/signature_test.go +++ b/modules/git/signature_test.go @@ -11,37 +11,18 @@ import ( ) func TestParseSignatureFromCommitLine(t *testing.T) { - tests := []struct { - line string - want *Signature - }{ - { - line: "a b 12345 +0100", - want: &Signature{ - Name: "a b", - Email: "c@d.com", - When: time.Unix(12345, 0).In(time.FixedZone("", 3600)), - }, - }, - { - line: "bad line", - want: &Signature{Name: "bad line"}, - }, - { - line: "bad < line", - want: &Signature{Name: "bad < line"}, - }, - { - line: "bad > line", - want: &Signature{Name: "bad > line"}, - }, - { - line: "bad-line ", - want: &Signature{Name: "bad-line "}, - }, - } - for _, test := range tests { - got := parseSignatureFromCommitLine(test.line) - assert.Equal(t, test.want, got) + assert.Equal(t, &Signature{ + Name: "a b", + Email: "c@d.com", + When: time.Unix(12345, 0).In(time.FixedZone("", 3600)), + }, parseSignatureFromCommitLine("a b 12345 +0100")) + + for _, line := range []string{ + "bad line", + "bad < line", + "bad > line", + "bad-line ", + } { + assert.Equal(t, &Signature{Name: line}, parseSignatureFromCommitLine(line)) } } diff --git a/modules/git/tree_entry_common_test.go b/modules/git/tree_entry_common_test.go index 6f4abb9bed4..a37e228c5b2 100644 --- a/modules/git/tree_entry_common_test.go +++ b/modules/git/tree_entry_common_test.go @@ -46,32 +46,22 @@ func TestFollowLink(t *testing.T) { assert.Nil(t, res) } - { - // should error for broken links - entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/broken_link") + for _, test := range []struct { + fullPath string + symlinkContent string + checkLink bool + }{ + {"foo/broken_link", "nar/broken_link", true}, // should error for broken links + {"foo/outside_repo", "../../outside_repo", true}, // should error for external links + {"foo/link_short", "a", false}, // testing fix for short link bug + } { + entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, test.fullPath) require.NoError(t, err) - assert.True(t, entry.IsLink()) - res, err := EntryFollowLink(t.Context(), r, commit, "foo/broken_link", entry) + if test.checkLink { + assert.True(t, entry.IsLink()) + } + res, err := EntryFollowLink(t.Context(), r, commit, test.fullPath, entry) assert.ErrorIs(t, err, util.ErrNotExist) - assert.Equal(t, "nar/broken_link", res.SymlinkContent) - } - - { - // should error for external links - entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/outside_repo") - require.NoError(t, err) - assert.True(t, entry.IsLink()) - res, err := EntryFollowLink(t.Context(), r, commit, "foo/outside_repo", entry) - assert.ErrorIs(t, err, util.ErrNotExist) - assert.Equal(t, "../../outside_repo", res.SymlinkContent) - } - - { - // testing fix for short link bug - entry, err := commit.Tree().GetTreeEntryByPath(t.Context(), r, "foo/link_short") - require.NoError(t, err) - res, err := EntryFollowLink(t.Context(), r, commit, "foo/link_short", entry) - assert.ErrorIs(t, err, util.ErrNotExist) - assert.Equal(t, "a", res.SymlinkContent) + assert.Equal(t, test.symlinkContent, res.SymlinkContent) } } diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go index 4f3280951ef..2267f18f4b3 100644 --- a/modules/git/url/url_test.go +++ b/modules/git/url/url_test.go @@ -17,156 +17,37 @@ import ( ) func TestParseGitURLs(t *testing.T) { - kases := []struct { - kase string - expected *GitURL + for _, kase := range []struct { + kase, scheme, host, path string + user *url.Userinfo + extraMark int }{ - { - kase: "git@127.0.0.1:go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "ssh", - User: url.User("git"), - Host: "127.0.0.1", - Path: "go-gitea/gitea.git", - }, - extraMark: 1, - }, - }, - { - kase: "git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "ssh", - User: url.User("git"), - Host: "[fe80::14fc:cec5:c174:d88%10]", - Path: "go-gitea/gitea.git", - }, - extraMark: 1, - }, - }, - { - kase: "git@[::1]:go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "ssh", - User: url.User("git"), - Host: "[::1]", - Path: "go-gitea/gitea.git", - }, - extraMark: 1, - }, - }, - { - kase: "git@github.com:go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "ssh", - User: url.User("git"), - Host: "github.com", - Path: "go-gitea/gitea.git", - }, - extraMark: 1, - }, - }, - { - kase: "ssh://git@github.com/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "ssh", - User: url.User("git"), - Host: "github.com", - Path: "/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - { - kase: "ssh://git@[::1]/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "ssh", - User: url.User("git"), - Host: "[::1]", - Path: "/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - { - kase: "/repositories/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "file", - Path: "/repositories/go-gitea/gitea.git", - }, - extraMark: 2, - }, - }, - { - kase: "file:///repositories/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "file", - Path: "/repositories/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - { - kase: "https://github.com/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "https", - Host: "github.com", - Path: "/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - { - kase: "https://git:git@github.com/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "https", - Host: "github.com", - User: url.UserPassword("git", "git"), - Path: "/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - { - kase: "https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "https", - Host: "[fe80::14fc:cec5:c174:d88%10]:20", - Path: "/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - - { - kase: "git://github.com/go-gitea/gitea.git", - expected: &GitURL{ - URL: &url.URL{ - Scheme: "git", - Host: "github.com", - Path: "/go-gitea/gitea.git", - }, - extraMark: 0, - }, - }, - } - - for _, kase := range kases { + {"git@127.0.0.1:go-gitea/gitea.git", "ssh", "127.0.0.1", "go-gitea/gitea.git", url.User("git"), 1}, + {"git@[fe80::14fc:cec5:c174:d88%2510]:go-gitea/gitea.git", "ssh", "[fe80::14fc:cec5:c174:d88%10]", "go-gitea/gitea.git", url.User("git"), 1}, + {"git@[::1]:go-gitea/gitea.git", "ssh", "[::1]", "go-gitea/gitea.git", url.User("git"), 1}, + {"git@github.com:go-gitea/gitea.git", "ssh", "github.com", "go-gitea/gitea.git", url.User("git"), 1}, + {"ssh://git@github.com/go-gitea/gitea.git", "ssh", "github.com", "/go-gitea/gitea.git", url.User("git"), 0}, + {"ssh://git@[::1]/go-gitea/gitea.git", "ssh", "[::1]", "/go-gitea/gitea.git", url.User("git"), 0}, + {"/repositories/go-gitea/gitea.git", "file", "", "/repositories/go-gitea/gitea.git", nil, 2}, + {"file:///repositories/go-gitea/gitea.git", "file", "", "/repositories/go-gitea/gitea.git", nil, 0}, + {"https://github.com/go-gitea/gitea.git", "https", "github.com", "/go-gitea/gitea.git", nil, 0}, + {"https://git:git@github.com/go-gitea/gitea.git", "https", "github.com", "/go-gitea/gitea.git", url.UserPassword("git", "git"), 0}, + {"https://[fe80::14fc:cec5:c174:d88%2510]:20/go-gitea/gitea.git", "https", "[fe80::14fc:cec5:c174:d88%10]:20", "/go-gitea/gitea.git", nil, 0}, + {"git://github.com/go-gitea/gitea.git", "git", "github.com", "/go-gitea/gitea.git", nil, 0}, + } { t.Run(kase.kase, func(t *testing.T) { u, err := ParseGitURL(kase.kase) assert.NoError(t, err) - assert.Equal(t, kase.expected.extraMark, u.extraMark) - assert.Equal(t, *kase.expected, *u) + assert.Equal(t, kase.extraMark, u.extraMark) + assert.Equal(t, GitURL{ + URL: &url.URL{ + Scheme: kase.scheme, + User: kase.user, + Host: kase.host, + Path: kase.path, + }, + extraMark: kase.extraMark, + }, *u) }) } } @@ -251,27 +132,18 @@ func TestMakeRepositoryBaseLink(t *testing.T) { defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")() defer test.MockVariableValue(&setting.AppSubURL, "/subpath")() - u, err := ParseRepositoryURL(t.Context(), "https://localhost:3000/subpath/user/repo.git") - assert.NoError(t, err) - assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u)) - - u, err = ParseRepositoryURL(t.Context(), "https://github.com/owner/repo.git") - assert.NoError(t, err) - assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u)) - - u, err = ParseRepositoryURL(t.Context(), "git@github.com:owner/repo.git") - assert.NoError(t, err) - assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u)) - - u, err = ParseRepositoryURL(t.Context(), "git+ssh://other:123/owner/repo.git") - assert.NoError(t, err) - assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u)) - - u, err = ParseRepositoryURL(t.Context(), "git+ssh://[::1]/owner/repo.git") - assert.NoError(t, err) - assert.Equal(t, "https://[::1]/owner/repo", MakeRepositoryWebLink(u)) - - u, err = ParseRepositoryURL(t.Context(), "git+ssh://[::1]:2222/owner/repo.git") - assert.NoError(t, err) - assert.Equal(t, "https://[::1]/owner/repo", MakeRepositoryWebLink(u)) + for _, kase := range []struct { + input, expected string + }{ + {"https://localhost:3000/subpath/user/repo.git", "/subpath/user/repo"}, + {"https://github.com/owner/repo.git", "https://github.com/owner/repo"}, + {"git@github.com:owner/repo.git", "https://github.com/owner/repo"}, + {"git+ssh://other:123/owner/repo.git", "https://other/owner/repo"}, + {"git+ssh://[::1]/owner/repo.git", "https://[::1]/owner/repo"}, + {"git+ssh://[::1]:2222/owner/repo.git", "https://[::1]/owner/repo"}, + } { + u, err := ParseRepositoryURL(t.Context(), kase.input) + assert.NoError(t, err) + assert.Equal(t, kase.expected, MakeRepositoryWebLink(u)) + } } diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go index 5c50844e3b9..665b0e593bf 100644 --- a/modules/highlight/highlight_test.go +++ b/modules/highlight/highlight_test.go @@ -34,12 +34,14 @@ func TestFile(t *testing.T) { name string code string want []template.HTML + plainWant []template.HTML lexerName string }{ { name: "empty.py", code: "", want: lines(""), + plainWant: lines(""), lexerName: "Python", }, { @@ -64,18 +66,21 @@ func TestFile(t *testing.T) { name: "tags.py", code: "<>", want: lines(`<>`), + plainWant: lines("<>"), lexerName: "Python", }, { name: "eol-no.py", code: "a=1", want: lines(`a=1`), + plainWant: lines(`a=1`), lexerName: "Python", }, { name: "eol-newline1.py", code: "a=1\n", want: lines(`a=1\n`), + plainWant: lines(`a=1\n`), lexerName: "Python", }, { @@ -86,6 +91,10 @@ func TestFile(t *testing.T) { \n `, ), + plainWant: lines(` +a=1\n +\n + `), lexerName: "Python", }, { @@ -106,6 +115,13 @@ c=2 \n c=2`, ), + plainWant: lines(` +def:\n + a=1\n +\n +b=''\n + \n +c=2`), lexerName: "Python", }, { @@ -135,67 +151,14 @@ Content-Type: text/html assert.Equal(t, tt.lexerName, lexerName) }) } -} - -func TestPlainText(t *testing.T) { - tests := []struct { - name string - code string - want []template.HTML - }{ - { - name: "empty.py", - code: "", - want: lines(""), - }, - { - name: "tags.py", - code: "<>", - want: lines("<>"), - }, - { - name: "eol-no.py", - code: "a=1", - want: lines(`a=1`), - }, - { - name: "eol-newline1.py", - code: "a=1\n", - want: lines(`a=1\n`), - }, - { - name: "eol-newline2.py", - code: "a=1\n\n", - want: lines(` -a=1\n -\n - `), - }, - { - name: "empty-line-with-space.py", - code: strings.ReplaceAll(strings.TrimSpace(` -def: - a=1 - -b='' -{space} -c=2 - `), "{space}", " "), - want: lines(` -def:\n - a=1\n -\n -b=''\n - \n -c=2`), - }, - } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - out := renderPlainText([]byte(tt.code)) - assert.Equal(t, tt.want, out) - }) + if tt.plainWant != nil { + t.Run("plaintext/"+tt.name, func(t *testing.T) { + out := renderPlainText([]byte(tt.code)) + assert.Equal(t, tt.plainWant, out) + }) + } } } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go index 2362c20553f..1320a864a72 100644 --- a/modules/httpcache/httpcache_test.go +++ b/modules/httpcache/httpcache_test.go @@ -25,76 +25,58 @@ func TestHandleGenericETagCache(t *testing.T) { respEtag string reqHeaders map[string]string wantHandled bool - wantHeaders map[string]string - wantStatus int } cases := []testCase{ { name: "No If-None-Match", wantHandled: false, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, }, { name: "Mismatched If-None-Match", reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag"`}, wantHandled: false, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, }, { name: "Matched If-None-Match", reqHeaders: map[string]string{"If-None-Match": matchedEtag}, wantHandled: true, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, - wantStatus: http.StatusNotModified, }, { name: "Multiple Mismatched If-None-Match", reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag1", "mismatched-etag2"`}, wantHandled: false, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, }, { name: "Wildcard If-None-Match matches any current representation", reqHeaders: map[string]string{"If-None-Match": "*"}, wantHandled: true, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, - wantStatus: http.StatusNotModified, }, { name: "Weak response tag matched by a strong request tag", respEtag: weakEtag, reqHeaders: map[string]string{"If-None-Match": matchedEtag}, wantHandled: true, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": weakEtag}, - wantStatus: http.StatusNotModified, }, { name: "Weak response tag matched by a weak request tag", respEtag: weakEtag, reqHeaders: map[string]string{"If-None-Match": weakEtag}, wantHandled: true, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": weakEtag}, - wantStatus: http.StatusNotModified, }, { name: "If-Modified-Since without If-None-Match", reqHeaders: map[string]string{"If-Modified-Since": lastModified}, wantHandled: true, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, - wantStatus: http.StatusNotModified, }, { name: "Mismatched If-None-Match takes precedence over If-Modified-Since", reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag"`, "If-Modified-Since": lastModified}, wantHandled: false, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, }, { name: "Multiple Matched If-None-Match", reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag", ` + matchedEtag}, wantHandled: true, - wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, - wantStatus: http.StatusNotModified, }, } @@ -107,10 +89,14 @@ func TestHandleGenericETagCache(t *testing.T) { w := httptest.NewRecorder() assert.Equal(t, tc.wantHandled, HandleGenericETagPrivateCache(req, w, util.IfZero(tc.respEtag, matchedEtag), lastModifiedTime)) resp := w.Result() - for k, v := range tc.wantHeaders { + for k, v := range map[string]string{ + "Last-Modified": lastModified, + "Cache-Control": util.Iif(tc.wantHandled, "", cacheControl), + "Etag": util.IfZero(tc.respEtag, matchedEtag), + } { assert.Equal(t, v, resp.Header.Get(k)) } - assert.Equal(t, tc.wantStatus, util.Iif(resp.StatusCode == http.StatusOK, 0, resp.StatusCode)) + assert.Equal(t, util.Iif(tc.wantHandled, http.StatusNotModified, 0), util.Iif(resp.StatusCode == http.StatusOK, 0, resp.StatusCode)) }) } } diff --git a/modules/httplib/serve_test.go b/modules/httplib/serve_test.go index 7f9fa2e2628..998f8efbca8 100644 --- a/modules/httplib/serve_test.go +++ b/modules/httplib/serve_test.go @@ -18,95 +18,51 @@ import ( "github.com/stretchr/testify/require" ) -func TestServeUserContentByReader(t *testing.T) { +func TestServeUserContent(t *testing.T) { data := "0123456789abcdef" - - test := func(t *testing.T, expectedStatusCode int, expectedContent string) { - _, rangeStr, _ := strings.Cut(t.Name(), "_range_") - r := &http.Request{Header: http.Header{}, Form: url.Values{}} - if rangeStr != "" { - r.Header.Set("Range", "bytes="+rangeStr) - } - reader := strings.NewReader(data) - w := httptest.NewRecorder() - ServeUserContentByReader(r, w, int64(len(data)), reader, ServeHeaderOptions{}) - assert.Equal(t, expectedStatusCode, w.Code) - if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { - assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length")) - assert.Equal(t, expectedContent, w.Body.String()) - } + for _, source := range []string{"reader", "file"} { + t.Run(source, func(t *testing.T) { + var tmpFile string + if source == "file" { + tmpFile = t.TempDir() + "/test" + assert.NoError(t, os.WriteFile(tmpFile, []byte(data), 0o644)) + } + for _, tc := range []struct { + rangeStr string + expectedStatusCode int + expectedContent string + }{ + {"", http.StatusOK, data}, + {"0-", http.StatusPartialContent, data}, + {"0-15", http.StatusPartialContent, data}, + {"1-", http.StatusPartialContent, data[1:]}, + {"1-3", http.StatusPartialContent, data[1 : 3+1]}, + {"16-", http.StatusRequestedRangeNotSatisfiable, ""}, + {"1-99999", http.StatusPartialContent, data[1:]}, + } { + t.Run("_range_"+tc.rangeStr, func(t *testing.T) { + r := &http.Request{Header: http.Header{}, Form: url.Values{}} + if tc.rangeStr != "" { + r.Header.Set("Range", "bytes="+tc.rangeStr) + } + w := httptest.NewRecorder() + if source == "file" { + seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644) + require.NoError(t, err) + defer seekReader.Close() + ServeUserContentByFile(r, w, seekReader, ServeHeaderOptions{}) + } else { + ServeUserContentByReader(r, w, int64(len(data)), strings.NewReader(data), ServeHeaderOptions{}) + } + assert.Equal(t, tc.expectedStatusCode, w.Code) + if tc.expectedStatusCode == http.StatusPartialContent || tc.expectedStatusCode == http.StatusOK { + assert.Equal(t, strconv.Itoa(len(tc.expectedContent)), w.Header().Get("Content-Length")) + assert.Equal(t, tc.expectedContent, w.Body.String()) + } + }) + } + }) } - - t.Run("_range_", func(t *testing.T) { - test(t, http.StatusOK, data) - }) - t.Run("_range_0-", func(t *testing.T) { - test(t, http.StatusPartialContent, data) - }) - t.Run("_range_0-15", func(t *testing.T) { - test(t, http.StatusPartialContent, data) - }) - t.Run("_range_1-", func(t *testing.T) { - test(t, http.StatusPartialContent, data[1:]) - }) - t.Run("_range_1-3", func(t *testing.T) { - test(t, http.StatusPartialContent, data[1:3+1]) - }) - t.Run("_range_16-", func(t *testing.T) { - test(t, http.StatusRequestedRangeNotSatisfiable, "") - }) - t.Run("_range_1-99999", func(t *testing.T) { - test(t, http.StatusPartialContent, data[1:]) - }) -} - -func TestServeUserContentByFile(t *testing.T) { - data := "0123456789abcdef" - tmpFile := t.TempDir() + "/test" - err := os.WriteFile(tmpFile, []byte(data), 0o644) - assert.NoError(t, err) - - test := func(t *testing.T, expectedStatusCode int, expectedContent string) { - _, rangeStr, _ := strings.Cut(t.Name(), "_range_") - r := &http.Request{Header: http.Header{}, Form: url.Values{}} - if rangeStr != "" { - r.Header.Set("Range", "bytes="+rangeStr) - } - - seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644) - require.NoError(t, err) - defer seekReader.Close() - - w := httptest.NewRecorder() - ServeUserContentByFile(r, w, seekReader, ServeHeaderOptions{}) - assert.Equal(t, expectedStatusCode, w.Code) - if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { - assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length")) - assert.Equal(t, expectedContent, w.Body.String()) - } - } - - t.Run("_range_", func(t *testing.T) { - test(t, http.StatusOK, data) - }) - t.Run("_range_0-", func(t *testing.T) { - test(t, http.StatusPartialContent, data) - }) - t.Run("_range_0-15", func(t *testing.T) { - test(t, http.StatusPartialContent, data) - }) - t.Run("_range_1-", func(t *testing.T) { - test(t, http.StatusPartialContent, data[1:]) - }) - t.Run("_range_1-3", func(t *testing.T) { - test(t, http.StatusPartialContent, data[1:3+1]) - }) - t.Run("_range_16-", func(t *testing.T) { - test(t, http.StatusRequestedRangeNotSatisfiable, "") - }) - t.Run("_range_1-99999", func(t *testing.T) { - test(t, http.StatusPartialContent, data[1:]) - }) } func TestServeSetHeaderContentRelated(t *testing.T) { diff --git a/modules/indexer/issues/bleve/bleve_test.go b/modules/indexer/issues/bleve/bleve_test.go index 2900d1f9721..079afc416c4 100644 --- a/modules/indexer/issues/bleve/bleve_test.go +++ b/modules/indexer/issues/bleve/bleve_test.go @@ -21,99 +21,72 @@ func TestBleveIndexer(t *testing.T) { tests.TestIndexer(t, indexer) } -func TestBleveIndexerNoAssignee(t *testing.T) { - dir := t.TempDir() - indexer := NewIndexer(dir) - defer indexer.Close() - - _, err := indexer.Init(t.Context()) - require.NoError(t, err) - - require.NoError(t, indexer.Index(t.Context(), - &internal.IndexerData{ID: 1, Title: "assigned through assignee_ids", AssigneeIDs: []int64{2}}, - &internal.IndexerData{ID: 2, Title: "unassigned", NoAssignee: true}, - &internal.IndexerData{ID: 3, Title: "assigned through multiple assignee_ids", AssigneeIDs: []int64{3, 4}}, - )) - - testCases := []struct { +func TestBleveIndexerSearch(t *testing.T) { + type searchCase struct { name string opts *internal.SearchOptions expectedIDs []int64 + } + for _, testCase := range []struct { + name string + data []*internal.IndexerData + searches []searchCase + checkTotal bool }{ { - name: "none", - opts: &internal.SearchOptions{AssigneeID: "(none)"}, - expectedIDs: []int64{2}, + name: "no assignee", + data: []*internal.IndexerData{ + {ID: 1, Title: "assigned through assignee_ids", AssigneeIDs: []int64{2}}, + {ID: 2, Title: "unassigned", NoAssignee: true}, + {ID: 3, Title: "assigned through multiple assignee_ids", AssigneeIDs: []int64{3, 4}}, + }, + searches: []searchCase{ + {"none", &internal.SearchOptions{AssigneeID: "(none)"}, []int64{2}}, + {"any", &internal.SearchOptions{AssigneeID: "(any)"}, []int64{1, 3}}, + {"specific", &internal.SearchOptions{AssigneeID: "2"}, []int64{1}}, + {"specific first multi-assignee", &internal.SearchOptions{AssigneeID: "3"}, []int64{3}}, + {"specific second multi-assignee", &internal.SearchOptions{AssigneeID: "4"}, []int64{3}}, + }, + checkTotal: true, }, { - name: "any", - opts: &internal.SearchOptions{AssigneeID: "(any)"}, - expectedIDs: []int64{1, 3}, + name: "token filter", + data: []*internal.IndexerData{ + {ID: 1, Title: "fix(packages): SomeThing needs a rewrite (#12345)"}, + {ID: 2, Title: "add support for mDNS discovery abc1234"}, + }, + searches: []searchCase{ + {"exact original case", &internal.SearchOptions{Keyword: "SomeThing"}, []int64{1}}, + {"case matching original transitions", &internal.SearchOptions{Keyword: "someThing"}, []int64{1}}, + {"all lower case", &internal.SearchOptions{Keyword: "something"}, []int64{1}}, + {"all upper case", &internal.SearchOptions{Keyword: "SOMETHING"}, []int64{1}}, + {"number match", &internal.SearchOptions{Keyword: "12345"}, []int64{1}}, + {"number as part", &internal.SearchOptions{Keyword: "1234"}, []int64{2}}, + {"sub-word search still works", &internal.SearchOptions{Keyword: "DNS"}, []int64{2}}, + {"sub-word search, lower case", &internal.SearchOptions{Keyword: "mdns"}, []int64{2}}, + {"keyword is camel case", &internal.SearchOptions{Keyword: "addSupport"}, []int64{2}}, + {"keyword not match", &internal.SearchOptions{Keyword: "addsupport"}, []int64{}}, + }, }, - { - name: "specific", - opts: &internal.SearchOptions{AssigneeID: "2"}, - expectedIDs: []int64{1}, - }, - { - name: "specific first multi-assignee", - opts: &internal.SearchOptions{AssigneeID: "3"}, - expectedIDs: []int64{3}, - }, - { - name: "specific second multi-assignee", - opts: &internal.SearchOptions{AssigneeID: "4"}, - expectedIDs: []int64{3}, - }, - } - - for _, testCase := range testCases { + } { t.Run(testCase.name, func(t *testing.T) { - result, err := indexer.Search(t.Context(), testCase.opts) + indexer := NewIndexer(t.TempDir()) + defer indexer.Close() + + _, err := indexer.Init(t.Context()) require.NoError(t, err) - assert.Equal(t, int64(len(testCase.expectedIDs)), result.Total) - assert.ElementsMatch(t, testCase.expectedIDs, searchResultIDs(result)) - }) - } -} + require.NoError(t, indexer.Index(t.Context(), testCase.data...)) -func TestBleveIndexerTokenFilter(t *testing.T) { - dir := t.TempDir() - indexer := NewIndexer(dir) - defer indexer.Close() - - _, err := indexer.Init(t.Context()) - require.NoError(t, err) - - require.NoError(t, indexer.Index(t.Context(), - &internal.IndexerData{ID: 1, Title: "fix(packages): SomeThing needs a rewrite (#12345)"}, - &internal.IndexerData{ID: 2, Title: "add support for mDNS discovery abc1234"}, - )) - - testCases := []struct { - name string - keyword string - expectedIDs []int64 - }{ - {name: "exact original case", keyword: "SomeThing", expectedIDs: []int64{1}}, - {name: "case matching original transitions", keyword: "someThing", expectedIDs: []int64{1}}, - {name: "all lower case", keyword: "something", expectedIDs: []int64{1}}, - {name: "all upper case", keyword: "SOMETHING", expectedIDs: []int64{1}}, - {name: "number match", keyword: "12345", expectedIDs: []int64{1}}, - {name: "number as part", keyword: "1234", expectedIDs: []int64{2}}, - {name: "sub-word search still works", keyword: "DNS", expectedIDs: []int64{2}}, - {name: "sub-word search, lower case", keyword: "mdns", expectedIDs: []int64{2}}, - {name: "keyword is camel case", keyword: "addSupport", expectedIDs: []int64{2}}, - {name: "keyword not match", keyword: "addsupport", expectedIDs: []int64{}}, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - result, err := indexer.Search(t.Context(), &internal.SearchOptions{ - Keyword: testCase.keyword, - }) - require.NoError(t, err) - assert.ElementsMatch(t, testCase.expectedIDs, searchResultIDs(result)) + for _, search := range testCase.searches { + t.Run(search.name, func(t *testing.T) { + result, err := indexer.Search(t.Context(), search.opts) + require.NoError(t, err) + if testCase.checkTotal { + assert.Equal(t, int64(len(search.expectedIDs)), result.Total) + } + assert.ElementsMatch(t, search.expectedIDs, searchResultIDs(result)) + }) + } }) } } diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go index 2c9814acf6e..80cb62beb10 100644 --- a/modules/indexer/issues/meilisearch/meilisearch_test.go +++ b/modules/indexer/issues/meilisearch/meilisearch_test.go @@ -89,6 +89,5 @@ func TestDoubleQuoteKeyword(t *testing.T) { assert.Empty(t, doubleQuoteKeyword("")) assert.Equal(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c")) assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) - assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`)) } diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go index 310e3831238..2899a23415c 100644 --- a/modules/issue/template/template_test.go +++ b/modules/issue/template/template_test.go @@ -15,6 +15,11 @@ import ( ) func TestValidate(t *testing.T) { + const metadata = ` +name: "test" +about: "this is about" +body: +` tests := []struct { name string filename string @@ -44,21 +49,13 @@ about: "this is about" }, { name: "markdown miss value", - content: ` -name: "test" -about: "this is about" -body: - - type: "markdown" + content: metadata + ` - type: "markdown" `, wantErr: "body[0](markdown): 'value' is required", }, { name: "markdown invalid value", - content: ` -name: "test" -about: "this is about" -body: - - type: "markdown" + content: metadata + ` - type: "markdown" attributes: value: true `, @@ -66,11 +63,7 @@ body: }, { name: "markdown empty value", - content: ` -name: "test" -about: "this is about" -body: - - type: "markdown" + content: metadata + ` - type: "markdown" attributes: value: "" `, @@ -78,33 +71,21 @@ body: }, { name: "textarea invalid id", - content: ` -name: "test" -about: "this is about" -body: - - type: "textarea" + content: metadata + ` - type: "textarea" id: "?" `, wantErr: "body[0](textarea): 'id' should contain only alphanumeric, '-' and '_'", }, { name: "textarea miss label", - content: ` -name: "test" -about: "this is about" -body: - - type: "textarea" + content: metadata + ` - type: "textarea" id: "1" `, wantErr: "body[0](textarea): 'label' is required", }, { name: "textarea conflict id", - content: ` -name: "test" -about: "this is about" -body: - - type: "textarea" + content: metadata + ` - type: "textarea" id: "1" attributes: label: "a" @@ -117,11 +98,7 @@ body: }, { name: "textarea invalid description", - content: ` -name: "test" -about: "this is about" -body: - - type: "textarea" + content: metadata + ` - type: "textarea" id: "1" attributes: label: "a" @@ -131,11 +108,7 @@ body: }, { name: "textarea invalid required", - content: ` -name: "test" -about: "this is about" -body: - - type: "textarea" + content: metadata + ` - type: "textarea" id: "1" attributes: label: "a" @@ -146,11 +119,7 @@ body: }, { name: "input invalid description", - content: ` -name: "test" -about: "this is about" -body: - - type: "input" + content: metadata + ` - type: "input" id: "1" attributes: label: "a" @@ -160,11 +129,7 @@ body: }, { name: "input invalid is_number", - content: ` -name: "test" -about: "this is about" -body: - - type: "input" + content: metadata + ` - type: "input" id: "1" attributes: label: "a" @@ -175,11 +140,7 @@ body: }, { name: "input invalid regex", - content: ` -name: "test" -about: "this is about" -body: - - type: "input" + content: metadata + ` - type: "input" id: "1" attributes: label: "a" @@ -190,11 +151,7 @@ body: }, { name: "dropdown invalid description", - content: ` -name: "test" -about: "this is about" -body: - - type: "dropdown" + content: metadata + ` - type: "dropdown" id: "1" attributes: label: "a" @@ -204,11 +161,7 @@ body: }, { name: "dropdown invalid multiple", - content: ` -name: "test" -about: "this is about" -body: - - type: "dropdown" + content: metadata + ` - type: "dropdown" id: "1" attributes: label: "a" @@ -218,11 +171,7 @@ body: }, { name: "dropdown invalid list", - content: ` -name: "test" -about: "this is about" -body: - - type: "dropdown" + content: metadata + ` - type: "dropdown" id: "1" attributes: label: "a" @@ -232,11 +181,7 @@ body: }, { name: "checkboxes invalid description", - content: ` -name: "test" -about: "this is about" -body: - - type: "checkboxes" + content: metadata + ` - type: "checkboxes" id: "1" attributes: label: "a" @@ -246,11 +191,7 @@ body: }, { name: "invalid type", - content: ` -name: "test" -about: "this is about" -body: - - type: "video" + content: metadata + ` - type: "video" id: "1" attributes: label: "a" @@ -259,11 +200,7 @@ body: }, { name: "dropdown miss options", - content: ` -name: "test" -about: "this is about" -body: - - type: "dropdown" + content: metadata + ` - type: "dropdown" id: "1" attributes: label: "a" @@ -272,11 +209,7 @@ body: }, { name: "dropdown invalid options", - content: ` -name: "test" -about: "this is about" -body: - - type: "dropdown" + content: metadata + ` - type: "dropdown" id: "1" attributes: label: "a" @@ -288,11 +221,7 @@ body: }, { name: "checkboxes invalid options", - content: ` -name: "test" -about: "this is about" -body: - - type: "checkboxes" + content: metadata + ` - type: "checkboxes" id: "1" attributes: label: "a" @@ -304,11 +233,7 @@ body: }, { name: "checkboxes option miss label", - content: ` -name: "test" -about: "this is about" -body: - - type: "checkboxes" + content: metadata + ` - type: "checkboxes" id: "1" attributes: label: "a" @@ -319,11 +244,7 @@ body: }, { name: "checkboxes option invalid required", - content: ` -name: "test" -about: "this is about" -body: - - type: "checkboxes" + content: metadata + ` - type: "checkboxes" id: "1" attributes: label: "a" @@ -335,11 +256,7 @@ body: }, { name: "field is required but hidden", - content: ` -name: "test" -about: "this is about" -body: - - type: "input" + content: metadata + ` - type: "input" id: "1" attributes: label: "a" @@ -351,11 +268,7 @@ body: }, { name: "checkboxes is required but hidden", - content: ` -name: "test" -about: "this is about" -body: - - type: checkboxes + content: metadata + ` - type: checkboxes id: "1" attributes: label: Label of checkboxes @@ -371,11 +284,7 @@ body: }, { name: "dropdown default is not an integer", - content: ` -name: "test" -about: "this is about" -body: - - type: dropdown + content: metadata + ` - type: dropdown id: "1" attributes: label: Label of dropdown @@ -393,11 +302,7 @@ body: }, { name: "dropdown default is out of range", - content: ` -name: "test" -about: "this is about" -body: - - type: dropdown + content: metadata + ` - type: dropdown id: "1" attributes: label: Label of dropdown @@ -415,11 +320,7 @@ body: }, { name: "dropdown without default is valid", - content: ` -name: "test" -about: "this is about" -body: - - type: dropdown + content: metadata + ` - type: dropdown id: "1" attributes: label: Label of dropdown @@ -770,19 +671,8 @@ Content } func TestRenderToMarkdown(t *testing.T) { - type args struct { - template string - values url.Values - } - tests := []struct { - name string - args args - want string - }{ - { - name: "normal", - args: args{ - template: ` + t.Run("normal", func(t *testing.T) { + template, err := Unmarshal("test.yaml", []byte(` name: Name title: Title about: About @@ -860,18 +750,11 @@ body: visible: [form] - label: Hidden Option of checkboxes visible: [content] -`, - values: map[string][]string{ - "form-field-id3": {"Value of id3"}, - "form-field-id4": {"Value of id4"}, - "form-field-id5": {"0,1"}, - "form-field-id6": {"1,2"}, - "form-field-id7-0": {"on"}, - "form-field-id7-2": {"on"}, - }, - }, - - want: `Value of the markdown shown in created issue +`)) + if err != nil { + t.Fatal(err) + } + want := `Value of the markdown shown in created issue ### Label of textarea @@ -894,70 +777,35 @@ Option 1 of dropdown, Option 2 of dropdown - [ ] Option 2 of checkboxes - [ ] Hidden Option of checkboxes -`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - template, err := Unmarshal("test.yaml", []byte(tt.args.template)) - if err != nil { - t.Fatal(err) - } - if got := RenderToMarkdown(template, tt.args.values); got != tt.want { - assert.Equal(t, tt.want, got) - } - }) - } +` + if got := RenderToMarkdown(template, url.Values{ + "form-field-id3": {"Value of id3"}, + "form-field-id4": {"Value of id4"}, + "form-field-id5": {"0,1"}, + "form-field-id6": {"1,2"}, + "form-field-id7-0": {"on"}, + "form-field-id7-2": {"on"}, + }); got != want { + assert.Equal(t, want, got) + } + }) } func Test_minQuotes(t *testing.T) { - type args struct { - value string - } tests := []struct { - name string - args args - want string + name string + value string + want string }{ - { - name: "without quote", - args: args{ - value: "Hello\nWorld", - }, - want: "```", - }, - { - name: "with 1 quote", - args: args{ - value: "Hello\nWorld\n`text`\n", - }, - want: "```", - }, - { - name: "with 3 quotes", - args: args{ - value: "Hello\nWorld\n`text`\n```go\ntext\n```\n", - }, - want: "````", - }, - { - name: "with more quotes", - args: args{ - value: "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n", - }, - want: "```````````", - }, - { - name: "not leading quotes", - args: args{ - value: "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n", - }, - want: "```", - }, + {"without quote", "Hello\nWorld", "```"}, + {"with 1 quote", "Hello\nWorld\n`text`\n", "```"}, + {"with 3 quotes", "Hello\nWorld\n`text`\n```go\ntext\n```\n", "````"}, + {"with more quotes", "Hello\nWorld\n`text`\n```go\ntext\n```\n``````````bash\ntext\n``````````\n", "```````````"}, + {"not leading quotes", "Hello\nWorld`text````go\ntext`````````````bash\ntext``````````\n", "```"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := minQuotes(tt.args.value) + got := minQuotes(tt.value) assert.Equal(t, tt.want, got) }) } diff --git a/modules/lfs/http_client_test.go b/modules/lfs/http_client_test.go index b6500cd6e96..c2998d3e272 100644 --- a/modules/lfs/http_client_test.go +++ b/modules/lfs/http_client_test.go @@ -162,209 +162,112 @@ func lfsTestRoundtripHandler(req *http.Request) *http.Response { return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(payload)} } -func TestHTTPClientDownload(t *testing.T) { - p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6} - - hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response { - assert.Equal(t, "POST", req.Method) - assert.Equal(t, MediaType, req.Header.Get("Content-type")) - assert.Equal(t, AcceptHeader, req.Header.Get("Accept")) - - var batchRequest BatchRequest - err := json.NewDecoder(req.Body).Decode(&batchRequest) - assert.NoError(t, err) - - assert.Equal(t, "download", batchRequest.Operation) - assert.Equal(t, &Reference{Name: "refs/heads/main"}, batchRequest.Ref) - assert.Len(t, batchRequest.Objects, 1) - assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) - assert.Equal(t, p.Size, batchRequest.Objects[0].Size) - - return lfsTestRoundtripHandler(req) - })} - dummy := &DummyTransferAdapter{} - - cases := []struct { +func TestHTTPClientTransfers(t *testing.T) { + type clientCase struct { endpoint string expectedError string + } + for _, operation := range []struct { + name string + cases []clientCase }{ { - endpoint: "https://status-not-ok.io", - expectedError: io.ErrUnexpectedEOF.Error(), + name: "download", + cases: []clientCase{ + {"https://status-not-ok.io", io.ErrUnexpectedEOF.Error()}, + {"https://invalid-json-response.io", "/(invalid json|invalid character)/"}, + {"https://valid-batch-request-download.io", ""}, + {"https://response-no-objects.io", ""}, + {"https://unknown-transfer-adapter.io", "TransferAdapter not found: "}, + {"https://error-in-response-objects.io", "Object not found"}, + {"https://empty-actions-map.io", "missing action 'download'"}, + {"https://download-actions-map.io", ""}, + {"https://upload-actions-map.io", "missing action 'download'"}, + {"https://verify-actions-map.io", "missing action 'download'"}, + {"https://unknown-actions-map.io", "missing action 'download'"}, + {"https://legacy-batch-request-download.io", ""}, + }, }, { - endpoint: "https://invalid-json-response.io", - expectedError: "/(invalid json|invalid character)/", + name: "upload", + cases: []clientCase{ + {"https://status-not-ok.io", io.ErrUnexpectedEOF.Error()}, + {"https://invalid-json-response.io", "/(invalid json|invalid character)/"}, + {"https://valid-batch-request-upload.io", ""}, + {"https://response-no-objects.io", ""}, + {"https://unknown-transfer-adapter.io", "TransferAdapter not found: "}, + {"https://error-in-response-objects.io", "Object not found"}, + {"https://empty-actions-map.io", ""}, + {"https://download-actions-map.io", "missing action 'upload'"}, + {"https://upload-actions-map.io", ""}, + {"https://verify-actions-map.io", "missing action 'upload'"}, + {"https://unknown-actions-map.io", "missing action 'upload'"}, + }, }, - { - endpoint: "https://valid-batch-request-download.io", - expectedError: "", - }, - { - endpoint: "https://response-no-objects.io", - expectedError: "", - }, - { - endpoint: "https://unknown-transfer-adapter.io", - expectedError: "TransferAdapter not found: ", - }, - { - endpoint: "https://error-in-response-objects.io", - expectedError: "Object not found", - }, - { - endpoint: "https://empty-actions-map.io", - expectedError: "missing action 'download'", - }, - { - endpoint: "https://download-actions-map.io", - expectedError: "", - }, - { - endpoint: "https://upload-actions-map.io", - expectedError: "missing action 'download'", - }, - { - endpoint: "https://verify-actions-map.io", - expectedError: "missing action 'download'", - }, - { - endpoint: "https://unknown-actions-map.io", - expectedError: "missing action 'download'", - }, - { - endpoint: "https://legacy-batch-request-download.io", - expectedError: "", - }, - } + } { + t.Run(operation.name, func(t *testing.T) { + p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6} - defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)() - for _, c := range cases { - t.Run(c.endpoint, func(t *testing.T) { - client := &HTTPClient{ - client: hc, - endpoint: c.endpoint, - transfers: map[string]TransferAdapter{ - "dummy": dummy, - }, - } + hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response { + assert.Equal(t, "POST", req.Method) + assert.Equal(t, MediaType, req.Header.Get("Content-type")) + assert.Equal(t, AcceptHeader, req.Header.Get("Accept")) - err := client.Download(t.Context(), "refs/heads/main", []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error { - if objectError != nil { - return objectError - } - b, err := io.ReadAll(content) + var batchRequest BatchRequest + err := json.NewDecoder(req.Body).Decode(&batchRequest) assert.NoError(t, err) - assert.Equal(t, []byte("dummy"), b) - return nil - }) - if c.expectedError != "" { - if strings.HasPrefix(c.expectedError, "/") && strings.HasSuffix(c.expectedError, "/") { - assert.Regexp(t, strings.Trim(c.expectedError, "/"), err.Error()) + + assert.Equal(t, operation.name, batchRequest.Operation) + if operation.name == "download" { + assert.Equal(t, &Reference{Name: "refs/heads/main"}, batchRequest.Ref) } else { - assert.ErrorContains(t, err, c.expectedError) + assert.Nil(t, batchRequest.Ref) } - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestHTTPClientUpload(t *testing.T) { - p := Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6} - - hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response { - assert.Equal(t, "POST", req.Method) - assert.Equal(t, MediaType, req.Header.Get("Content-type")) - assert.Equal(t, AcceptHeader, req.Header.Get("Accept")) - - var batchRequest BatchRequest - err := json.NewDecoder(req.Body).Decode(&batchRequest) - assert.NoError(t, err) - - assert.Equal(t, "upload", batchRequest.Operation) - assert.Nil(t, batchRequest.Ref) - assert.Len(t, batchRequest.Objects, 1) - assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) - assert.Equal(t, p.Size, batchRequest.Objects[0].Size) - - return lfsTestRoundtripHandler(req) - })} - dummy := &DummyTransferAdapter{} - - cases := []struct { - endpoint string - expectedError string - }{ - { - endpoint: "https://status-not-ok.io", - expectedError: io.ErrUnexpectedEOF.Error(), - }, - { - endpoint: "https://invalid-json-response.io", - expectedError: "/(invalid json|invalid character)/", - }, - { - endpoint: "https://valid-batch-request-upload.io", - expectedError: "", - }, - { - endpoint: "https://response-no-objects.io", - expectedError: "", - }, - { - endpoint: "https://unknown-transfer-adapter.io", - expectedError: "TransferAdapter not found: ", - }, - { - endpoint: "https://error-in-response-objects.io", - expectedError: "Object not found", - }, - { - endpoint: "https://empty-actions-map.io", - expectedError: "", - }, - { - endpoint: "https://download-actions-map.io", - expectedError: "missing action 'upload'", - }, - { - endpoint: "https://upload-actions-map.io", - expectedError: "", - }, - { - endpoint: "https://verify-actions-map.io", - expectedError: "missing action 'upload'", - }, - { - endpoint: "https://unknown-actions-map.io", - expectedError: "missing action 'upload'", - }, - } - - defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)() - for _, c := range cases { - t.Run(c.endpoint, func(t *testing.T) { - client := &HTTPClient{ - client: hc, - endpoint: c.endpoint, - transfers: map[string]TransferAdapter{ - "dummy": dummy, - }, - } - - err := client.Upload(t.Context(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) { - return io.NopCloser(new(bytes.Buffer)), objectError - }) - if c.expectedError != "" { - if strings.HasPrefix(c.expectedError, "/") && strings.HasSuffix(c.expectedError, "/") { - assert.Regexp(t, strings.Trim(c.expectedError, "/"), err.Error()) - } else { - assert.ErrorContains(t, err, c.expectedError) - } - } else { - assert.NoError(t, err) + assert.Len(t, batchRequest.Objects, 1) + assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) + assert.Equal(t, p.Size, batchRequest.Objects[0].Size) + + return lfsTestRoundtripHandler(req) + })} + dummy := &DummyTransferAdapter{} + + defer test.MockVariableValue(&setting.LFSClient.BatchOperationConcurrency, 8)() + for _, testCase := range operation.cases { + t.Run(testCase.endpoint, func(t *testing.T) { + client := &HTTPClient{ + client: hc, + endpoint: testCase.endpoint, + transfers: map[string]TransferAdapter{ + "dummy": dummy, + }, + } + + var err error + if operation.name == "download" { + err = client.Download(t.Context(), "refs/heads/main", []Pointer{p}, func(p Pointer, content io.ReadCloser, objectError error) error { + if objectError != nil { + return objectError + } + b, err := io.ReadAll(content) + assert.NoError(t, err) + assert.Equal(t, []byte("dummy"), b) + return nil + }) + } else { + err = client.Upload(t.Context(), []Pointer{p}, func(p Pointer, objectError error) (io.ReadCloser, error) { + return io.NopCloser(new(bytes.Buffer)), objectError + }) + } + if testCase.expectedError != "" { + if strings.HasPrefix(testCase.expectedError, "/") && strings.HasSuffix(testCase.expectedError, "/") { + assert.Regexp(t, strings.Trim(testCase.expectedError, "/"), err.Error()) + } else { + assert.ErrorContains(t, err, testCase.expectedError) + } + } else { + assert.NoError(t, err) + } + }) } }) } diff --git a/modules/lfs/transferadapter_test.go b/modules/lfs/transferadapter_test.go index b3f6422a782..8eea459e83d 100644 --- a/modules/lfs/transferadapter_test.go +++ b/modules/lfs/transferadapter_test.go @@ -69,7 +69,7 @@ func TestBasicTransferAdapter(t *testing.T) { hc := &http.Client{Transport: RoundTripFunc(roundTripHandler)} a := &BasicTransferAdapter{hc} - t.Run("Download", func(t *testing.T) { + testOperation := func(t *testing.T, endpoint string, transfer func(*Link) error) { cases := []struct { link *Link expectederror string @@ -77,7 +77,7 @@ func TestBasicTransferAdapter(t *testing.T) { // case 0 { link: &Link{ - Href: "https://download-request.io", + Href: endpoint, Header: map[string]string{"test-header": "test-value"}, }, expectederror: "", @@ -93,46 +93,26 @@ func TestBasicTransferAdapter(t *testing.T) { } for n, c := range cases { - _, err := a.Download(t.Context(), c.link) + err := transfer(c.link) if len(c.expectederror) > 0 { assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) } else { assert.NoError(t, err, "case %d", n) } } + } + + t.Run("Download", func(t *testing.T) { + testOperation(t, "https://download-request.io", func(link *Link) error { + _, err := a.Download(t.Context(), link) + return err + }) }) t.Run("Upload", func(t *testing.T) { - cases := []struct { - link *Link - expectederror string - }{ - // case 0 - { - link: &Link{ - Href: "https://upload-request.io", - Header: map[string]string{"test-header": "test-value"}, - }, - expectederror: "", - }, - // case 1 - { - link: &Link{ - Href: "https://error-response.io", - Header: map[string]string{"test-header": "test-value"}, - }, - expectederror: "Object not found", - }, - } - - for n, c := range cases { - err := a.Upload(t.Context(), c.link, p, strings.NewReader("dummy")) - if len(c.expectederror) > 0 { - assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) - } else { - assert.NoError(t, err, "case %d", n) - } - } + testOperation(t, "https://upload-request.io", func(link *Link) error { + return a.Upload(t.Context(), link, p, strings.NewReader("dummy")) + }) }) t.Run("Upload created", func(t *testing.T) { @@ -145,35 +125,8 @@ func TestBasicTransferAdapter(t *testing.T) { }) t.Run("Verify", func(t *testing.T) { - cases := []struct { - link *Link - expectederror string - }{ - // case 0 - { - link: &Link{ - Href: "https://verify-request.io", - Header: map[string]string{"test-header": "test-value"}, - }, - expectederror: "", - }, - // case 1 - { - link: &Link{ - Href: "https://error-response.io", - Header: map[string]string{"test-header": "test-value"}, - }, - expectederror: "Object not found", - }, - } - - for n, c := range cases { - err := a.Verify(t.Context(), c.link, p) - if len(c.expectederror) > 0 { - assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) - } else { - assert.NoError(t, err, "case %d", n) - } - } + testOperation(t, "https://verify-request.io", func(link *Link) error { + return a.Verify(t.Context(), link, p) + }) }) } diff --git a/modules/log/event_format_test.go b/modules/log/event_format_test.go index 6fd0f36d485..f7e38234144 100644 --- a/modules/log/event_format_test.go +++ b/modules/log/event_format_test.go @@ -11,45 +11,33 @@ import ( ) func TestItoa(t *testing.T) { - b := itoa(nil, 0, 0) - assert.Equal(t, "0", string(b)) - - b = itoa(nil, 0, 1) - assert.Equal(t, "0", string(b)) - - b = itoa(nil, 0, 2) - assert.Equal(t, "00", string(b)) + for width, expected := range []string{"0", "0", "00"} { + assert.Equal(t, expected, string(itoa(nil, 0, width))) + } } func TestEventFormatTextMessage(t *testing.T) { - res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: false, Flags: Flags{defined: true, flags: 0xffffffff}}, - &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - Level: ERROR, - Stacktrace: "stacktrace", - }, - "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), - ) - - assert.Equal(t, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [no-gopid] msg format: arg0 arg1 + for _, testCase := range []struct { + colorize bool + expected string + }{ + {false, `[PREFIX] 2020/01/02 03:04:05.000000 filename:123:caller [E] [no-gopid] msg format: arg0 arg1 stacktrace -`, string(res)) - - res = EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: true, Flags: Flags{defined: true, flags: 0xffffffff}}, - &Event{ - Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), - Caller: "caller", - Filename: "filename", - Line: 123, - Level: ERROR, - Stacktrace: "stacktrace", - }, - "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), - ) - - assert.Equal(t, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mno-gopid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n", string(res)) +`}, + {true, "[PREFIX] \x1b[36m2020/01/02 03:04:05.000000 \x1b[0m\x1b[32mfilename:123:\x1b[32mcaller\x1b[0m \x1b[1;31m[E]\x1b[0m [\x1b[93mno-gopid\x1b[0m] msg format: arg0 \x1b[34marg1\x1b[0m\n\tstacktrace\n\n"}, + } { + res := EventFormatTextMessage(&WriterMode{Prefix: "[PREFIX] ", Colorize: testCase.colorize, Flags: Flags{defined: true, flags: 0xffffffff}}, + &Event{ + Time: time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC), + Caller: "caller", + Filename: "filename", + Line: 123, + Level: ERROR, + Stacktrace: "stacktrace", + }, + "msg format: %v %v", "arg0", NewColoredValue("arg1", FgBlue), + ) + assert.Equal(t, testCase.expected, string(res)) + } } diff --git a/modules/log/level_test.go b/modules/log/level_test.go index 8dc96ecfaeb..fdee7f6abd3 100644 --- a/modules/log/level_test.go +++ b/modules/log/level_test.go @@ -24,25 +24,17 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) { assert.Equal(t, string(makeTestLevelBytes(INFO.String())), string(levelBytes)) var testLevel testLevel - err = json.Unmarshal(levelBytes, &testLevel) - assert.NoError(t, err) - assert.Equal(t, INFO, testLevel.Level) - - err = json.Unmarshal(makeTestLevelBytes(`FOFOO`), &testLevel) - assert.NoError(t, err) - assert.Equal(t, INFO, testLevel.Level) - - err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 2), &testLevel) - assert.NoError(t, err) - assert.Equal(t, INFO, testLevel.Level) - - err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 10012), &testLevel) - assert.NoError(t, err) - assert.Equal(t, INFO, testLevel.Level) - - err = json.Unmarshal([]byte(`{"level":{}}`), &testLevel) - assert.NoError(t, err) - assert.Equal(t, INFO, testLevel.Level) + for _, levelJSON := range [][]byte{ + levelBytes, + makeTestLevelBytes(`FOFOO`), + fmt.Appendf(nil, `{"level":%d}`, 2), + fmt.Appendf(nil, `{"level":%d}`, 10012), + []byte(`{"level":{}}`), + } { + err = json.Unmarshal(levelJSON, &testLevel) + assert.NoError(t, err) + assert.Equal(t, INFO, testLevel.Level) + } assert.Equal(t, INFO.String(), Level(1001).String()) diff --git a/modules/markup/camo_test.go b/modules/markup/camo_test.go index d79b78605fc..0dba7b312d3 100644 --- a/modules/markup/camo_test.go +++ b/modules/markup/camo_test.go @@ -21,27 +21,18 @@ func TestCamoHandleLink(t *testing.T) { setting.Camo.ServerURL = "https://image.proxy" setting.Camo.HMACKey = "geheim" dataURL := "data:image/png;base64,i//V" - assert.Equal(t, dataURL, camoHandleLink(dataURL)) - - assert.Equal(t, - "https://gitea.com/img.jpg", - camoHandleLink("https://gitea.com/img.jpg")) - assert.Equal(t, - "https://testimages.org/img.jpg", - camoHandleLink("https://testimages.org/img.jpg")) - assert.Equal(t, - "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc", - camoHandleLink("http://testimages.org/img.jpg")) + test := func(expectedHTTPS string) { + assert.Equal(t, dataURL, camoHandleLink(dataURL)) + assert.Equal(t, + "https://gitea.com/img.jpg", + camoHandleLink("https://gitea.com/img.jpg")) + assert.Equal(t, expectedHTTPS, camoHandleLink("https://testimages.org/img.jpg")) + assert.Equal(t, + "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc", + camoHandleLink("http://testimages.org/img.jpg")) + } + test("https://testimages.org/img.jpg") setting.Camo.Always = true - assert.Equal(t, dataURL, camoHandleLink(dataURL)) - assert.Equal(t, - "https://gitea.com/img.jpg", - camoHandleLink("https://gitea.com/img.jpg")) - assert.Equal(t, - "https://image.proxy/tkdlvmqpbIr7SjONfHNgEU622y0/aHR0cHM6Ly90ZXN0aW1hZ2VzLm9yZy9pbWcuanBn", - camoHandleLink("https://testimages.org/img.jpg")) - assert.Equal(t, - "https://image.proxy/eivin43gJwGVIjR9MiYYtFIk0mw/aHR0cDovL3Rlc3RpbWFnZXMub3JnL2ltZy5qcGc", - camoHandleLink("http://testimages.org/img.jpg")) + test("https://image.proxy/tkdlvmqpbIr7SjONfHNgEU622y0/aHR0cHM6Ly90ZXN0aW1hZ2VzLm9yZy9pbWcuanBn") } diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index f0366f68275..6c0f52c879a 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -85,7 +85,6 @@ func TestRender_IssueIndexPattern(t *testing.T) { test("#abcd") test("test#1234") test("#1234test") - test("#abcd") test("test!1234") test("!1234test") test(" test !1234test") @@ -257,16 +256,7 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) { testRenderIssueIndexPattern(t, "#1", "#1", NewTestRenderContext(metas)) testRenderIssueIndexPattern(t, "#1312", "#1312", NewTestRenderContext(metas)) testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(metas)) -} -func TestRender_PostProcessIssueTitle(t *testing.T) { - setting.AppURL = TestAppURL - metas := map[string]string{ - "format": "https://someurl.com/{user}/{repo}/{index}", - "user": "someUser", - "repo": "someRepo", - "style": IssueNameStyleNumeric, - } actual := PostProcessIssueTitle(NewTestRenderContext(metas), "#1") assert.Equal(t, template.HTML("#1"), actual) } diff --git a/modules/markup/html_issue_test.go b/modules/markup/html_issue_test.go index 210a699051d..6ed0df082be 100644 --- a/modules/markup/html_issue_test.go +++ b/modules/markup/html_issue_test.go @@ -37,43 +37,24 @@ func TestRender_IssueList(t *testing.T) { assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out))) } - t.Run("NormalIssueRef", func(t *testing.T) { - test( - "#12345", + for _, tc := range []struct { + name, input, expected string + }{ + { + "NormalIssueRef", "#12345", `

#12345

`, - ) - }) - - t.Run("ListIssueRef", func(t *testing.T) { - test( - "* #12345", - `
    + }, + {"ListIssueRef", "* #12345", `
    • issue #12345
    • -
    `, - ) - }) - - t.Run("ListIssueRefNormal", func(t *testing.T) { - test( - "* foo #12345 bar", - `
      +
    `}, + {"ListIssueRefNormal", "* foo #12345 bar", ``, - ) - }) - - t.Run("ListTodoIssueRef", func(t *testing.T) { - test( - "* [ ] #12345", - `
      +
    `}, + {"ListTodoIssueRef", "* [ ] #12345", `
    • issue #12345
    • -
    `, - ) - }) - - t.Run("IssueFootnote", func(t *testing.T) { - test( - "foo[^1][^2]\n\n[^1]: bar\n[^2]: baz", +
`}, + { + "IssueFootnote", "foo[^1][^2]\n\n[^1]: bar\n[^2]: baz", `

foo1 2


@@ -86,6 +67,10 @@ func TestRender_IssueList(t *testing.T) {
`, - ) - }) + }, + } { + t.Run(tc.name, func(_t *testing.T) { + test(tc.input, tc.expected) + }) + } } diff --git a/modules/markup/html_node_test.go b/modules/markup/html_node_test.go index 007e3c2a122..f5751e4ad1a 100644 --- a/modules/markup/html_node_test.go +++ b/modules/markup/html_node_test.go @@ -11,94 +11,79 @@ import ( ) func TestProcessNodeAttrID_HTMLHeadingWithoutID(t *testing.T) { - // Test that HTML headings without id get an auto-generated id from their text content - // when EnableHeadingIDGeneration is true (for repo files and wiki pages) testCases := []struct { - name string - input string - expected string + name string + generateHeadingID bool + input, expected string }{ + // Test that HTML headings without id get an auto-generated id from their text content + // when EnableHeadingIDGeneration is true (for repo files and wiki pages) { - name: "h1 without id", - input: `

Heading without ID

`, - expected: `

Heading without ID

`, + "h1 without id", true, + `

Heading without ID

`, + `

Heading without ID

`, }, { - name: "h2 without id", - input: `

Another Heading

`, - expected: `

Another Heading

`, + "h2 without id", true, + `

Another Heading

`, + `

Another Heading

`, }, { - name: "h3 without id", - input: `

Third Level

`, - expected: `

Third Level

`, + "h3 without id", true, + `

Third Level

`, + `

Third Level

`, }, { - name: "h1 with existing id should keep it", - input: `

Heading with ID

`, - expected: `

Heading with ID

`, + "h1 with existing id should keep it", true, + `

Heading with ID

`, + `

Heading with ID

`, }, { - name: "h1 with user-content prefix should not double prefix", - input: `

Already Prefixed

`, - expected: `

Already Prefixed

`, + "h1 with user-content prefix should not double prefix", true, + `

Already Prefixed

`, + `

Already Prefixed

`, }, { - name: "heading with special characters", - input: `

What is Wine Staging?

`, - expected: `

What is Wine Staging?

`, + "heading with special characters", true, + `

What is Wine Staging?

`, + `

What is Wine Staging?

`, }, { - name: "heading with nested elements", - input: `

Bold and Italic

`, - expected: `

Bold and Italic

`, + "heading with nested elements", true, + `

Bold and Italic

`, + `

Bold and Italic

`, + }, + // Test that HTML headings in comment-like contexts (issue comments) + // do NOT get auto-generated IDs to avoid duplicate IDs on pages with multiple documents. + // This is controlled by EnableHeadingIDGeneration which defaults to false. + // Default context without EnableHeadingIDGeneration (simulates comment rendering) + { + "h1 without id in comment context", false, + `

Heading without ID

`, + `

Heading without ID

`, + }, + { + "h2 without id in comment context", false, + `

Another Heading

`, + `

Another Heading

`, + }, + { + "h1 with existing id should still be prefixed", false, + `

Heading with ID

`, + `

Heading with ID

`, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var result strings.Builder - ctx := NewTestRenderContext().WithEnableHeadingIDGeneration(true) + ctx := NewTestRenderContext() + if tc.generateHeadingID { + ctx.WithEnableHeadingIDGeneration(true) + } err := PostProcessDefault(ctx, strings.NewReader(tc.input), &result) assert.NoError(t, err) assert.Equal(t, tc.expected, strings.TrimSpace(result.String())) }) } } - -func TestProcessNodeAttrID_SkipHeadingIDForComments(t *testing.T) { - // Test that HTML headings in comment-like contexts (issue comments) - // do NOT get auto-generated IDs to avoid duplicate IDs on pages with multiple documents. - // This is controlled by EnableHeadingIDGeneration which defaults to false. - testCases := []struct { - name string - input string - expected string - }{ - { - name: "h1 without id in comment context", - input: `

Heading without ID

`, - expected: `

Heading without ID

`, - }, - { - name: "h2 without id in comment context", - input: `

Another Heading

`, - expected: `

Another Heading

`, - }, - { - name: "h1 with existing id should still be prefixed", - input: `

Heading with ID

`, - expected: `

Heading with ID

`, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - var result strings.Builder - // Default context without EnableHeadingIDGeneration (simulates comment rendering) - err := PostProcessDefault(NewTestRenderContext(), strings.NewReader(tc.input), &result) - assert.NoError(t, err) - assert.Equal(t, tc.expected, strings.TrimSpace(result.String())) - }) - } -} diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 5b87b1a2e2d..66456c88977 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -145,90 +145,42 @@ func TestRender_links(t *testing.T) { setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"} common.InitLinkURLSchemes(setting.Markdown.CustomURLSchemes) - // Text that should be turned into URL - test( - "https://www.example.com", - `

https://www.example.com

`) - test( - "http://www.example.com", - `

http://www.example.com

`) - test( - "https://example.com", - `

https://example.com

`) - test( - "http://example.com", - `

http://example.com

`) - test( - "http://foo.com/blah_blah", - `

http://foo.com/blah_blah

`) - test( - "http://foo.com/blah_blah/", - `

http://foo.com/blah_blah/

`) - test( - "http://www.example.com/wpstyle/?p=364", - `

http://www.example.com/wpstyle/?p=364

`) - test( - "https://www.example.com/foo/?bar=baz&inga=42&quux", - `

https://www.example.com/foo/?bar=baz&inga=42&quux

`) - test( - "http://142.42.1.1/", - `

http://142.42.1.1/

`) - test( - "https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd", - `

https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd

`) - test( - "https://en.wikipedia.org/wiki/URL_(disambiguation)", - `

https://en.wikipedia.org/wiki/URL_(disambiguation)

`) - test( - "https://foo_bar.example.com/", - `

https://foo_bar.example.com/

`) - test( - "https://stackoverflow.com/questions/2896191/what-is-go-used-fore", - `

https://stackoverflow.com/questions/2896191/what-is-go-used-fore

`) - test( - "https://username:password@gitea.com", - `

https://username:password@gitea.com

`) - test( - "ftp://gitea.com/file.txt", - `

ftp://gitea.com/file.txt

`) - test( - "magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download", - `

magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download

`) - test( - `[link](https://example.com)`, - `

link

`) - test( - `[link](mailto:test@example.com)`, - `

link

`) - test( - `[link](javascript:xss)`, - `

link

`) + for _, testCase := range []struct { + input, expected string + }{ + // Text that should be turned into URL + {"https://www.example.com", `

https://www.example.com

`}, + {"http://www.example.com", `

http://www.example.com

`}, + {"https://example.com", `

https://example.com

`}, + {"http://example.com", `

http://example.com

`}, + {"http://foo.com/blah_blah", `

http://foo.com/blah_blah

`}, + {"http://foo.com/blah_blah/", `

http://foo.com/blah_blah/

`}, + {"http://www.example.com/wpstyle/?p=364", `

http://www.example.com/wpstyle/?p=364

`}, + {"https://www.example.com/foo/?bar=baz&inga=42&quux", `

https://www.example.com/foo/?bar=baz&inga=42&quux

`}, + {"http://142.42.1.1/", `

http://142.42.1.1/

`}, + {"https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd", `

https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd

`}, + {"https://en.wikipedia.org/wiki/URL_(disambiguation)", `

https://en.wikipedia.org/wiki/URL_(disambiguation)

`}, + {"https://foo_bar.example.com/", `

https://foo_bar.example.com/

`}, + {"https://stackoverflow.com/questions/2896191/what-is-go-used-fore", `

https://stackoverflow.com/questions/2896191/what-is-go-used-fore

`}, + {"https://username:password@gitea.com", `

https://username:password@gitea.com

`}, + {"ftp://gitea.com/file.txt", `

ftp://gitea.com/file.txt

`}, + {"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download", `

magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download

`}, + {`[link](https://example.com)`, `

link

`}, + {`[link](mailto:test@example.com)`, `

link

`}, + {`[link](javascript:xss)`, `

link

`}, - // Test that should *not* be turned into URL - test( - "www.example.com", - `

www.example.com

`) - test( - "example.com", - `

example.com

`) - test( - "test.example.com", - `

test.example.com

`) - test( - "http://", - `

http://

`) - test( - "https://", - `

https://

`) - test( - "://", - `

://

`) - test( - "www", - `

www

`) - test( - "ftps://gitea.com", - `

ftps://gitea.com

`) + // Test that should *not* be turned into URL + {"www.example.com", `

www.example.com

`}, + {"example.com", `

example.com

`}, + {"test.example.com", `

test.example.com

`}, + {"http://", `

http://

`}, + {"https://", `

https://

`}, + {"://", `

://

`}, + {"www", `

www

`}, + {"ftps://gitea.com", `

ftps://gitea.com

`}, + } { + test(testCase.input, testCase.expected) + } t.Run("LinkEllipsis", func(t *testing.T) { input := util.EllipsisDisplayString("http://10.1.2.3", 12) @@ -244,73 +196,41 @@ func TestRender_links(t *testing.T) { func TestRender_email(t *testing.T) { setting.AppURL = markup.TestAppURL defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() - test := func(input, expected string) { - res, err := testRenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res), "input: %s", input) - } + for _, testCase := range []struct { + input, expected string + }{ + // Text that should be turned into email link + {"info@gitea.com", `

info@gitea.com

`}, + {"(info@gitea.com)", `

(info@gitea.com)

`}, + {"[info@gitea.com]", `

[info@gitea.com]

`}, + {"info@gitea.com.", `

info@gitea.com.

`}, + {"firstname+lastname@gitea.com", `

firstname+lastname@gitea.com

`}, + {"send email to info@gitea.co.uk.", `

send email to info@gitea.co.uk.

`}, - // Text that should be turned into email link - test( - "info@gitea.com", - `

info@gitea.com

`) - test( - "(info@gitea.com)", - `

(info@gitea.com)

`) - test( - "[info@gitea.com]", - `

[info@gitea.com]

`) - test( - "info@gitea.com.", - `

info@gitea.com.

`) - test( - "firstname+lastname@gitea.com", - `

firstname+lastname@gitea.com

`) - test( - "send email to info@gitea.co.uk.", - `

send email to info@gitea.co.uk.

`) - - test( - `j.doe@example.com, + {`j.doe@example.com, j.doe@example.com. j.doe@example.com; j.doe@example.com? - j.doe@example.com!`, - `

j.doe@example.com, + j.doe@example.com!`, `

j.doe@example.com, j.doe@example.com. j.doe@example.com; j.doe@example.com? -j.doe@example.com!

`) +j.doe@example.com!

`}, - // match GitHub behavior - test("email@domain@domain.com", `

email@domain@domain.com

`) + // match GitHub behavior + {"email@domain@domain.com", `

email@domain@domain.com

`}, - // match GitHub behavior - test(`"info@gitea.com"`, `

"info@gitea.com"

`) + // match GitHub behavior + {`"info@gitea.com"`, `

"info@gitea.com"

`}, - // Test that should *not* be turned into email links - test( - "/home/gitea/mailstore/info@gitea/com", - `

/home/gitea/mailstore/info@gitea/com

`) - test( - "git@try.gitea.io:go-gitea/gitea.git", - `

git@try.gitea.io:go-gitea/gitea.git

`) - test( - "https://foo:bar@gitea.io", - `

https://foo:bar@gitea.io

`) - test( - "gitea@3", - `

gitea@3

`) - test( - "gitea@gmail.c", - `

gitea@gmail.c

`) - test( - "email@domain..com", - `

email@domain..com

`) + // Test that should *not* be turned into email links + {"/home/gitea/mailstore/info@gitea/com", `

/home/gitea/mailstore/info@gitea/com

`}, + {"git@try.gitea.io:go-gitea/gitea.git", `

git@try.gitea.io:go-gitea/gitea.git

`}, + {"https://foo:bar@gitea.io", `

https://foo:bar@gitea.io

`}, + {"gitea@3", `

gitea@3

`}, + {"gitea@gmail.c", `

gitea@gmail.c

`}, + {"email@domain..com", `

email@domain..com

`}, - cases := []struct { - input, expected string - }{ // match GitHub behavior {"?a@d.zz", `

?a@d.zz

`}, {"*a@d.zz", `

*a@d.zz

`}, @@ -320,9 +240,10 @@ func TestRender_email(t *testing.T) { // maybe we should reduce the candidate characters for the "name" part in the future {"a*a@d.zz", `

a*a@d.zz

`}, {"a~a@d.zz", `

a~a@d.zz

`}, - } - for _, c := range cases { - test(c.input, c.expected) + } { + res, err := testRenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), testCase.input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(testCase.expected), strings.TrimSpace(res), "input: %s", testCase.input) } } @@ -394,12 +315,6 @@ func TestRender_ShortLinks(t *testing.T) { setting.AppURL = markup.TestAppURL tree := markup.TestRepoURL + "src/master" - test := func(input, expected string) { - buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), input) - assert.NoError(t, err) - assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) - } - url := tree + "/Link" otherURL := tree + "/Other-Link" encodedURL := tree + "/Link%3F" @@ -410,88 +325,36 @@ func TestRender_ShortLinks(t *testing.T) { renderableFileURL := tree + "/markdown_file.md" unrenderableFileURL := tree + "/file.zip" favicon := "http://google.com/favicon.ico" - test( - "[[Link]]", - `

Link

`, - ) - test( - "[[Link.-]]", - `

Link.-

`, - ) - test( - "[[Link.jpg]]", - `

Link.jpg

`, - ) - test( - "[["+favicon+"]]", - `

`+favicon+`

`, - ) - test( - "[[Name|Link]]", - `

Name

`, - ) - test( - "[[Name|Link.jpg]]", - `

Name

`, - ) - test( - "[[Name|Link.jpg|alt=AltName]]", - `

AltName

`, - ) - test( - "[[Name|Link.jpg|title=Title]]", - `

Title

`, - ) - test( - "[[Name|Link.jpg|alt=AltName|title=Title]]", - `

AltName

`, - ) - test( - "[[Name|Link.jpg|alt=\"AltName\"|title='Title']]", - `

AltName

`, - ) - test( - "[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]", - `

AltName

`, - ) - test( - "[[Link]] [[Other Link]]", - `

Link Other Link

`, - ) - test( - "[[Link?]]", - `

Link?

`, - ) - test( - "[[Link]] [[Other Link]] [[Link?]]", - `

Link Other Link Link?

`, - ) - test( - "[[markdown_file.md]]", - `

markdown_file.md

`, - ) - test( - "[[file.zip]]", - `

file.zip

`, - ) - test( - "[[Link #.jpg]]", - `

Link #.jpg

`, - ) - test( - "[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", - `

AltName

`, - ) - // FIXME: it's unable to resolve: [[link?k=v]] - // FIXME: it is a wrong test case, it is not an image, but a link with anchor "#.jpg" - test( - "[[some/path/Link #.jpg]]", - `

some/path/Link #.jpg

`, - ) - test( - "

[[foobar]]

", - `

[[foobar]]

`, - ) + for _, testCase := range []struct { + input, expected string + }{ + {"[[Link]]", `

Link

`}, + {"[[Link.-]]", `

Link.-

`}, + {"[[Link.jpg]]", `

Link.jpg

`}, + {"[[" + favicon + "]]", `

` + favicon + `

`}, + {"[[Name|Link]]", `

Name

`}, + {"[[Name|Link.jpg]]", `

Name

`}, + {"[[Name|Link.jpg|alt=AltName]]", `

AltName

`}, + {"[[Name|Link.jpg|title=Title]]", `

Title

`}, + {"[[Name|Link.jpg|alt=AltName|title=Title]]", `

AltName

`}, + {"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`}, + {"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`}, + {"[[Link]] [[Other Link]]", `

Link Other Link

`}, + {"[[Link?]]", `

Link?

`}, + {"[[Link]] [[Other Link]] [[Link?]]", `

Link Other Link Link?

`}, + {"[[markdown_file.md]]", `

markdown_file.md

`}, + {"[[file.zip]]", `

file.zip

`}, + {"[[Link #.jpg]]", `

Link #.jpg

`}, + {"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`}, + // FIXME: it's unable to resolve: [[link?k=v]] + // FIXME: it is a wrong test case, it is not an image, but a link with anchor "#.jpg" + {"[[some/path/Link #.jpg]]", `

some/path/Link #.jpg

`}, + {"

[[foobar]]

", `

[[foobar]]

`}, + } { + buffer, err := markdown.RenderString(markup.NewTestRenderContext(tree), testCase.input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(testCase.expected), strings.TrimSpace(string(buffer))) + } } func Test_ParseClusterFuzz(t *testing.T) { @@ -519,45 +382,36 @@ func TestPostProcess(t *testing.T) { setting.StaticURLPrefix = strings.TrimSuffix(markup.TestAppURL, "/") // can't run standalone defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() - test := func(input, expected string) { + for _, testCase := range []struct { + input, expected string + }{ + // Issue index shouldn't be post-processing in a document. + {"#1", "#1"}, + + // But cross-referenced issue index should work. + {"go-gitea/gitea#12345", `go-gitea/gitea#12345`}, + + // Test that other post-processing still works. + {":gitea:", `:gitea:`}, + {"Some text with 😄 in the middle", `Some text with 😄 in the middle`}, + {"http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`}, + {":gitea: go-gitea/gitea#12345", ":gitea: go-gitea/gitea#12345"}, + + // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible + {"", `<script>a</script>`}, + {"", `<style>a</STYLE>`}, + + // other special tags, our special behavior + {"go-gitea/gitea#12345`) - - // Test that other post-processing still works. - test( - ":gitea:", - `:gitea:`) - test( - "Some text with 😄 in the middle", - `Some text with 😄 in the middle`) - test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", - `person/repo#4 (comment)`) - test( - ":gitea: go-gitea/gitea#12345", - ":gitea: go-gitea/gitea#12345") - - // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible - test("", `<script>a</script>`) - test("", `<style>a</STYLE>`) - - // other special tags, our special behavior - test("`) - _, _ = io.WriteString(in, `any`) - _ = in.Close() - assert.Equal(t, `any`, out.String()) - }) - - t.Run("HeadNotExists", func(t *testing.T) { - out := &bytes.Buffer{} - var r RenderInternal - in := r.init("sec", out, ``) - _, _ = io.WriteString(in, `
`) - _ = in.Close() - assert.Equal(t, `
`, out.String()) - }) - - t.Run("NotHTML", func(t *testing.T) { - out := &bytes.Buffer{} - var r RenderInternal - in := r.init("sec", out, ``) - _, _ = io.WriteString(in, ``) - _ = in.Close() - assert.Equal(t, ``, out.String()) - }) + for _, test := range []struct { + name, input, expected string + }{ + {"HeadExists", `any`, `any`}, + {"HeadNotExists", `
`, `
`}, + {"NotHTML", ``, ``}, + } { + t.Run(test.name, func(t *testing.T) { + out := &bytes.Buffer{} + var r RenderInternal + in := r.init("sec", out, ``) + _, _ = io.WriteString(in, test.input) + _ = in.Close() + assert.Equal(t, test.expected, out.String()) + }) + } } diff --git a/modules/markup/jupyter/jupyter_test.go b/modules/markup/jupyter/jupyter_test.go index 69b69a54e9a..a6d4d65412a 100644 --- a/modules/markup/jupyter/jupyter_test.go +++ b/modules/markup/jupyter/jupyter_test.go @@ -16,6 +16,12 @@ import ( func TestRender(t *testing.T) { r := renderer{} + render := func(t *testing.T, ctx *markup.RenderContext, input string) string { + var output strings.Builder + err := r.Render(ctx, strings.NewReader(input), &output) + assert.NoError(t, err) + return output.String() + } t.Run("Basic notebook", func(t *testing.T) { input := `{ @@ -37,12 +43,7 @@ func TestRender(t *testing.T) { "nbformat": 4 }` - var output strings.Builder - ctx := &markup.RenderContext{} - err := r.Render(ctx, strings.NewReader(input), &output) - - assert.NoError(t, err) - result := output.String() + result := render(t, &markup.RenderContext{}, input) assert.Contains(t, result, `
`) assert.Contains(t, result, `
`) assert.Contains(t, result, `In [1]:`) @@ -68,12 +69,7 @@ func TestRender(t *testing.T) { "nbformat": 4 }` - var output strings.Builder - ctx := markup.NewRenderContext(t.Context()) - err := r.Render(ctx, strings.NewReader(input), &output) - - assert.NoError(t, err) - result := output.String() + result := render(t, markup.NewRenderContext(t.Context()), input) // Assert normal markup still renders correctly assert.Contains(t, result, `
`) @@ -94,12 +90,7 @@ func TestRender(t *testing.T) { } input := fmt.Sprintf(`{"cells": [%s], "metadata": {}, "nbformat": 4}`, strings.Join(cellBlocks, ",")) - var output strings.Builder - ctx := markup.NewRenderContext(t.Context()) - err := r.Render(ctx, strings.NewReader(input), &output) - - assert.NoError(t, err) - result := output.String() + result := render(t, markup.NewRenderContext(t.Context()), input) // Verify it halts rendering gracefully and shows the truncation warning assert.Contains(t, result, "Output truncated.") @@ -130,12 +121,7 @@ func TestRender(t *testing.T) { "nbformat": 4 }` - var output strings.Builder - ctx := markup.NewRenderContext(t.Context()) - err := r.Render(ctx, strings.NewReader(input), &output) - - assert.NoError(t, err) - result := output.String() + result := render(t, markup.NewRenderContext(t.Context()), input) assert.Contains(t, result, ``) assert.Contains(t, result, `
1
`) assert.Contains(t, result, `1`) @@ -193,12 +174,7 @@ func TestRender(t *testing.T) { "nbformat": 4 }` - var output strings.Builder - ctx := markup.NewRenderContext(t.Context()) - err := r.Render(ctx, strings.NewReader(input), &output) - - assert.NoError(t, err) - result := output.String() + result := render(t, markup.NewRenderContext(t.Context()), input) assert.Contains(t, result, `ValueError: test error`) assert.Contains(t, result, `cell-output-error`) }) @@ -210,12 +186,8 @@ func TestRender(t *testing.T) { "nbformat": 3 }` - var output strings.Builder - ctx := markup.NewRenderContext(t.Context()) - err := r.Render(ctx, strings.NewReader(input), &output) - - assert.NoError(t, err) - assert.Regexp(t, `
This notebook uses an older format.*
`, output.String()) + result := render(t, markup.NewRenderContext(t.Context()), input) + assert.Regexp(t, `
This notebook uses an older format.*
`, result) }) } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 2af0d41b242..259613b12c5 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -58,17 +58,6 @@ func TestRender_Images(t *testing.T) { result := baseLink + "/.images/src/02/train.jpg" // resolved link should not go out of the base link // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now - render( - "!["+title+"]("+url+")", - `

`+title+`

`) - - render( - "[["+title+"|"+url+"]]", - `

`+title+`

`) - render( - "[!["+title+"]("+url+")]("+href+")", - `

`+title+`

`) - render( "!["+title+"]("+url+")", `

`+title+`

`) diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go index 283d289d483..f6cd9da161b 100644 --- a/modules/markup/markdown/meta_test.go +++ b/modules/markup/markdown/meta_test.go @@ -26,67 +26,46 @@ func (it *IssueTemplate) Valid() bool { } func TestExtractMetadata(t *testing.T) { - t.Run("ValidFrontAndBody", func(t *testing.T) { - var meta IssueTemplate - body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta) - assert.NoError(t, err) - assert.Equal(t, bodyTest, body) - assert.Equal(t, metaTest, meta) - assert.True(t, meta.Valid()) - }) + for _, mode := range []string{"String", "Bytes"} { + t.Run(mode, func(t *testing.T) { + extract := func(format string, args ...any) (string, IssueTemplate, error) { + var meta IssueTemplate + if mode == "String" { + body, err := ExtractMetadata(fmt.Sprintf(format, args...), &meta) + return body, meta, err + } else { + body, err := ExtractMetadataBytes(fmt.Appendf(nil, format, args...), &meta) + return string(body), meta, err + } + } - t.Run("NoFirstSeparator", func(t *testing.T) { - var meta IssueTemplate - _, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta) - assert.Error(t, err) - }) + t.Run("ValidFrontAndBody", func(t *testing.T) { + body, meta, err := extract("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest) + assert.NoError(t, err) + assert.Equal(t, bodyTest, body) + assert.Equal(t, metaTest, meta) + assert.True(t, meta.Valid()) + }) - t.Run("NoLastSeparator", func(t *testing.T) { - var meta IssueTemplate - _, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta) - assert.Error(t, err) - }) + t.Run("NoFirstSeparator", func(t *testing.T) { + _, _, err := extract("%s\n%s\n%s", frontTest, sepTest, bodyTest) + assert.Error(t, err) + }) - t.Run("NoBody", func(t *testing.T) { - var meta IssueTemplate - body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta) - assert.NoError(t, err) - assert.Empty(t, body) - assert.Equal(t, metaTest, meta) - assert.True(t, meta.Valid()) - }) -} + t.Run("NoLastSeparator", func(t *testing.T) { + _, _, err := extract("%s\n%s\n%s", sepTest, frontTest, bodyTest) + assert.Error(t, err) + }) -func TestExtractMetadataBytes(t *testing.T) { - t.Run("ValidFrontAndBody", func(t *testing.T) { - var meta IssueTemplate - body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta) - assert.NoError(t, err) - assert.Equal(t, bodyTest, string(body)) - assert.Equal(t, metaTest, meta) - assert.True(t, meta.Valid()) - }) - - t.Run("NoFirstSeparator", func(t *testing.T) { - var meta IssueTemplate - _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta) - assert.Error(t, err) - }) - - t.Run("NoLastSeparator", func(t *testing.T) { - var meta IssueTemplate - _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta) - assert.Error(t, err) - }) - - t.Run("NoBody", func(t *testing.T) { - var meta IssueTemplate - body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, sepTest), &meta) - assert.NoError(t, err) - assert.Empty(t, string(body)) - assert.Equal(t, metaTest, meta) - assert.True(t, meta.Valid()) - }) + t.Run("NoBody", func(t *testing.T) { + body, meta, err := extract("%s\n%s\n%s", sepTest, frontTest, sepTest) + assert.NoError(t, err) + assert.Empty(t, body) + assert.Equal(t, metaTest, meta) + assert.True(t, meta.Valid()) + }) + }) + } } var ( diff --git a/modules/markup/render_test.go b/modules/markup/render_test.go index abbfff85d12..5423036d174 100644 --- a/modules/markup/render_test.go +++ b/modules/markup/render_test.go @@ -12,17 +12,12 @@ import ( ) func TestRenderIFrame(t *testing.T) { - render := func(ctx *RenderContext, opts ExternalRendererOptions) string { - sb := &strings.Builder{} - require.NoError(t, RenderIFrame(ctx, &opts, sb)) - return sb.String() - } - ctx := NewRenderContext(t.Context()). WithRelativePath("tree-path"). WithMetas(map[string]string{"user": "test-owner", "repo": "test-repo", "RefTypeNameSubURL": "src/branch/master"}) // iframe doesn't need sandbox, the sandbox is set in render's response header - ret := render(ctx, ExternalRendererOptions{ContentSandbox: "any"}) - assert.Equal(t, ``, ret) + sb := &strings.Builder{} + require.NoError(t, RenderIFrame(ctx, &ExternalRendererOptions{ContentSandbox: "any"}, sb)) + assert.Equal(t, ``, sb.String()) } diff --git a/modules/nosql/manager_redis_test.go b/modules/nosql/manager_redis_test.go index d979ea07f6f..6a58518bd98 100644 --- a/modules/nosql/manager_redis_test.go +++ b/modules/nosql/manager_redis_test.go @@ -8,74 +8,26 @@ import ( "testing" ) -func TestRedisUsernameOpt(t *testing.T) { - uri, _ := url.Parse("redis://redis:password@myredis/0") - opts := getRedisOptions(uri) - - if opts.Username != "redis" { - t.Fail() - } -} - -func TestRedisPasswordOpt(t *testing.T) { - uri, _ := url.Parse("redis://redis:password@myredis/0") - opts := getRedisOptions(uri) - - if opts.Password != "password" { - t.Fail() - } -} - -func TestSkipVerifyOpt(t *testing.T) { - uri, _ := url.Parse("rediss://myredis/0?skipverify=true") - tlsConfig := getRedisTLSOptions(uri) - - if !tlsConfig.InsecureSkipVerify { - t.Fail() - } -} - -func TestInsecureSkipVerifyOpt(t *testing.T) { - uri, _ := url.Parse("rediss://myredis/0?insecureskipverify=true") - tlsConfig := getRedisTLSOptions(uri) - - if !tlsConfig.InsecureSkipVerify { - t.Fail() - } -} - -func TestRedisSentinelUsernameOpt(t *testing.T) { - uri, _ := url.Parse("redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass") - opts := getRedisOptions(uri).Failover() - - if opts.SentinelUsername != "suser" { - t.Fail() - } -} - -func TestRedisSentinelPasswordOpt(t *testing.T) { - uri, _ := url.Parse("redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass") - opts := getRedisOptions(uri).Failover() - - if opts.SentinelPassword != "spass" { - t.Fail() - } -} - -func TestRedisDatabaseIndexTcp(t *testing.T) { - uri, _ := url.Parse("redis://redis:password@myredis/12") - opts := getRedisOptions(uri) - - if opts.DB != 12 { - t.Fail() - } -} - -func TestRedisDatabaseIndexUnix(t *testing.T) { - uri, _ := url.Parse("redis+socket:///var/run/redis.sock?database=12") - opts := getRedisOptions(uri) - - if opts.DB != 12 { - t.Fail() +func TestRedisOptions(t *testing.T) { + for _, test := range []struct { + name string + connection string + valid func(*url.URL) bool + }{ + {"username", "redis://redis:password@myredis/0", func(uri *url.URL) bool { return getRedisOptions(uri).Username == "redis" }}, + {"password", "redis://redis:password@myredis/0", func(uri *url.URL) bool { return getRedisOptions(uri).Password == "password" }}, + {"skipverify", "rediss://myredis/0?skipverify=true", func(uri *url.URL) bool { return getRedisTLSOptions(uri).InsecureSkipVerify }}, + {"insecureskipverify", "rediss://myredis/0?insecureskipverify=true", func(uri *url.URL) bool { return getRedisTLSOptions(uri).InsecureSkipVerify }}, + {"sentinel username", "redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass", func(uri *url.URL) bool { return getRedisOptions(uri).Failover().SentinelUsername == "suser" }}, + {"sentinel password", "redis+sentinel://redis:password@myredis/0?sentinelusername=suser&sentinelpassword=spass", func(uri *url.URL) bool { return getRedisOptions(uri).Failover().SentinelPassword == "spass" }}, + {"tcp database", "redis://redis:password@myredis/12", func(uri *url.URL) bool { return getRedisOptions(uri).DB == 12 }}, + {"unix database", "redis+socket:///var/run/redis.sock?database=12", func(uri *url.URL) bool { return getRedisOptions(uri).DB == 12 }}, + } { + t.Run(test.name, func(t *testing.T) { + uri, _ := url.Parse(test.connection) + if !test.valid(uri) { + t.Fail() + } + }) } } diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go index e65c53a5dbe..44bd2aac8eb 100644 --- a/modules/optional/serialization_test.go +++ b/modules/optional/serialization_test.go @@ -37,16 +37,22 @@ type testSerializationNullPointer struct { OptNull optional.Option[*time.Time] `json:"opt_null,omitempty" yaml:"opt_null,omitempty"` } -func TestOptionalToJson(t *testing.T) { +func TestOptionalToJsonAndYaml(t *testing.T) { tests := []struct { - name string - obj *testSerializationStruct - want string + name string + obj *testSerializationStruct + want string + wantYaml string }{ { name: "empty", obj: new(testSerializationStruct), want: `{"normal_string":"","normal_bool":false,"optional_two_bool":null,"optional_two_string":null}`, + wantYaml: `normal_string: "" +normal_bool: false +optional_two_bool: null +optional_two_string: null +`, }, { name: "some", @@ -59,6 +65,13 @@ func TestOptionalToJson(t *testing.T) { OptTwoString: optional.None[string](), }, want: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`, + wantYaml: `normal_string: a string +normal_bool: true +optional_bool: false +optional_string: "" +optional_two_bool: null +optional_two_string: null +`, }, } for _, tc := range tests { @@ -70,6 +83,10 @@ func TestOptionalToJson(t *testing.T) { b, err = std_json.Marshal(tc.obj) assert.NoError(t, err) assert.Equal(t, tc.want, string(b), "std json module returned unexpected") + + b, err = yaml.Marshal(tc.obj) + assert.NoError(t, err) + assert.Equal(t, tc.wantYaml, string(b), "yaml module returned unexpected") }) } @@ -100,11 +117,12 @@ func TestOptionalToJson(t *testing.T) { }) } -func TestOptionalFromJson(t *testing.T) { +func TestOptionalFromJsonAndYaml(t *testing.T) { tests := []struct { - name string - data string - want testSerializationStruct + name string + data string + dataYaml string + want testSerializationStruct }{ { name: "empty", @@ -116,94 +134,7 @@ func TestOptionalFromJson(t *testing.T) { { name: "some", data: `{"normal_string":"a string","normal_bool":true,"optional_bool":false,"optional_string":"","optional_two_bool":null,"optional_two_string":null}`, - want: testSerializationStruct{ - NormalString: "a string", - NormalBool: true, - OptBool: optional.Some(false), - OptString: optional.Some(""), - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - var obj1 testSerializationStruct - err := json.Unmarshal([]byte(tc.data), &obj1) - assert.NoError(t, err) - assert.Equal(t, tc.want, obj1, "gitea json module returned unexpected") - - var obj2 testSerializationStruct - err = std_json.Unmarshal([]byte(tc.data), &obj2) - assert.NoError(t, err) - assert.Equal(t, tc.want, obj2, "std json module returned unexpected") - }) - } -} - -func TestOptionalToYaml(t *testing.T) { - tests := []struct { - name string - obj *testSerializationStruct - want string - }{ - { - name: "empty", - obj: new(testSerializationStruct), - want: `normal_string: "" -normal_bool: false -optional_two_bool: null -optional_two_string: null -`, - }, - { - name: "some", - obj: &testSerializationStruct{ - NormalString: "a string", - NormalBool: true, - OptBool: optional.Some(false), - OptString: optional.Some(""), - }, - want: `normal_string: a string -normal_bool: true -optional_bool: false -optional_string: "" -optional_two_bool: null -optional_two_string: null -`, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - b, err := yaml.Marshal(tc.obj) - assert.NoError(t, err) - assert.Equal(t, tc.want, string(b), "yaml module returned unexpected") - }) - } -} - -func TestOptionalFromYaml(t *testing.T) { - tests := []struct { - name string - data string - want testSerializationStruct - }{ - { - name: "empty", - data: ``, - want: testSerializationStruct{}, - }, - { - name: "empty but init", - data: `normal_string: "" -normal_bool: false -optional_bool: -optional_two_bool: -optional_two_string: -`, - want: testSerializationStruct{}, - }, - { - name: "some", - data: ` + dataYaml: ` normal_string: a string normal_bool: true optional_bool: false @@ -221,10 +152,31 @@ optional_two_string: null } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var obj testSerializationStruct - err := yaml.Unmarshal([]byte(tc.data), &obj) - assert.NoError(t, err) - assert.Equal(t, tc.want, obj, "yaml module returned unexpected") + for _, format := range []struct { + data string + unmarshal func([]byte, any) error + message string + }{ + {tc.data, json.Unmarshal, "gitea json module returned unexpected"}, + {tc.data, std_json.Unmarshal, "std json module returned unexpected"}, + {tc.dataYaml, yaml.Unmarshal, "yaml module returned unexpected"}, + } { + var obj testSerializationStruct + err := format.unmarshal([]byte(format.data), &obj) + assert.NoError(t, err) + assert.Equal(t, tc.want, obj, format.message) + } }) } + t.Run("empty but init", func(t *testing.T) { + var obj testSerializationStruct + err := yaml.Unmarshal([]byte(`normal_string: "" +normal_bool: false +optional_bool: +optional_two_bool: +optional_two_string: +`), &obj) + assert.NoError(t, err) + assert.Equal(t, testSerializationStruct{}, obj, "yaml module returned unexpected") + }) } diff --git a/modules/packages/chef/metadata_test.go b/modules/packages/chef/metadata_test.go index 6def4162a9d..ec7f2984125 100644 --- a/modules/packages/chef/metadata_test.go +++ b/modules/packages/chef/metadata_test.go @@ -22,43 +22,37 @@ const ( ) func TestParsePackage(t *testing.T) { - t.Run("MissingMetadataFile", func(t *testing.T) { - var buf bytes.Buffer - zw := gzip.NewWriter(&buf) - tw := tar.NewWriter(zw) - tw.Close() - zw.Close() + for _, name := range []string{"MissingMetadataFile", "Valid"} { + t.Run(name, func(t *testing.T) { + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + tw := tar.NewWriter(zw) + if name == "Valid" { + content := `{"name":"` + packageName + `","version":"` + packageVersion + `"}` + hdr := &tar.Header{ + Name: packageName + "/metadata.json", + Mode: 0o600, + Size: int64(len(content)), + } + tw.WriteHeader(hdr) + tw.Write([]byte(content)) + } + tw.Close() + zw.Close() - p, err := ParsePackage(&buf) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrMissingMetadataFile) - }) - - t.Run("Valid", func(t *testing.T) { - var buf bytes.Buffer - zw := gzip.NewWriter(&buf) - tw := tar.NewWriter(zw) - - content := `{"name":"` + packageName + `","version":"` + packageVersion + `"}` - - hdr := &tar.Header{ - Name: packageName + "/metadata.json", - Mode: 0o600, - Size: int64(len(content)), - } - tw.WriteHeader(hdr) - tw.Write([]byte(content)) - - tw.Close() - zw.Close() - - p, err := ParsePackage(&buf) - assert.NoError(t, err) - assert.NotNil(t, p) - assert.Equal(t, packageName, p.Name) - assert.Equal(t, packageVersion, p.Version) - assert.NotNil(t, p.Metadata) - }) + p, err := ParsePackage(&buf) + if name == "MissingMetadataFile" { + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrMissingMetadataFile) + } else { + assert.NoError(t, err) + assert.NotNil(t, p) + assert.Equal(t, packageName, p.Name) + assert.Equal(t, packageVersion, p.Version) + assert.NotNil(t, p.Metadata) + } + }) + } } func TestParseChefMetadata(t *testing.T) { diff --git a/modules/packages/composer/metadata_test.go b/modules/packages/composer/metadata_test.go index ca9abac3859..c6cb196bf31 100644 --- a/modules/packages/composer/metadata_test.go +++ b/modules/packages/composer/metadata_test.go @@ -75,45 +75,28 @@ func TestCommentsUnmarshal(t *testing.T) { } func TestParsePackage(t *testing.T) { - t.Run("MissingComposerFile", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{"dummy.txt": ""}).Bytes() + for _, tc := range []struct { + name, filename, content string + expectedError error + }{ + {"MissingComposerFile", "dummy.txt", "", ErrMissingComposerFile}, + {"MissingComposerFileInRoot", "sub/sub/composer.json", "", ErrMissingComposerFile}, + {"InvalidComposerFile", "composer.json", "", nil}, + {"InvalidPackageName", "composer.json", "{}", ErrInvalidName}, + {"InvalidPackageVersion", "composer.json", `{"name": "gitea/composer-package", "version": "1.a.3"}`, ErrInvalidVersion}, + } { + t.Run(tc.name, func(t *testing.T) { + data := test.WriteZipArchive(map[string]string{tc.filename: tc.content}).Bytes() - cp, err := ParsePackage(bytes.NewReader(data)) - assert.Nil(t, cp) - assert.ErrorIs(t, err, ErrMissingComposerFile) - }) - - t.Run("MissingComposerFileInRoot", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{"sub/sub/composer.json": ""}).Bytes() - - cp, err := ParsePackage(bytes.NewReader(data)) - assert.Nil(t, cp) - assert.ErrorIs(t, err, ErrMissingComposerFile) - }) - - t.Run("InvalidComposerFile", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{"composer.json": ""}).Bytes() - - cp, err := ParsePackage(bytes.NewReader(data)) - assert.Nil(t, cp) - assert.Error(t, err) - }) - - t.Run("InvalidPackageName", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{"composer.json": "{}"}).Bytes() - - cp, err := ParsePackage(bytes.NewReader(data)) - assert.Nil(t, cp) - assert.ErrorIs(t, err, ErrInvalidName) - }) - - t.Run("InvalidPackageVersion", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "version": "1.a.3"}`}).Bytes() - - cp, err := ParsePackage(bytes.NewReader(data)) - assert.Nil(t, cp) - assert.ErrorIs(t, err, ErrInvalidVersion) - }) + cp, err := ParsePackage(bytes.NewReader(data)) + assert.Nil(t, cp) + if tc.expectedError == nil { + assert.Error(t, err) + } else { + assert.ErrorIs(t, err, tc.expectedError) + } + }) + } t.Run("InvalidReadmePath", func(t *testing.T) { data := test.WriteZipArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "readme": "sub/README.md"}`}).Bytes() diff --git a/modules/packages/conan/reference_test.go b/modules/packages/conan/reference_test.go index 6ea86eb0ddc..8b45e61ebe5 100644 --- a/modules/packages/conan/reference_test.go +++ b/modules/packages/conan/reference_test.go @@ -49,45 +49,25 @@ func TestNewRecipeReference(t *testing.T) { } func TestRecipeReferenceRevisionOrDefault(t *testing.T) { - rref, err := NewRecipeReference("name", "1.0", "", "", "") - assert.NoError(t, err) - assert.Equal(t, DefaultRevision, rref.RevisionOrDefault()) - - rref, err = NewRecipeReference("name", "1.0", "", "", DefaultRevision) - assert.NoError(t, err) - assert.Equal(t, DefaultRevision, rref.RevisionOrDefault()) - - rref, err = NewRecipeReference("name", "1.0", "", "", "Az09") - assert.NoError(t, err) - assert.Equal(t, "Az09", rref.RevisionOrDefault()) -} - -func TestRecipeReferenceString(t *testing.T) { - rref, err := NewRecipeReference("name", "1.0", "", "", "") - assert.NoError(t, err) - assert.Equal(t, "name/1.0", rref.String()) - - rref, err = NewRecipeReference("name", "1.0", "user", "channel", "") - assert.NoError(t, err) - assert.Equal(t, "name/1.0@user/channel", rref.String()) - - rref, err = NewRecipeReference("name", "1.0", "user", "channel", "Az09") - assert.NoError(t, err) - assert.Equal(t, "name/1.0@user/channel#Az09", rref.String()) -} - -func TestRecipeReferenceLinkName(t *testing.T) { - rref, err := NewRecipeReference("name", "1.0", "", "", "") - assert.NoError(t, err) - assert.Equal(t, "name/1.0/_/_/0", rref.LinkName()) - - rref, err = NewRecipeReference("name", "1.0", "user", "channel", "") - assert.NoError(t, err) - assert.Equal(t, "name/1.0/user/channel/0", rref.LinkName()) - - rref, err = NewRecipeReference("name", "1.0", "user", "channel", "Az09") - assert.NoError(t, err) - assert.Equal(t, "name/1.0/user/channel/Az09", rref.LinkName()) + for _, test := range []struct { + user, channel, revision string + method func(*RecipeReference) string + expected string + }{ + {"", "", "", (*RecipeReference).RevisionOrDefault, DefaultRevision}, + {"", "", DefaultRevision, (*RecipeReference).RevisionOrDefault, DefaultRevision}, + {"", "", "Az09", (*RecipeReference).RevisionOrDefault, "Az09"}, + {"", "", "", (*RecipeReference).String, "name/1.0"}, + {"user", "channel", "", (*RecipeReference).String, "name/1.0@user/channel"}, + {"user", "channel", "Az09", (*RecipeReference).String, "name/1.0@user/channel#Az09"}, + {"", "", "", (*RecipeReference).LinkName, "name/1.0/_/_/0"}, + {"user", "channel", "", (*RecipeReference).LinkName, "name/1.0/user/channel/0"}, + {"user", "channel", "Az09", (*RecipeReference).LinkName, "name/1.0/user/channel/Az09"}, + } { + rref, err := NewRecipeReference("name", "1.0", test.user, test.channel, test.revision) + assert.NoError(t, err) + assert.Equal(t, test.expected, test.method(rref)) + } } func TestNewPackageReference(t *testing.T) { @@ -121,27 +101,19 @@ func TestNewPackageReference(t *testing.T) { func TestPackageReferenceRevisionOrDefault(t *testing.T) { rref, _ := NewRecipeReference("name", "1.0", "", "", "") - pref, err := NewPackageReference(rref, "ref", "") - assert.NoError(t, err) - assert.Equal(t, DefaultRevision, pref.RevisionOrDefault()) - - pref, err = NewPackageReference(rref, "ref", DefaultRevision) - assert.NoError(t, err) - assert.Equal(t, DefaultRevision, pref.RevisionOrDefault()) - - pref, err = NewPackageReference(rref, "ref", "Az09") - assert.NoError(t, err) - assert.Equal(t, "Az09", pref.RevisionOrDefault()) -} - -func TestPackageReferenceLinkName(t *testing.T) { - rref, _ := NewRecipeReference("name", "1.0", "", "", "") - - pref, err := NewPackageReference(rref, "ref", "") - assert.NoError(t, err) - assert.Equal(t, "ref/0", pref.LinkName()) - - pref, err = NewPackageReference(rref, "ref", "Az09") - assert.NoError(t, err) - assert.Equal(t, "ref/Az09", pref.LinkName()) + for _, test := range []struct { + revision string + method func(*PackageReference) string + expected string + }{ + {"", (*PackageReference).RevisionOrDefault, DefaultRevision}, + {DefaultRevision, (*PackageReference).RevisionOrDefault, DefaultRevision}, + {"Az09", (*PackageReference).RevisionOrDefault, "Az09"}, + {"", (*PackageReference).LinkName, "ref/0"}, + {"Az09", (*PackageReference).LinkName, "ref/Az09"}, + } { + pref, err := NewPackageReference(rref, "ref", test.revision) + assert.NoError(t, err) + assert.Equal(t, test.expected, test.method(pref)) + } } diff --git a/modules/packages/cran/metadata_test.go b/modules/packages/cran/metadata_test.go index c7b09ba4670..413044594b2 100644 --- a/modules/packages/cran/metadata_test.go +++ b/modules/packages/cran/metadata_test.go @@ -37,8 +37,11 @@ func createDescription(name, version string) *bytes.Buffer { } func TestParsePackage(t *testing.T) { - t.Run(".tar.gz", func(t *testing.T) { - createArchive := func(filename string, content []byte) *bytes.Reader { + for _, archive := range []struct { + extension string + create func(string, []byte) *bytes.Reader + }{ + {".tar.gz", func(filename string, content []byte) *bytes.Reader { var buf bytes.Buffer gw := gzip.NewWriter(&buf) tw := tar.NewWriter(gw) @@ -52,70 +55,43 @@ func TestParsePackage(t *testing.T) { tw.Close() gw.Close() return bytes.NewReader(buf.Bytes()) - } - - t.Run("MissingDescriptionFile", func(t *testing.T) { - buf := createArchive( - "dummy.txt", - []byte{}, - ) - - p, err := ParsePackage(buf, buf.Size()) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrMissingDescriptionFile) - }) - - t.Run("Valid", func(t *testing.T) { - buf := createArchive( - "package/DESCRIPTION", - createDescription(packageName, packageVersion).Bytes(), - ) - - p, err := ParsePackage(buf, buf.Size()) - - assert.NotNil(t, p) - assert.NoError(t, err) - - assert.Equal(t, packageName, p.Name) - assert.Equal(t, packageVersion, p.Version) - }) - }) - - t.Run(".zip", func(t *testing.T) { - createArchive := func(filename string, content []byte) *bytes.Reader { + }}, + {".zip", func(filename string, content []byte) *bytes.Reader { var buf bytes.Buffer archive := zip.NewWriter(&buf) w, _ := archive.Create(filename) w.Write(content) archive.Close() return bytes.NewReader(buf.Bytes()) - } + }}, + } { + t.Run(archive.extension, func(t *testing.T) { + t.Run("MissingDescriptionFile", func(t *testing.T) { + buf := archive.create( + "dummy.txt", + []byte{}, + ) - t.Run("MissingDescriptionFile", func(t *testing.T) { - buf := createArchive( - "dummy.txt", - []byte{}, - ) + p, err := ParsePackage(buf, buf.Size()) + assert.Nil(t, p) + assert.ErrorIs(t, err, ErrMissingDescriptionFile) + }) - p, err := ParsePackage(buf, buf.Size()) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrMissingDescriptionFile) + t.Run("Valid", func(t *testing.T) { + buf := archive.create( + "package/DESCRIPTION", + createDescription(packageName, packageVersion).Bytes(), + ) + + p, err := ParsePackage(buf, buf.Size()) + assert.NotNil(t, p) + assert.NoError(t, err) + + assert.Equal(t, packageName, p.Name) + assert.Equal(t, packageVersion, p.Version) + }) }) - - t.Run("Valid", func(t *testing.T) { - buf := createArchive( - "package/DESCRIPTION", - createDescription(packageName, packageVersion).Bytes(), - ) - - p, err := ParsePackage(buf, buf.Size()) - assert.NotNil(t, p) - assert.NoError(t, err) - - assert.Equal(t, packageName, p.Name) - assert.Equal(t, packageVersion, p.Version) - }) - }) + } } func TestParseDescription(t *testing.T) { diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go index 20d8c5f509c..603eac45d1f 100644 --- a/modules/packages/debian/metadata_test.go +++ b/modules/packages/debian/metadata_test.go @@ -110,21 +110,14 @@ func TestParsePackage(t *testing.T) { w.Write(buf.Bytes()) w.Close() - data := createArchive(map[string][]byte{"control.tar" + c.Extension: cbuf.Bytes()}) - - p, err := ParsePackage(data) - assert.NotNil(t, p) - assert.NoError(t, err) - assert.Equal(t, "gitea", p.Name) - - t.Run("TrailingSlash", func(t *testing.T) { - data := createArchive(map[string][]byte{"control.tar" + c.Extension + "/": cbuf.Bytes()}) + for _, suffix := range []string{"", "/"} { + data := createArchive(map[string][]byte{"control.tar" + c.Extension + suffix: cbuf.Bytes()}) p, err := ParsePackage(data) assert.NotNil(t, p) assert.NoError(t, err) assert.Equal(t, "gitea", p.Name) - }) + } }) } }) diff --git a/modules/packages/goproxy/metadata_test.go b/modules/packages/goproxy/metadata_test.go index 24db3596510..edf52d93dd4 100644 --- a/modules/packages/goproxy/metadata_test.go +++ b/modules/packages/goproxy/metadata_test.go @@ -28,58 +28,41 @@ func TestParsePackage(t *testing.T) { return bytes.NewReader(buf.Bytes()) } - t.Run("EmptyPackage", func(t *testing.T) { - data := createArchive(nil) - - p, err := ParsePackage(data, int64(data.Len())) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidStructure) - }) - - t.Run("InvalidNameOrVersionStructure", func(t *testing.T) { - data := createArchive(map[string][]byte{ + for _, testCase := range []struct { + name string + files map[string][]byte + expectedError error + goMod string + }{ + {"EmptyPackage", nil, ErrInvalidStructure, ""}, + {"InvalidNameOrVersionStructure", map[string][]byte{ packageName + "/" + packageVersion + "/go.mod": {}, - }) - - p, err := ParsePackage(data, int64(data.Len())) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidStructure) - }) - - t.Run("GoModFileInWrongDirectory", func(t *testing.T) { - data := createArchive(map[string][]byte{ + }, ErrInvalidStructure, ""}, + {"GoModFileInWrongDirectory", map[string][]byte{ packageName + "@" + packageVersion + "/subdir/go.mod": {}, - }) - - p, err := ParsePackage(data, int64(data.Len())) - assert.NotNil(t, p) - assert.NoError(t, err) - assert.Equal(t, packageName, p.Name) - assert.Equal(t, packageVersion, p.Version) - assert.Equal(t, "module gitea.com/go-gitea/gitea", p.GoMod) - }) - - t.Run("InvalidVersion", func(t *testing.T) { - data := createArchive(map[string][]byte{ + }, nil, "module gitea.com/go-gitea/gitea"}, + {"InvalidVersion", map[string][]byte{ packageName + "@v1.0.0\nv99.0.0/go.mod": []byte("module " + packageName), - }) - - p, err := ParsePackage(data, int64(data.Len())) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidVersion) - }) - - t.Run("Valid", func(t *testing.T) { - data := createArchive(map[string][]byte{ + }, ErrInvalidVersion, ""}, + {"Valid", map[string][]byte{ packageName + "@" + packageVersion + "/subdir/go.mod": []byte("invalid"), packageName + "@" + packageVersion + "/go.mod": []byte("valid"), - }) + }, nil, "valid"}, + } { + t.Run(testCase.name, func(t *testing.T) { + data := createArchive(testCase.files) - p, err := ParsePackage(data, int64(data.Len())) - assert.NotNil(t, p) - assert.NoError(t, err) - assert.Equal(t, packageName, p.Name) - assert.Equal(t, packageVersion, p.Version) - assert.Equal(t, "valid", p.GoMod) - }) + p, err := ParsePackage(data, int64(data.Len())) + if testCase.expectedError != nil { + assert.Nil(t, p) + assert.ErrorIs(t, err, testCase.expectedError) + } else { + assert.NotNil(t, p) + assert.NoError(t, err) + assert.Equal(t, packageName, p.Name) + assert.Equal(t, packageVersion, p.Version) + assert.Equal(t, testCase.goMod, p.GoMod) + } + }) + } } diff --git a/modules/packages/multi_hasher_test.go b/modules/packages/multi_hasher_test.go index a37debbc95f..147de22192b 100644 --- a/modules/packages/multi_hasher_test.go +++ b/modules/packages/multi_hasher_test.go @@ -18,36 +18,28 @@ const ( ) func TestMultiHasherSums(t *testing.T) { - t.Run("Sums", func(t *testing.T) { - h := NewMultiHasher() - h.Write([]byte("gitea")) + for _, content := range []string{"gitea", "git"} { + t.Run(content, func(t *testing.T) { + h := NewMultiHasher() + h.Write([]byte(content)) - hashMD5, hashSHA1, hashSHA256, hashSHA512 := h.Sums() + if content == "git" { + state, err := h.MarshalBinary() + assert.NoError(t, err) - assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) - assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) - assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) - assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) - }) + h = NewMultiHasher() + err = h.UnmarshalBinary(state) + assert.NoError(t, err) - t.Run("State", func(t *testing.T) { - h := NewMultiHasher() - h.Write([]byte("git")) + h.Write([]byte("ea")) + } - state, err := h.MarshalBinary() - assert.NoError(t, err) + hashMD5, hashSHA1, hashSHA256, hashSHA512 := h.Sums() - h2 := NewMultiHasher() - err = h2.UnmarshalBinary(state) - assert.NoError(t, err) - - h2.Write([]byte("ea")) - - hashMD5, hashSHA1, hashSHA256, hashSHA512 := h2.Sums() - - assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) - assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) - assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) - assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) - }) + assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) + assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) + assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) + assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) + }) + } } diff --git a/modules/packages/npm/creator_test.go b/modules/packages/npm/creator_test.go index 1728f5d0224..7770601f5d0 100644 --- a/modules/packages/npm/creator_test.go +++ b/modules/packages/npm/creator_test.go @@ -52,71 +52,38 @@ func TestParsePackage(t *testing.T) { assert.ErrorIs(t, err, ErrInvalidPackage) }) - t.Run("InvalidPackageName", func(t *testing.T) { - test := func(t *testing.T, name string) { - p, err := parseUploadPackage(&packageUpload{ - PackageMetadata: PackageMetadata{ - ID: name, - Name: name, - Versions: map[string]*PackageMetadataVersion{ - packageVersion: { - Name: name, + for _, testCase := range []struct { + name string + names []string + expectedError error + }{ + {"InvalidPackageName", []string{ + " test ", " test", "test ", "te st", "Test", "_test", ".test", "^test", "te^st", + "te|st", "te)(st", "te'st", "te!st", "te*st", "te~st", "invalid/scope", "@invalid/_name", "@invalid/.name", + }, ErrInvalidPackageName}, + {"ValidPackageName", []string{ + "test", "@scope/name", "@scope/q", "q", "@scope/package-name", "@scope/package.name", "@scope/package_name", + "123name", "----", packageFullName, + }, ErrInvalidPackageVersion}, + } { + t.Run(testCase.name, func(t *testing.T) { + for _, name := range testCase.names { + p, err := parseUploadPackage(&packageUpload{ + PackageMetadata: PackageMetadata{ + ID: name, + Name: name, + Versions: map[string]*PackageMetadataVersion{ + packageVersion: { + Name: name, + }, }, }, - }, - }) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidPackageName) - } - - test(t, " test ") - test(t, " test") - test(t, "test ") - test(t, "te st") - test(t, "Test") - test(t, "_test") - test(t, ".test") - test(t, "^test") - test(t, "te^st") - test(t, "te|st") - test(t, "te)(st") - test(t, "te'st") - test(t, "te!st") - test(t, "te*st") - test(t, "te~st") - test(t, "invalid/scope") - test(t, "@invalid/_name") - test(t, "@invalid/.name") - }) - - t.Run("ValidPackageName", func(t *testing.T) { - test := func(t *testing.T, name string) { - p, err := parseUploadPackage(&packageUpload{ - PackageMetadata: PackageMetadata{ - ID: name, - Name: name, - Versions: map[string]*PackageMetadataVersion{ - packageVersion: { - Name: name, - }, - }, - }, - }) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidPackageVersion) - } - - test(t, "test") - test(t, "@scope/name") - test(t, "@scope/q") - test(t, "q") - test(t, "@scope/package-name") - test(t, "@scope/package.name") - test(t, "@scope/package_name") - test(t, "123name") - test(t, "----") - test(t, packageFullName) - }) + }) + assert.Nil(t, p) + assert.ErrorIs(t, err, testCase.expectedError) + } + }) + } t.Run("InvalidPackageVersion", func(t *testing.T) { version := "first-version" @@ -136,108 +103,42 @@ func TestParsePackage(t *testing.T) { assert.ErrorIs(t, err, ErrInvalidPackageVersion) }) - t.Run("InvalidAttachment", func(t *testing.T) { - b, _ := json.Marshal(packageUpload{ - PackageMetadata: PackageMetadata{ - ID: packageFullName, - Name: packageFullName, - Versions: map[string]*PackageMetadataVersion{ - packageVersion: { - Name: packageFullName, - Version: packageVersion, - }, - }, - }, - Attachments: map[string]*PackageAttachment{ - "dummy.tgz": {}, - }, - }) - - p, _, err := ParseUpload(bytes.NewReader(b)) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidAttachment) - }) - - t.Run("InvalidData", func(t *testing.T) { - filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion) - b, _ := json.Marshal(packageUpload{ - PackageMetadata: PackageMetadata{ - ID: packageFullName, - Name: packageFullName, - Versions: map[string]*PackageMetadataVersion{ - packageVersion: { - Name: packageFullName, - Version: packageVersion, - }, - }, - }, - Attachments: map[string]*PackageAttachment{ - filename: { - Data: "/", - }, - }, - }) - - p, _, err := ParseUpload(bytes.NewReader(b)) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidAttachment) - }) - - t.Run("InvalidIntegrity", func(t *testing.T) { - filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion) - b, _ := json.Marshal(packageUpload{ - PackageMetadata: PackageMetadata{ - ID: packageFullName, - Name: packageFullName, - Versions: map[string]*PackageMetadataVersion{ - packageVersion: { - Name: packageFullName, - Version: packageVersion, - Dist: PackageDistribution{ - Integrity: "sha512-test==", + for _, testCase := range []struct { + name, filename, data, integrity string + expectedError error + }{ + {"InvalidAttachment", "dummy.tgz", "", "", ErrInvalidAttachment}, + {"InvalidData", fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion), "/", "", ErrInvalidAttachment}, + {"InvalidIntegrity", fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion), data, "sha512-test==", ErrInvalidIntegrity}, + {"InvalidIntegrity2", fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion), base64.StdEncoding.EncodeToString([]byte("data")), integrity, ErrInvalidIntegrity}, + } { + t.Run(testCase.name, func(t *testing.T) { + b, _ := json.Marshal(packageUpload{ + PackageMetadata: PackageMetadata{ + ID: packageFullName, + Name: packageFullName, + Versions: map[string]*PackageMetadataVersion{ + packageVersion: { + Name: packageFullName, + Version: packageVersion, + Dist: PackageDistribution{ + Integrity: testCase.integrity, + }, }, }, }, - }, - Attachments: map[string]*PackageAttachment{ - filename: { - Data: data, - }, - }, - }) - - p, _, err := ParseUpload(bytes.NewReader(b)) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidIntegrity) - }) - - t.Run("InvalidIntegrity2", func(t *testing.T) { - filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion) - b, _ := json.Marshal(packageUpload{ - PackageMetadata: PackageMetadata{ - ID: packageFullName, - Name: packageFullName, - Versions: map[string]*PackageMetadataVersion{ - packageVersion: { - Name: packageFullName, - Version: packageVersion, - Dist: PackageDistribution{ - Integrity: integrity, - }, + Attachments: map[string]*PackageAttachment{ + testCase.filename: { + Data: testCase.data, }, }, - }, - Attachments: map[string]*PackageAttachment{ - filename: { - Data: base64.StdEncoding.EncodeToString([]byte("data")), - }, - }, - }) + }) - p, _, err := ParseUpload(bytes.NewReader(b)) - assert.Nil(t, p) - assert.ErrorIs(t, err, ErrInvalidIntegrity) - }) + p, _, err := ParseUpload(bytes.NewReader(b)) + assert.Nil(t, p) + assert.ErrorIs(t, err, testCase.expectedError) + }) + } t.Run("Valid", func(t *testing.T) { filename := fmt.Sprintf("%s-%s.tgz", packageFullName, packageVersion) diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go index 564329b4df7..6ba81ac86d6 100644 --- a/modules/packages/nuget/metadata_test.go +++ b/modules/packages/nuget/metadata_test.go @@ -92,53 +92,37 @@ func TestParsePackageMetaData(t *testing.T) { return buf.Bytes() } - t.Run("MissingNuspecFile", func(t *testing.T) { - data := createArchive(map[string]string{"dummy.txt": ""}) - - np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) - assert.Nil(t, np) - assert.ErrorIs(t, err, ErrMissingNuspecFile) - }) - - t.Run("MissingNuspecFileInRoot", func(t *testing.T) { - data := createArchive(map[string]string{"sub/package.nuspec": ""}) - - np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) - assert.Nil(t, np) - assert.ErrorIs(t, err, ErrMissingNuspecFile) - }) - - t.Run("InvalidNuspecFile", func(t *testing.T) { - data := createArchive(map[string]string{"package.nuspec": ""}) - - np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) - assert.Nil(t, np) - assert.Error(t, err) - }) - - t.Run("InvalidPackageId", func(t *testing.T) { - data := createArchive(map[string]string{"package.nuspec": ` + for _, testCase := range []struct { + name string + files map[string]string + expectedError error + }{ + {"MissingNuspecFile", map[string]string{"dummy.txt": ""}, ErrMissingNuspecFile}, + {"MissingNuspecFileInRoot", map[string]string{"sub/package.nuspec": ""}, ErrMissingNuspecFile}, + {"InvalidNuspecFile", map[string]string{"package.nuspec": ""}, nil}, + {"InvalidPackageId", map[string]string{"package.nuspec": ` - `}) - - np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) - assert.Nil(t, np) - assert.ErrorIs(t, err, ErrNuspecInvalidID) - }) - - t.Run("InvalidPackageVersion", func(t *testing.T) { - data := createArchive(map[string]string{"package.nuspec": ` + `}, ErrNuspecInvalidID}, + {"InvalidPackageVersion", map[string]string{"package.nuspec": ` ` + id + ` - `}) + `}, ErrNuspecInvalidVersion}, + } { + t.Run(testCase.name, func(t *testing.T) { + data := createArchive(testCase.files) - np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) - assert.Nil(t, np) - assert.ErrorIs(t, err, ErrNuspecInvalidVersion) - }) + np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) + assert.Nil(t, np) + if testCase.expectedError == nil { + assert.Error(t, err) + } else { + assert.ErrorIs(t, err, testCase.expectedError) + } + }) + } t.Run("MissingReadme", func(t *testing.T) { data := createArchive(map[string]string{"package.nuspec": nuspecContent}) diff --git a/modules/packages/rpm/metadata_test.go b/modules/packages/rpm/metadata_test.go index 6ae66640434..840f3b5621a 100644 --- a/modules/packages/rpm/metadata_test.go +++ b/modules/packages/rpm/metadata_test.go @@ -110,49 +110,22 @@ OvSXaRmpzyTLHvsXg/M9EcwbAAA=` }, p.FileMetadata.Provides, ) - assert.ElementsMatch( - t, - []*Entry{ - { - Name: "/bin/sh", - }, - { - Name: "/bin/sh", - }, - { - Name: "/bin/sh", - }, - { - Name: "rpmlib(CompressedFileNames)", - Flags: "LE", - Version: "3.0.4", - Epoch: "0", - Release: "1", - }, - { - Name: "rpmlib(FileDigests)", - Flags: "LE", - Version: "4.6.0", - Epoch: "0", - Release: "1", - }, - { - Name: "rpmlib(PayloadFilesHavePrefix)", - Flags: "LE", - Version: "4.0", - Epoch: "0", - Release: "1", - }, - { - Name: "rpmlib(PayloadIsXz)", - Flags: "LE", - Version: "5.2", - Epoch: "0", - Release: "1", - }, - }, - p.FileMetadata.Requires, - ) + requires := []*Entry{{Name: "/bin/sh"}, {Name: "/bin/sh"}, {Name: "/bin/sh"}} + for _, dependency := range []struct{ name, version string }{ + {"rpmlib(CompressedFileNames)", "3.0.4"}, + {"rpmlib(FileDigests)", "4.6.0"}, + {"rpmlib(PayloadFilesHavePrefix)", "4.0"}, + {"rpmlib(PayloadIsXz)", "5.2"}, + } { + requires = append(requires, &Entry{ + Name: dependency.name, + Flags: "LE", + Version: dependency.version, + Epoch: "0", + Release: "1", + }) + } + assert.ElementsMatch(t, requires, p.FileMetadata.Requires) assert.ElementsMatch( t, []*File{ diff --git a/modules/packages/swift/metadata_test.go b/modules/packages/swift/metadata_test.go index ac77c90c1f6..71f0fa1f329 100644 --- a/modules/packages/swift/metadata_test.go +++ b/modules/packages/swift/metadata_test.go @@ -164,15 +164,15 @@ func TestParsePackage(t *testing.T) { assert.Equal(t, contentA, p.Metadata.Manifests[""].Content) }) - t.Run("WithMetadata", func(t *testing.T) { + parseWithMetadata := func(metadata string) (*Package, error) { data := test.WriteZipArchive(map[string]string{ "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift", }) + return ParsePackage(bytes.NewReader(data.Bytes()), int64(data.Len()), strings.NewReader(metadata)) + } - p, err := ParsePackage( - bytes.NewReader(data.Bytes()), int64(data.Len()), - strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","keywords":["swift","package"],"license":"`+packageLicense+`","licenseURL":"`+packageLicenseURL+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`), - ) + t.Run("WithMetadata", func(t *testing.T) { + p, err := parseWithMetadata(`{"name":"` + packageName + `","version":"` + packageVersion + `","description":"` + packageDescription + `","keywords":["swift","package"],"license":"` + packageLicense + `","licenseURL":"` + packageLicenseURL + `","codeRepository":"` + packageRepositoryURL + `","author":{"givenName":"` + packageAuthor + `"},"repositoryURLs":["` + packageRepositoryURL + `"]}`) assert.NotNil(t, p) assert.NoError(t, err) @@ -192,15 +192,8 @@ func TestParsePackage(t *testing.T) { }) t.Run("WithExplicitNameField", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{ - "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift", - }) - authorName := "John Doe" - p, err := ParsePackage( - bytes.NewReader(data.Bytes()), int64(data.Len()), - strings.NewReader(`{"name":"`+packageName+`","version":"`+packageVersion+`","description":"`+packageDescription+`","author":{"name":"`+authorName+`","givenName":"John","familyName":"Doe"}}`), - ) + p, err := parseWithMetadata(`{"name":"` + packageName + `","version":"` + packageVersion + `","description":"` + packageDescription + `","author":{"name":"` + authorName + `","givenName":"John","familyName":"Doe"}}`) assert.NotNil(t, p) assert.NoError(t, err) @@ -210,14 +203,7 @@ func TestParsePackage(t *testing.T) { }) t.Run("WithEmptyJSONMetadata", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{ - "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift", - }) - - p, err := ParsePackage( - bytes.NewReader(data.Bytes()), int64(data.Len()), - strings.NewReader(`{}`), - ) + p, err := parseWithMetadata(`{}`) assert.NotNil(t, p) assert.NoError(t, err) assert.NotNil(t, p.Metadata) @@ -226,15 +212,8 @@ func TestParsePackage(t *testing.T) { }) t.Run("NameFieldGeneration", func(t *testing.T) { - data := test.WriteZipArchive(map[string]string{ - "Package.swift": "// swift-tools-version:5.7\n//\n// Package.swift", - }) - // Test with only individual name components - Name should be auto-generated - p, err := ParsePackage( - bytes.NewReader(data.Bytes()), int64(data.Len()), - strings.NewReader(`{"author":{"givenName":"John","middleName":"Q","familyName":"Doe"}}`), - ) + p, err := parseWithMetadata(`{"author":{"givenName":"John","middleName":"Q","familyName":"Doe"}}`) assert.NotNil(t, p) assert.NoError(t, err) assert.Equal(t, "John Q Doe", p.Metadata.Author.Name) @@ -245,42 +224,16 @@ func TestParsePackage(t *testing.T) { } func TestTrimmedVersionString(t *testing.T) { - cases := []struct { - Version *version.Version + for _, c := range []struct { + Versions []string Expected string }{ - { - Version: version.Must(version.NewVersion("1")), - Expected: "1.0", - }, - { - Version: version.Must(version.NewVersion("1.0")), - Expected: "1.0", - }, - { - Version: version.Must(version.NewVersion("1.0.0")), - Expected: "1.0", - }, - { - Version: version.Must(version.NewVersion("1.0.1")), - Expected: "1.0.1", - }, - { - Version: version.Must(version.NewVersion("1.0+meta")), - Expected: "1.0", - }, - { - Version: version.Must(version.NewVersion("1.0.0+meta")), - Expected: "1.0", - }, - { - Version: version.Must(version.NewVersion("1.0.1+meta")), - Expected: "1.0.1", - }, - } - - for _, c := range cases { - assert.Equal(t, c.Expected, TrimmedVersionString(c.Version)) + {[]string{"1", "1.0", "1.0.0", "1.0+meta", "1.0.0+meta"}, "1.0"}, + {[]string{"1.0.1", "1.0.1+meta"}, "1.0.1"}, + } { + for _, input := range c.Versions { + assert.Equal(t, c.Expected, TrimmedVersionString(version.Must(version.NewVersion(input)))) + } } } diff --git a/modules/packages/vagrant/metadata_test.go b/modules/packages/vagrant/metadata_test.go index ab42c65f9f6..eac65c6d783 100644 --- a/modules/packages/vagrant/metadata_test.go +++ b/modules/packages/vagrant/metadata_test.go @@ -49,29 +49,6 @@ func TestParseMetadataFromBox(t *testing.T) { assert.NoError(t, err) }) - t.Run("Valid", func(t *testing.T) { - content, err := json.Marshal(map[string]string{ - "description": description, - "author": author, - "website": projectURL, - "repository": repositoryURL, - }) - assert.NoError(t, err) - - data := createArchive(map[string][]byte{"info.json": content}) - - metadata, err := ParseMetadataFromBox(data) - assert.NotNil(t, metadata) - assert.NoError(t, err) - - assert.Equal(t, author, metadata.Author) - assert.Equal(t, description, metadata.Description) - assert.Equal(t, projectURL, metadata.ProjectURL) - assert.Equal(t, repositoryURL, metadata.RepositoryURL) - }) -} - -func TestParseInfoFile(t *testing.T) { t.Run("UnknownKeys", func(t *testing.T) { content, err := json.Marshal(map[string]string{ "package": "", @@ -89,22 +66,31 @@ func TestParseInfoFile(t *testing.T) { assert.Empty(t, metadata.RepositoryURL) }) - t.Run("Valid", func(t *testing.T) { - content, err := json.Marshal(map[string]string{ - "description": description, - "author": author, - "website": projectURL, - "repository": repositoryURL, + for name, parse := range map[string]func([]byte) (*Metadata, error){ + "ValidBox": func(content []byte) (*Metadata, error) { + return ParseMetadataFromBox(createArchive(map[string][]byte{"info.json": content})) + }, + "ValidInfoFile": func(content []byte) (*Metadata, error) { + return ParseInfoFile(bytes.NewReader(content)) + }, + } { + t.Run(name, func(t *testing.T) { + content, err := json.Marshal(map[string]string{ + "description": description, + "author": author, + "website": projectURL, + "repository": repositoryURL, + }) + assert.NoError(t, err) + + metadata, err := parse(content) + assert.NotNil(t, metadata) + assert.NoError(t, err) + + assert.Equal(t, author, metadata.Author) + assert.Equal(t, description, metadata.Description) + assert.Equal(t, projectURL, metadata.ProjectURL) + assert.Equal(t, repositoryURL, metadata.RepositoryURL) }) - assert.NoError(t, err) - - metadata, err := ParseInfoFile(bytes.NewReader(content)) - assert.NotNil(t, metadata) - assert.NoError(t, err) - - assert.Equal(t, author, metadata.Author) - assert.Equal(t, description, metadata.Description) - assert.Equal(t, projectURL, metadata.ProjectURL) - assert.Equal(t, repositoryURL, metadata.RepositoryURL) - }) + } } diff --git a/modules/paginator/paginator_test.go b/modules/paginator/paginator_test.go index ed46ecea94c..16fa8556505 100644 --- a/modules/paginator/paginator_test.go +++ b/modules/paginator/paginator_test.go @@ -85,226 +85,71 @@ func TestPaginator(t *testing.T) { assert.True(t, pages[0].IsCurrent()) }) + assertPages := func(t *testing.T, total, current, numPages int, expected ...int) { + pages := New(total, 10, current, numPages).Pages() + assert.Len(t, pages, len(expected)) + for i, number := range expected { + assert.Equal(t, number, pages[i].Num()) + if number == current { + assert.True(t, pages[i].IsCurrent()) + } else { + assert.False(t, pages[i].IsCurrent()) + } + } + } + t.Run("Total page number is less or equal", func(t *testing.T) { - p := New(1, 10, 1, 2) - pages := p.Pages() - assert.Len(t, pages, 1) - assert.Equal(t, 1, pages[0].Num()) - assert.True(t, pages[0].IsCurrent()) + assertPages(t, 1, 1, 2, 1) - p = New(11, 10, 1, 2) - pages = p.Pages() - assert.Len(t, pages, 2) - assert.Equal(t, 1, pages[0].Num()) - assert.True(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) + assertPages(t, 11, 1, 2, 1, 2) - p = New(11, 10, 2, 2) - pages = p.Pages() - assert.Len(t, pages, 2) - assert.Equal(t, 1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) + assertPages(t, 11, 2, 2, 1, 2) - p = New(25, 10, 2, 3) - pages = p.Pages() - assert.Len(t, pages, 3) - assert.Equal(t, 1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) + assertPages(t, 25, 2, 3, 1, 2, 3) }) t.Run("Has more previous pages ", func(t *testing.T) { // ... 2 - p := New(11, 10, 2, 1) - pages := p.Pages() - assert.Len(t, pages, 2) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) + assertPages(t, 11, 2, 1, -1, 2) // ... 2 3 - p = New(21, 10, 2, 2) - pages = p.Pages() - assert.Len(t, pages, 3) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) + assertPages(t, 21, 2, 2, -1, 2, 3) // ... 2 3 4 - p = New(31, 10, 3, 3) - pages = p.Pages() - assert.Len(t, pages, 4) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.True(t, pages[2].IsCurrent()) - assert.Equal(t, 4, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) + assertPages(t, 31, 3, 3, -1, 2, 3, 4) // ... 3 4 5 - p = New(41, 10, 4, 3) - pages = p.Pages() - assert.Len(t, pages, 4) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 3, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) - assert.Equal(t, 4, pages[2].Num()) - assert.True(t, pages[2].IsCurrent()) - assert.Equal(t, 5, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) + assertPages(t, 41, 4, 3, -1, 3, 4, 5) // ... 4 5 6 7 8 9 10 - p = New(100, 10, 9, 7) - pages = p.Pages() - assert.Len(t, pages, 8) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 4, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) - assert.Equal(t, 5, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) - assert.Equal(t, 6, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) - assert.Equal(t, 7, pages[4].Num()) - assert.False(t, pages[4].IsCurrent()) - assert.Equal(t, 8, pages[5].Num()) - assert.False(t, pages[5].IsCurrent()) - assert.Equal(t, 9, pages[6].Num()) - assert.True(t, pages[6].IsCurrent()) - assert.Equal(t, 10, pages[7].Num()) - assert.False(t, pages[7].IsCurrent()) + assertPages(t, 100, 9, 7, -1, 4, 5, 6, 7, 8, 9, 10) }) t.Run("Has more next pages", func(t *testing.T) { // 1 ... - p := New(21, 10, 1, 1) - pages := p.Pages() - assert.Len(t, pages, 2) - assert.Equal(t, 1, pages[0].Num()) - assert.True(t, pages[0].IsCurrent()) - assert.Equal(t, -1, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) + assertPages(t, 21, 1, 1, 1, -1) // 1 2 ... - p = New(21, 10, 1, 2) - pages = p.Pages() - assert.Len(t, pages, 3) - assert.Equal(t, 1, pages[0].Num()) - assert.True(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) - assert.Equal(t, -1, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) + assertPages(t, 21, 1, 2, 1, 2, -1) // 1 2 3 ... - p = New(31, 10, 2, 3) - pages = p.Pages() - assert.Len(t, pages, 4) - assert.Equal(t, 1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) - assert.Equal(t, -1, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) + assertPages(t, 31, 2, 3, 1, 2, 3, -1) // 1 2 3 ... - p = New(41, 10, 2, 3) - pages = p.Pages() - assert.Len(t, pages, 4) - assert.Equal(t, 1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) - assert.Equal(t, -1, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) + assertPages(t, 41, 2, 3, 1, 2, 3, -1) // 1 2 3 4 5 6 7 ... - p = New(100, 10, 1, 7) - pages = p.Pages() - assert.Len(t, pages, 8) - assert.Equal(t, 1, pages[0].Num()) - assert.True(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) - assert.Equal(t, 4, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) - assert.Equal(t, 5, pages[4].Num()) - assert.False(t, pages[4].IsCurrent()) - assert.Equal(t, 6, pages[5].Num()) - assert.False(t, pages[5].IsCurrent()) - assert.Equal(t, 7, pages[6].Num()) - assert.False(t, pages[6].IsCurrent()) - assert.Equal(t, -1, pages[7].Num()) - assert.False(t, pages[7].IsCurrent()) + assertPages(t, 100, 1, 7, 1, 2, 3, 4, 5, 6, 7, -1) // 1 2 3 4 5 6 7 ... - p = New(100, 10, 2, 7) - pages = p.Pages() - assert.Len(t, pages, 8) - assert.Equal(t, 1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) - assert.Equal(t, 4, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) - assert.Equal(t, 5, pages[4].Num()) - assert.False(t, pages[4].IsCurrent()) - assert.Equal(t, 6, pages[5].Num()) - assert.False(t, pages[5].IsCurrent()) - assert.Equal(t, 7, pages[6].Num()) - assert.False(t, pages[6].IsCurrent()) - assert.Equal(t, -1, pages[7].Num()) - assert.False(t, pages[7].IsCurrent()) + assertPages(t, 100, 2, 7, 1, 2, 3, 4, 5, 6, 7, -1) }) t.Run("Has both more previous and next pages", func(t *testing.T) { // ... 2 3 ... - p := New(35, 10, 2, 2) - pages := p.Pages() - assert.Len(t, pages, 4) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.True(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.False(t, pages[2].IsCurrent()) - assert.Equal(t, -1, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) + assertPages(t, 35, 2, 2, -1, 2, 3, -1) // ... 2 3 4 ... - p = New(49, 10, 3, 3) - pages = p.Pages() - assert.Len(t, pages, 5) - assert.Equal(t, -1, pages[0].Num()) - assert.False(t, pages[0].IsCurrent()) - assert.Equal(t, 2, pages[1].Num()) - assert.False(t, pages[1].IsCurrent()) - assert.Equal(t, 3, pages[2].Num()) - assert.True(t, pages[2].IsCurrent()) - assert.Equal(t, 4, pages[3].Num()) - assert.False(t, pages[3].IsCurrent()) - assert.Equal(t, -1, pages[4].Num()) - assert.False(t, pages[4].IsCurrent()) + assertPages(t, 49, 3, 3, -1, 2, 3, 4, -1) }) } diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go index bf2e84bf1c0..6012ab61aee 100644 --- a/modules/process/manager_test.go +++ b/modules/process/manager_test.go @@ -41,29 +41,23 @@ func TestManager_AddContext(t *testing.T) { func TestManager_Cancel(t *testing.T) { pm := Manager{processMap: make(map[IDType]*process), next: 1} - ctx, _, finished := pm.AddContext(t.Context(), "foo") - defer finished() + for _, managerCancel := range []bool{true, false} { + ctx, cancel, finished := pm.AddContext(t.Context(), "foo") + defer finished() - pm.Cancel(GetPID(ctx)) + if managerCancel { + pm.Cancel(GetPID(ctx)) + } else { + cancel() + } - select { - case <-ctx.Done(): - default: - assert.FailNow(t, "Cancel should cancel the provided context") + select { + case <-ctx.Done(): + default: + assert.FailNow(t, "Cancel should cancel the provided context") + } + finished() } - finished() - - ctx, cancel, finished := pm.AddContext(t.Context(), "foo") - defer finished() - - cancel() - - select { - case <-ctx.Done(): - default: - assert.FailNow(t, "Cancel should cancel the provided context") - } - finished() } func TestManager_Remove(t *testing.T) { diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go index 5cadabdeac6..e85ec58a452 100644 --- a/modules/queue/workerqueue_test.go +++ b/modules/queue/workerqueue_test.go @@ -4,6 +4,7 @@ package queue import ( + "fmt" "slices" "strconv" "sync" @@ -76,40 +77,32 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) { } runCount := 2 // we can run these tests even hundreds times to see its stability - t.Run("1/1", func(t *testing.T) { - for range runCount { - test(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1}) - } - }) - t.Run("3/1", func(t *testing.T) { - for range runCount { - test(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1}) - } - }) - t.Run("4/5", func(t *testing.T) { - for range runCount { - test(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5}) - } - }) + for _, queueSetting := range []setting.QueueSettings{ + {BatchLength: 1, MaxWorkers: 1}, + {BatchLength: 3, MaxWorkers: 1}, + {BatchLength: 4, MaxWorkers: 5}, + } { + t.Run(fmt.Sprintf("%d/%d", queueSetting.BatchLength, queueSetting.MaxWorkers), func(t *testing.T) { + for range runCount { + test(t, queueSetting) + } + }) + } } func TestWorkerPoolQueuePersistence(t *testing.T) { runCount := 2 // we can run these tests even hundreds times to see its stability - t.Run("1/1", func(t *testing.T) { - for range runCount { - testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1, Length: 100}) - } - }) - t.Run("3/1", func(t *testing.T) { - for range runCount { - testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1, Length: 100}) - } - }) - t.Run("4/5", func(t *testing.T) { - for range runCount { - testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5, Length: 100}) - } - }) + for _, queueSetting := range []setting.QueueSettings{ + {BatchLength: 1, MaxWorkers: 1, Length: 100}, + {BatchLength: 3, MaxWorkers: 1, Length: 100}, + {BatchLength: 4, MaxWorkers: 5, Length: 100}, + } { + t.Run(fmt.Sprintf("%d/%d", queueSetting.BatchLength, queueSetting.MaxWorkers), func(t *testing.T) { + for range runCount { + testWorkerPoolQueuePersistence(t, queueSetting) + } + }) + } } func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSettings) { @@ -184,37 +177,23 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { return nil } - q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 1, Length: 100}, handler, false) - stop := runWorkerPoolQueue(q) - for i := range 5 { - assert.NoError(t, q.Push(i)) + for _, workerCount := range []int{1, 3} { + q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: workerCount, Length: 100}, handler, false) + stop := runWorkerPoolQueue(q) + for i := range workerCount * 5 { + assert.NoError(t, q.Push(i)) + } + + time.Sleep(50 * time.Millisecond) + assert.Equal(t, workerCount, q.GetWorkerNumber()) + assert.Equal(t, workerCount, q.GetWorkerActiveNumber()) + time.Sleep(500 * time.Millisecond) + assert.Equal(t, workerCount, q.GetWorkerNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) + time.Sleep(workerIdleDuration) + assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working + stop() } - - time.Sleep(50 * time.Millisecond) - assert.Equal(t, 1, q.GetWorkerNumber()) - assert.Equal(t, 1, q.GetWorkerActiveNumber()) - time.Sleep(500 * time.Millisecond) - assert.Equal(t, 1, q.GetWorkerNumber()) - assert.Equal(t, 0, q.GetWorkerActiveNumber()) - time.Sleep(workerIdleDuration) - assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working - stop() - - q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 3, Length: 100}, handler, false) - stop = runWorkerPoolQueue(q) - for i := range 15 { - assert.NoError(t, q.Push(i)) - } - - time.Sleep(50 * time.Millisecond) - assert.Equal(t, 3, q.GetWorkerNumber()) - assert.Equal(t, 3, q.GetWorkerActiveNumber()) - time.Sleep(500 * time.Millisecond) - assert.Equal(t, 3, q.GetWorkerNumber()) - assert.Equal(t, 0, q.GetWorkerActiveNumber()) - time.Sleep(workerIdleDuration) - assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working - stop() } func TestWorkerPoolQueueShutdown(t *testing.T) { diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index 5e6266d9f21..dfe3093ace6 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -10,6 +10,7 @@ import ( repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" "gitea.dev/modules/git" + api "gitea.dev/modules/structs" "github.com/stretchr/testify/assert" ) @@ -52,49 +53,27 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Len(t, payloadCommits, 3) assert.NotNil(t, headCommit) - assert.Equal(t, "69554a6", payloadCommits[0].ID) - assert.Equal(t, "not signed commit", payloadCommits[0].Message) - assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/69554a6", payloadCommits[0].URL) - assert.Equal(t, "User2", payloadCommits[0].Committer.Name) - assert.Equal(t, "user2", payloadCommits[0].Committer.UserName) - assert.Equal(t, "User2", payloadCommits[0].Author.Name) - assert.Equal(t, "user2", payloadCommits[0].Author.UserName) - assert.Equal(t, []string{}, payloadCommits[0].Added) - assert.Equal(t, []string{}, payloadCommits[0].Removed) - assert.Equal(t, []string{"readme.md"}, payloadCommits[0].Modified) - - assert.Equal(t, "27566bd", payloadCommits[1].ID) - assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message) - assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/27566bd", payloadCommits[1].URL) - assert.Equal(t, "User2", payloadCommits[1].Committer.Name) - assert.Equal(t, "user2", payloadCommits[1].Committer.UserName) - assert.Equal(t, "User2", payloadCommits[1].Author.Name) - assert.Equal(t, "user2", payloadCommits[1].Author.UserName) - assert.Equal(t, []string{}, payloadCommits[1].Added) - assert.Equal(t, []string{}, payloadCommits[1].Removed) - assert.Equal(t, []string{"readme.md"}, payloadCommits[1].Modified) - - assert.Equal(t, "5099b81", payloadCommits[2].ID) - assert.Equal(t, "good signed commit", payloadCommits[2].Message) - assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/5099b81", payloadCommits[2].URL) - assert.Equal(t, "User2", payloadCommits[2].Committer.Name) - assert.Equal(t, "user2", payloadCommits[2].Committer.UserName) - assert.Equal(t, "User2", payloadCommits[2].Author.Name) - assert.Equal(t, "user2", payloadCommits[2].Author.UserName) - assert.Equal(t, []string{"readme.md"}, payloadCommits[2].Added) - assert.Equal(t, []string{}, payloadCommits[2].Removed) - assert.Equal(t, []string{}, payloadCommits[2].Modified) - - assert.Equal(t, "69554a6", headCommit.ID) - assert.Equal(t, "not signed commit", headCommit.Message) - assert.Equal(t, "https://try.gitea.io/user2/repo16/commit/69554a6", headCommit.URL) - assert.Equal(t, "User2", headCommit.Committer.Name) - assert.Equal(t, "user2", headCommit.Committer.UserName) - assert.Equal(t, "User2", headCommit.Author.Name) - assert.Equal(t, "user2", headCommit.Author.UserName) - assert.Equal(t, []string{}, headCommit.Added) - assert.Equal(t, []string{}, headCommit.Removed) - assert.Equal(t, []string{"readme.md"}, headCommit.Modified) + for _, tt := range []struct { + commit *api.PayloadCommit + id, message, url string + added, modified []string + }{ + {payloadCommits[0], "69554a6", "not signed commit", "https://try.gitea.io/user2/repo16/commit/69554a6", []string{}, []string{"readme.md"}}, + {payloadCommits[1], "27566bd", "good signed commit (with not yet validated email)", "https://try.gitea.io/user2/repo16/commit/27566bd", []string{}, []string{"readme.md"}}, + {payloadCommits[2], "5099b81", "good signed commit", "https://try.gitea.io/user2/repo16/commit/5099b81", []string{"readme.md"}, []string{}}, + {headCommit, "69554a6", "not signed commit", "https://try.gitea.io/user2/repo16/commit/69554a6", []string{}, []string{"readme.md"}}, + } { + assert.Equal(t, tt.id, tt.commit.ID) + assert.Equal(t, tt.message, tt.commit.Message) + assert.Equal(t, tt.url, tt.commit.URL) + assert.Equal(t, "User2", tt.commit.Committer.Name) + assert.Equal(t, "user2", tt.commit.Committer.UserName) + assert.Equal(t, "User2", tt.commit.Author.Name) + assert.Equal(t, "user2", tt.commit.Author.UserName) + assert.Equal(t, tt.added, tt.commit.Added) + assert.Equal(t, []string{}, tt.commit.Removed) + assert.Equal(t, tt.modified, tt.commit.Modified) + } } func TestCommitToPushCommit(t *testing.T) { diff --git a/modules/repository/license_test.go b/modules/repository/license_test.go index d00156a496a..c1b94a55c93 100644 --- a/modules/repository/license_test.go +++ b/modules/repository/license_test.go @@ -11,22 +11,17 @@ import ( ) func Test_getLicense(t *testing.T) { - type args struct { - name string - values *LicenseValues - } tests := []struct { name string - args args + license string + values *LicenseValues want string wantErr assert.ErrorAssertionFunc }{ { - name: "regular", - args: args{ - name: "MIT", - values: &LicenseValues{Owner: "Gitea", Year: "2023"}, - }, + name: "regular", + license: "MIT", + values: &LicenseValues{Owner: "Gitea", Year: "2023"}, want: `MIT License Copyright (c) 2023 Gitea @@ -35,41 +30,33 @@ Permission is hereby granted`, wantErr: assert.NoError, }, { - name: "license not found", - args: args{ - name: "notfound", - }, + name: "license not found", + license: "notfound", wantErr: assert.Error, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := GetLicense(tt.args.name, tt.args.values) - if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) { + got, err := GetLicense(tt.license, tt.values) + if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.license, tt.values)) { return } - assert.Contains(t, string(got), tt.want, "GetLicense(%v, %v)", tt.args.name, tt.args.values) + assert.Contains(t, string(got), tt.want, "GetLicense(%v, %v)", tt.license, tt.values) }) } } func Test_fillLicensePlaceholder(t *testing.T) { - type args struct { - name string - values *LicenseValues - origin string - } tests := []struct { - name string - args args - want string + name string + license string + origin string + want string }{ { - name: "owner", - args: args{ - name: "regular", - values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, - origin: ` + name: "owner", + license: "regular", + origin: ` [NAME] @@ -81,7 +68,6 @@ func Test_fillLicensePlaceholder(t *testing.T) { [one or more legally recognised persons or entities offering the Work under the terms and conditions of this Licence] `, - }, want: ` Gitea Gitea @@ -96,39 +82,31 @@ Gitea `, }, { - name: "email", - args: args{ - name: "regular", - values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, - origin: ` + name: "email", + license: "regular", + origin: ` [EMAIL] `, - }, want: ` teabot@gitea.io `, }, { - name: "repo", - args: args{ - name: "regular", - values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, - origin: ` + name: "repo", + license: "regular", + origin: ` `, - }, want: ` gitea gitea `, }, { - name: "year", - args: args{ - name: "regular", - values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, - origin: ` + name: "year", + license: "regular", + origin: ` [YEAR] {YEAR} @@ -136,7 +114,6 @@ gitea [Year] [year] `, - }, want: ` 2023 2023 @@ -147,18 +124,15 @@ gitea `, }, { - name: "0BSD", - args: args{ - name: "0BSD", - values: &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"}, - origin: ` + name: "0BSD", + license: "0BSD", + origin: ` Copyright (C) YEAR by AUTHOR EMAIL ... ... THE AUTHOR BE LIABLE FOR ... `, - }, want: ` Copyright (C) 2023 by Gitea teabot@gitea.io @@ -170,7 +144,8 @@ Copyright (C) 2023 by Gitea teabot@gitea.io } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, string(fillLicensePlaceholder(tt.args.name, tt.args.values, []byte(tt.args.origin))), "fillLicensePlaceholder(%v, %v, %v)", tt.args.name, tt.args.values, tt.args.origin) + values := &LicenseValues{Year: "2023", Owner: "Gitea", Email: "teabot@gitea.io", Repo: "gitea"} + assert.Equalf(t, tt.want, string(fillLicensePlaceholder(tt.license, values, []byte(tt.origin))), "fillLicensePlaceholder(%v, %v, %v)", tt.license, values, tt.origin) }) } } diff --git a/modules/setting/actions_test.go b/modules/setting/actions_test.go index 5b2e5ac3d9f..e5f8cd30768 100644 --- a/modules/setting/actions_test.go +++ b/modules/setting/actions_test.go @@ -15,89 +15,57 @@ import ( ) func Test_getStorageInheritNameSectionTypeForActions(t *testing.T) { - iniStr := ` + for _, tc := range []struct { + iniStr, logType, artifactType string + }{ + {` [storage] STORAGE_TYPE = minio - ` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadActionsFrom(cfg)) - - assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) - assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) - - iniStr = ` + `, "minio", "minio"}, + {` [storage.actions_log] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadActionsFrom(cfg)) - - assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) - assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) - - iniStr = ` +`, "minio", "local"}, + {` [storage.actions_log] STORAGE_TYPE = my_storage [storage.my_storage] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadActionsFrom(cfg)) - - assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) - assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) - - iniStr = ` +`, "minio", "local"}, + {` [storage.actions_artifacts] STORAGE_TYPE = my_storage [storage.my_storage] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadActionsFrom(cfg)) - - assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) - assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) - - iniStr = ` +`, "local", "minio"}, + {` [storage.actions_artifacts] STORAGE_TYPE = my_storage [storage.my_storage] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadActionsFrom(cfg)) +`, "local", "minio"}, + {``, "local", "local"}, + } { + cfg, err := NewConfigProviderFromData(tc.iniStr) + assert.NoError(t, err) + assert.NoError(t, loadActionsFrom(cfg)) - assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) - assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) - - iniStr = `` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadActionsFrom(cfg)) - - assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) - assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.EqualValues(t, tc.logType, Actions.LogStorage.Type) + if tc.logType == "minio" { + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + } else { + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + } + assert.EqualValues(t, tc.artifactType, Actions.ArtifactStorage.Type) + if tc.artifactType == "minio" { + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + } else { + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + } + } } func Test_WorkflowDirs(t *testing.T) { diff --git a/modules/setting/attachment_test.go b/modules/setting/attachment_test.go index c566dfa60cd..74a088e7bf5 100644 --- a/modules/setting/attachment_test.go +++ b/modules/setting/attachment_test.go @@ -10,7 +10,10 @@ import ( ) func Test_getStorageCustomType(t *testing.T) { - iniStr := ` + for _, tc := range []struct { + iniStr, endpoint, bucket string + }{ + {` [attachment] STORAGE_TYPE = my_minio MINIO_BUCKET = gitea-attachment @@ -18,20 +21,8 @@ MINIO_BUCKET = gitea-attachment [storage.my_minio] STORAGE_TYPE = minio MINIO_ENDPOINT = my_minio:9000 -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - assert.NoError(t, loadAttachmentFrom(cfg)) - - assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.Equal(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint) - assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) -} - -func Test_getStorageTypeSectionOverridesStorageSection(t *testing.T) { - iniStr := ` +`, "my_minio:9000", "gitea-attachment"}, + {` [attachment] STORAGE_TYPE = minio @@ -40,19 +31,8 @@ MINIO_BUCKET = gitea-minio [storage] MINIO_BUCKET = gitea -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - assert.NoError(t, loadAttachmentFrom(cfg)) - - assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.Equal(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket) - assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) -} - -func Test_getStorageSpecificOverridesStorage(t *testing.T) { - iniStr := ` +`, "", "gitea-minio"}, + {` [attachment] STORAGE_TYPE = minio MINIO_BUCKET = gitea-attachment @@ -62,15 +42,19 @@ MINIO_BUCKET = gitea [storage] STORAGE_TYPE = local -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) +`, "", "gitea-attachment"}, + } { + cfg, err := NewConfigProviderFromData(tc.iniStr) + assert.NoError(t, err) + assert.NoError(t, loadAttachmentFrom(cfg)) - assert.NoError(t, loadAttachmentFrom(cfg)) - - assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.EqualValues(t, "minio", Attachment.Storage.Type) + if tc.endpoint != "" { + assert.Equal(t, tc.endpoint, Attachment.Storage.MinioConfig.Endpoint) + } + assert.Equal(t, tc.bucket, Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + } } func Test_getStorageGetDefaults(t *testing.T) { diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go index 7d270ac21ad..d8c83220841 100644 --- a/modules/setting/config_env_test.go +++ b/modules/setting/config_env_test.go @@ -11,22 +11,20 @@ import ( ) func TestDecodeEnvSectionKey(t *testing.T) { - ok, section, key := decodeEnvSectionKey("SEC__KEY") - assert.True(t, ok) - assert.Equal(t, "sec", section) - assert.Equal(t, "KEY", key) + for _, tc := range []struct { + input, section, key string + }{ + {"SEC__KEY", "sec", "KEY"}, + {"sec__key", "sec", "key"}, + {"LOG_0x2E_CONSOLE__STDERR", "log.console", "STDERR"}, + } { + ok, section, key := decodeEnvSectionKey(tc.input) + assert.True(t, ok) + assert.Equal(t, tc.section, section) + assert.Equal(t, tc.key, key) + } - ok, section, key = decodeEnvSectionKey("sec__key") - assert.True(t, ok) - assert.Equal(t, "sec", section) - assert.Equal(t, "key", key) - - ok, section, key = decodeEnvSectionKey("LOG_0x2E_CONSOLE__STDERR") - assert.True(t, ok) - assert.Equal(t, "log.console", section) - assert.Equal(t, "STDERR", key) - - ok, section, key = decodeEnvSectionKey("SEC") + ok, section, key := decodeEnvSectionKey("SEC") assert.False(t, ok) assert.Empty(t, section) assert.Empty(t, key) @@ -36,19 +34,15 @@ func TestDecodeEnvironmentKey(t *testing.T) { prefix := "GITEA__" suffix := "__FILE" - ok, section, key, file := decodeEnvironmentKey(prefix, suffix, "SEC__KEY") - assert.False(t, ok) - assert.Empty(t, section) - assert.Empty(t, key) - assert.False(t, file) + for _, input := range []string{"SEC__KEY", "GITEA__SEC"} { + ok, section, key, file := decodeEnvironmentKey(prefix, suffix, input) + assert.False(t, ok) + assert.Empty(t, section) + assert.Empty(t, key) + assert.False(t, file) + } - ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC") - assert.False(t, ok) - assert.Empty(t, section) - assert.Empty(t, key) - assert.False(t, file) - - ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA____KEY") + ok, section, key, file := decodeEnvironmentKey(prefix, suffix, "GITEA____KEY") assert.True(t, ok) assert.Empty(t, section) assert.Equal(t, "KEY", key) @@ -103,20 +97,18 @@ key = old assert.True(t, changed) assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String()) - cfg, _ = NewConfigProviderFromData("") - _ = os.WriteFile(tmpFile, []byte("value-from-file\n"), 0o644) - EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) - assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String()) - - cfg, _ = NewConfigProviderFromData("") - _ = os.WriteFile(tmpFile, []byte("value-from-file\r\n"), 0o644) - EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) - assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String()) - - cfg, _ = NewConfigProviderFromData("") - _ = os.WriteFile(tmpFile, []byte("value-from-file\n\n"), 0o644) - EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) - assert.Equal(t, "value-from-file\n", cfg.Section("sec").Key("key").String()) + for _, tc := range []struct { + content, want string + }{ + {"value-from-file\n", "value-from-file"}, + {"value-from-file\r\n", "value-from-file"}, + {"value-from-file\n\n", "value-from-file\n"}, + } { + cfg, _ = NewConfigProviderFromData("") + _ = os.WriteFile(tmpFile, []byte(tc.content), 0o644) + EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile}) + assert.Equal(t, tc.want, cfg.Section("sec").Key("key").String()) + } } func TestEnvironmentToConfigSubSecKey(t *testing.T) { diff --git a/modules/setting/global_lock_test.go b/modules/setting/global_lock_test.go index 22bb4233f3d..6ff3e5abcbe 100644 --- a/modules/setting/global_lock_test.go +++ b/modules/setting/global_lock_test.go @@ -12,63 +12,39 @@ import ( ) func TestLoadGlobalLockConfig(t *testing.T) { - t.Run("DefaultGlobalLockConfig", func(t *testing.T) { - defer test.MockVariableValue(&Redis)() - iniStr := `` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - loadRedisFrom(cfg) - loadGlobalLockFrom(cfg) - assert.Equal(t, "memory", GlobalLock.ServiceType) - }) - - t.Run("RedisGlobalLockConfig", func(t *testing.T) { - defer test.MockVariableValue(&Redis)() - iniStr := ` + for _, tt := range []struct { + name, config, serviceType, connStr string + }{ + {"DefaultGlobalLockConfig", ``, "memory", ""}, + {"RedisGlobalLockConfig", ` [global_lock] SERVICE_TYPE = redis SERVICE_CONN_STR = addrs=127.0.0.1:6379 db=0 -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - loadRedisFrom(cfg) - loadGlobalLockFrom(cfg) - assert.Equal(t, "redis", GlobalLock.ServiceType) - assert.Equal(t, "addrs=127.0.0.1:6379 db=0", GlobalLock.ServiceConnStr) - }) - - t.Run("RedisGlobalLockFallsBackToSharedRedis", func(t *testing.T) { - defer test.MockVariableValue(&Redis)() - iniStr := ` +`, "redis", "addrs=127.0.0.1:6379 db=0"}, + {"RedisGlobalLockFallsBackToSharedRedis", ` [redis] CONN_STR = redis://127.0.0.1:6379/0 [global_lock] SERVICE_TYPE = redis -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - loadRedisFrom(cfg) - loadGlobalLockFrom(cfg) - assert.Equal(t, "redis", GlobalLock.ServiceType) - assert.Equal(t, "redis://127.0.0.1:6379/0", GlobalLock.ServiceConnStr) - }) - - t.Run("RedisGlobalLockOwnConnWinsOverSharedRedis", func(t *testing.T) { - defer test.MockVariableValue(&Redis)() - iniStr := ` +`, "redis", "redis://127.0.0.1:6379/0"}, + {"RedisGlobalLockOwnConnWinsOverSharedRedis", ` [redis] CONN_STR = redis://127.0.0.1:6379/0 [global_lock] SERVICE_TYPE = redis SERVICE_CONN_STR = redis://10.0.0.1:6379/1 -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - loadRedisFrom(cfg) - loadGlobalLockFrom(cfg) - assert.Equal(t, "redis", GlobalLock.ServiceType) - assert.Equal(t, "redis://10.0.0.1:6379/1", GlobalLock.ServiceConnStr) - }) +`, "redis", "redis://10.0.0.1:6379/1"}, + } { + t.Run(tt.name, func(t *testing.T) { + defer test.MockVariableValue(&Redis)() + cfg, err := NewConfigProviderFromData(tt.config) + assert.NoError(t, err) + loadRedisFrom(cfg) + loadGlobalLockFrom(cfg) + assert.Equal(t, tt.serviceType, GlobalLock.ServiceType) + if tt.serviceType == "redis" { + assert.Equal(t, tt.connStr, GlobalLock.ServiceConnStr) + } + }) + } } diff --git a/modules/setting/lfs_test.go b/modules/setting/lfs_test.go index faab84d1aa6..62e8eebff0d 100644 --- a/modules/setting/lfs_test.go +++ b/modules/setting/lfs_test.go @@ -12,82 +12,56 @@ import ( ) func Test_getStorageInheritNameSectionTypeForLFS(t *testing.T) { - iniStr := ` + for _, tt := range []struct { + config, storageType, path string + }{ + {` [storage] STORAGE_TYPE = minio - ` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadLFSFrom(cfg)) - - assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) - - iniStr = ` + `, "minio", "lfs/"}, + {` [server] LFS_CONTENT_PATH = path_ignored [lfs] PATH = path_used -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - applyDeprecatedKeys(cfg) - assert.NoError(t, loadLFSFrom(cfg)) - - assert.EqualValues(t, "local", LFS.Storage.Type) - assert.Contains(t, LFS.Storage.Path, "path_used") - - iniStr = ` +`, "local", "path_used"}, + {` [server] LFS_CONTENT_PATH = deprecatedpath -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - applyDeprecatedKeys(cfg) - assert.NoError(t, loadLFSFrom(cfg)) - - assert.EqualValues(t, "local", LFS.Storage.Type) - assert.Contains(t, LFS.Storage.Path, "deprecatedpath") - - iniStr = ` +`, "local", "deprecatedpath"}, + {` [storage.lfs] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadLFSFrom(cfg)) - - assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) - - iniStr = ` +`, "minio", "lfs/"}, + {` [lfs] STORAGE_TYPE = my_minio [storage.my_minio] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadLFSFrom(cfg)) - - assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) - - iniStr = ` +`, "minio", "lfs/"}, + {` [lfs] STORAGE_TYPE = my_minio MINIO_BASE_PATH = my_lfs/ [storage.my_minio] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadLFSFrom(cfg)) - - assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.Equal(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath) +`, "minio", "my_lfs/"}, + } { + cfg, err := NewConfigProviderFromData(tt.config) + assert.NoError(t, err) + if tt.storageType == "local" { + applyDeprecatedKeys(cfg) + } + assert.NoError(t, loadLFSFrom(cfg)) + assert.EqualValues(t, tt.storageType, LFS.Storage.Type) + if tt.storageType == "local" { + assert.Contains(t, LFS.Storage.Path, tt.path) + } else { + assert.Equal(t, tt.path, LFS.Storage.MinioConfig.BasePath) + } + } } func Test_LFSStorage1(t *testing.T) { diff --git a/modules/setting/log_test.go b/modules/setting/log_test.go index a217cf5490c..17b5b31b83f 100644 --- a/modules/setting/log_test.go +++ b/modules/setting/log_test.go @@ -36,9 +36,6 @@ func toJSON(v any) string { } func TestLogConfigDefault(t *testing.T) { - manager, managerClose := initLoggersByConfig(t, ``) - defer managerClose() - writerDump := ` { "console": { @@ -57,95 +54,37 @@ func TestLogConfigDefault(t *testing.T) { } ` - dump := manager.GetLogger(log.DEFAULT).DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) - - dump = manager.GetLogger("access").DumpWriters() - require.JSONEq(t, "{}", toJSON(dump)) - - dump = manager.GetLogger("router").DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) - - dump = manager.GetLogger("xorm").DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) -} - -func TestLogConfigDisable(t *testing.T) { - manager, managerClose := initLoggersByConfig(t, ` + for _, tt := range []struct { + name, config, routerDump string + }{ + {"Default", ``, writerDump}, + {"Disable", ` [log] logger.router.MODE = logger.xorm.MODE = -`) - defer managerClose() - - writerDump := ` -{ - "console": { - "BufferLen": 10000, - "Colorize": false, - "Expression": "", - "Flags": "stdflags", - "Level": "info", - "Prefix": "", - "StacktraceLevel": "none", - "WriterOption": { - "Stderr": false - }, - "WriterType": "console" - } -} -` - - dump := manager.GetLogger(log.DEFAULT).DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) - - dump = manager.GetLogger("access").DumpWriters() - require.JSONEq(t, "{}", toJSON(dump)) - - dump = manager.GetLogger("router").DumpWriters() - require.JSONEq(t, "{}", toJSON(dump)) - - dump = manager.GetLogger("xorm").DumpWriters() - require.JSONEq(t, "{}", toJSON(dump)) -} - -func TestLogConfigLegacyDefault(t *testing.T) { - manager, managerClose := initLoggersByConfig(t, ` +`, "{}"}, + {"LegacyDefault", ` [log] MODE = console -`) - defer managerClose() +`, writerDump}, + } { + t.Run(tt.name, func(t *testing.T) { + manager, managerClose := initLoggersByConfig(t, tt.config) + defer managerClose() - writerDump := ` -{ - "console": { - "BufferLen": 10000, - "Colorize": false, - "Expression": "", - "Flags": "stdflags", - "Level": "info", - "Prefix": "", - "StacktraceLevel": "none", - "WriterOption": { - "Stderr": false - }, - "WriterType": "console" + for _, logger := range []struct { + name, expected string + }{ + {log.DEFAULT, writerDump}, + {"access", "{}"}, + {"router", tt.routerDump}, + {"xorm", tt.routerDump}, + } { + require.JSONEq(t, logger.expected, toJSON(manager.GetLogger(logger.name).DumpWriters())) + } + }) } } -` - - dump := manager.GetLogger(log.DEFAULT).DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) - - dump = manager.GetLogger("access").DumpWriters() - require.JSONEq(t, "{}", toJSON(dump)) - - dump = manager.GetLogger("router").DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) - - dump = manager.GetLogger("xorm").DumpWriters() - require.JSONEq(t, writerDump, toJSON(dump)) -} func TestLogConfigLegacyMode(t *testing.T) { tempDir := t.TempDir() diff --git a/modules/setting/packages_test.go b/modules/setting/packages_test.go index 47378f35add..ea7fe5a74ea 100644 --- a/modules/setting/packages_test.go +++ b/modules/setting/packages_test.go @@ -31,168 +31,98 @@ func TestMustBytes(t *testing.T) { } func Test_getStorageInheritNameSectionTypeForPackages(t *testing.T) { - // packages storage inherits from storage if nothing configured - iniStr := ` + for _, testcase := range []struct { + iniStr, basePath string + }{ + // packages storage inherits from storage if nothing configured + {` [storage] STORAGE_TYPE = minio -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadPackagesFrom(cfg)) - - assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) - - // we can also configure packages storage directly - iniStr = ` +`, "packages/"}, + // we can also configure packages storage directly + {` [storage.packages] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadPackagesFrom(cfg)) - - assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) - - // or we can indicate the storage type in the packages section - iniStr = ` +`, "packages/"}, + // or we can indicate the storage type in the packages section + {` [packages] STORAGE_TYPE = my_minio [storage.my_minio] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadPackagesFrom(cfg)) - - assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) - - // or we can indicate the storage type and minio base path in the packages section - iniStr = ` +`, "packages/"}, + // or we can indicate the storage type and minio base path in the packages section + {` [packages] STORAGE_TYPE = my_minio MINIO_BASE_PATH = my_packages/ [storage.my_minio] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadPackagesFrom(cfg)) +`, "my_packages/"}, + } { + cfg, err := NewConfigProviderFromData(testcase.iniStr) + assert.NoError(t, err) + assert.NoError(t, loadPackagesFrom(cfg)) - assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.Equal(t, "my_packages/", Packages.Storage.MinioConfig.BasePath) + assert.EqualValues(t, "minio", Packages.Storage.Type) + assert.Equal(t, testcase.basePath, Packages.Storage.MinioConfig.BasePath) + } } -func Test_PackageStorage1(t *testing.T) { - iniStr := ` +func Test_PackageStorage(t *testing.T) { + const storageConfig = `STORAGE_TYPE = minio +MINIO_ENDPOINT = s3.my-domain.net +MINIO_BUCKET = gitea +MINIO_LOCATION = homenet +MINIO_USE_SSL = true +MINIO_ACCESS_KEY_ID = correct_key +MINIO_SECRET_ACCESS_KEY = correct_key +` + for _, testcase := range []struct { + iniStr, basePath string + }{ + {` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [packages] MINIO_BASE_PATH = packages/ SERVE_DIRECT = true [storage] -STORAGE_TYPE = minio -MINIO_ENDPOINT = s3.my-domain.net -MINIO_BUCKET = gitea -MINIO_LOCATION = homenet -MINIO_USE_SSL = true -MINIO_ACCESS_KEY_ID = correct_key -MINIO_SECRET_ACCESS_KEY = correct_key -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - assert.NoError(t, loadPackagesFrom(cfg)) - storage := Packages.Storage - - assert.EqualValues(t, "minio", storage.Type) - assert.Equal(t, "gitea", storage.MinioConfig.Bucket) - assert.Equal(t, "packages/", storage.MinioConfig.BasePath) - assert.True(t, storage.MinioConfig.ServeDirect) -} - -func Test_PackageStorage2(t *testing.T) { - iniStr := ` +`, "packages/"}, + {` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [storage.packages] MINIO_BASE_PATH = packages/ SERVE_DIRECT = true [storage] -STORAGE_TYPE = minio -MINIO_ENDPOINT = s3.my-domain.net -MINIO_BUCKET = gitea -MINIO_LOCATION = homenet -MINIO_USE_SSL = true -MINIO_ACCESS_KEY_ID = correct_key -MINIO_SECRET_ACCESS_KEY = correct_key -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - assert.NoError(t, loadPackagesFrom(cfg)) - storage := Packages.Storage - - assert.EqualValues(t, "minio", storage.Type) - assert.Equal(t, "gitea", storage.MinioConfig.Bucket) - assert.Equal(t, "packages/", storage.MinioConfig.BasePath) - assert.True(t, storage.MinioConfig.ServeDirect) -} - -func Test_PackageStorage3(t *testing.T) { - iniStr := ` +`, "packages/"}, + {` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [packages] STORAGE_TYPE = my_cfg MINIO_BASE_PATH = my_packages/ SERVE_DIRECT = true [storage.my_cfg] -STORAGE_TYPE = minio -MINIO_ENDPOINT = s3.my-domain.net -MINIO_BUCKET = gitea -MINIO_LOCATION = homenet -MINIO_USE_SSL = true -MINIO_ACCESS_KEY_ID = correct_key -MINIO_SECRET_ACCESS_KEY = correct_key -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - assert.NoError(t, loadPackagesFrom(cfg)) - storage := Packages.Storage - - assert.EqualValues(t, "minio", storage.Type) - assert.Equal(t, "gitea", storage.MinioConfig.Bucket) - assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath) - assert.True(t, storage.MinioConfig.ServeDirect) -} - -func Test_PackageStorage4(t *testing.T) { - iniStr := ` +`, "my_packages/"}, + {` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [storage.packages] STORAGE_TYPE = my_cfg MINIO_BASE_PATH = my_packages/ SERVE_DIRECT = true [storage.my_cfg] -STORAGE_TYPE = minio -MINIO_ENDPOINT = s3.my-domain.net -MINIO_BUCKET = gitea -MINIO_LOCATION = homenet -MINIO_USE_SSL = true -MINIO_ACCESS_KEY_ID = correct_key -MINIO_SECRET_ACCESS_KEY = correct_key -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) +`, "my_packages/"}, + } { + cfg, err := NewConfigProviderFromData(testcase.iniStr + storageConfig) + assert.NoError(t, err) - assert.NoError(t, loadPackagesFrom(cfg)) - storage := Packages.Storage + assert.NoError(t, loadPackagesFrom(cfg)) + storage := Packages.Storage - assert.EqualValues(t, "minio", storage.Type) - assert.Equal(t, "gitea", storage.MinioConfig.Bucket) - assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath) - assert.True(t, storage.MinioConfig.ServeDirect) + assert.EqualValues(t, "minio", storage.Type) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, testcase.basePath, storage.MinioConfig.BasePath) + assert.True(t, storage.MinioConfig.ServeDirect) + } } diff --git a/modules/setting/repository_archive_test.go b/modules/setting/repository_archive_test.go index d5b95272d63..1fc639b80a4 100644 --- a/modules/setting/repository_archive_test.go +++ b/modules/setting/repository_archive_test.go @@ -10,64 +10,49 @@ import ( ) func Test_getStorageInheritNameSectionTypeForRepoArchive(t *testing.T) { - // packages storage inherits from storage if nothing configured - iniStr := ` + for _, tc := range []struct { + ini, basePath string + }{ + // packages storage inherits from storage if nothing configured + {` [storage] STORAGE_TYPE = minio -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadRepoArchiveFrom(cfg)) - - assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) - - // we can also configure packages storage directly - iniStr = ` +`, "repo-archive/"}, + // we can also configure packages storage directly + {` [storage.repo-archive] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadRepoArchiveFrom(cfg)) - - assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) - - // or we can indicate the storage type in the packages section - iniStr = ` +`, "repo-archive/"}, + // or we can indicate the storage type in the packages section + {` [repo-archive] STORAGE_TYPE = my_minio [storage.my_minio] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadRepoArchiveFrom(cfg)) - - assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) - - // or we can indicate the storage type and minio base path in the packages section - iniStr = ` +`, "repo-archive/"}, + // or we can indicate the storage type and minio base path in the packages section + {` [repo-archive] STORAGE_TYPE = my_minio MINIO_BASE_PATH = my_archive/ [storage.my_minio] STORAGE_TYPE = minio -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - assert.NoError(t, loadRepoArchiveFrom(cfg)) +`, "my_archive/"}, + } { + cfg, err := NewConfigProviderFromData(tc.ini) + assert.NoError(t, err) + assert.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.Equal(t, "my_archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.EqualValues(t, "minio", RepoArchive.Storage.Type) + assert.Equal(t, tc.basePath, RepoArchive.Storage.MinioConfig.BasePath) + } } func Test_RepoArchiveStorage(t *testing.T) { - iniStr := ` + for _, ini := range []string{ + ` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [storage] STORAGE_TYPE = minio @@ -77,17 +62,8 @@ MINIO_LOCATION = homenet MINIO_USE_SSL = true MINIO_ACCESS_KEY_ID = correct_key MINIO_SECRET_ACCESS_KEY = correct_key -` - cfg, err := NewConfigProviderFromData(iniStr) - assert.NoError(t, err) - - assert.NoError(t, loadRepoArchiveFrom(cfg)) - storage := RepoArchive.Storage - - assert.EqualValues(t, "minio", storage.Type) - assert.Equal(t, "gitea", storage.MinioConfig.Bucket) - - iniStr = ` +`, + ` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [storage.repo-archive] STORAGE_TYPE = s3 @@ -99,13 +75,15 @@ MINIO_LOCATION = homenet MINIO_USE_SSL = true MINIO_ACCESS_KEY_ID = correct_key MINIO_SECRET_ACCESS_KEY = correct_key -` - cfg, err = NewConfigProviderFromData(iniStr) - assert.NoError(t, err) +`, + } { + cfg, err := NewConfigProviderFromData(ini) + assert.NoError(t, err) - assert.NoError(t, loadRepoArchiveFrom(cfg)) - storage = RepoArchive.Storage + assert.NoError(t, loadRepoArchiveFrom(cfg)) + storage := RepoArchive.Storage - assert.EqualValues(t, "minio", storage.Type) - assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.EqualValues(t, "minio", storage.Type) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + } } diff --git a/modules/setting/service_test.go b/modules/setting/service_test.go index 1527d4f263b..5dc8a8b08cf 100644 --- a/modules/setting/service_test.go +++ b/modules/setting/service_test.go @@ -54,77 +54,57 @@ func TestLoadServiceVisibilityModes(t *testing.T) { } return ret } - testCases := map[string]func(){ + testCases := map[string]struct { + defaultMode structs.VisibleType + allowedModes []structs.VisibilityString + }{ ` [service] DEFAULT_USER_VISIBILITY = public ALLOWED_USER_VISIBILITY_MODES = public,limited,private -`: func() { - assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, +`: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}}, ` [service] DEFAULT_USER_VISIBILITY = public - `: func() { - assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, + `: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}}, ` [service] DEFAULT_USER_VISIBILITY = limited - `: func() { - assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, + `: {structs.VisibleTypeLimited, []structs.VisibilityString{"public", "limited", "private"}}, ` [service] ALLOWED_USER_VISIBILITY_MODES = public,limited,private -`: func() { - assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, +`: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}}, ` [service] DEFAULT_USER_VISIBILITY = public ALLOWED_USER_VISIBILITY_MODES = limited,private -`: func() { - assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, +`: {structs.VisibleTypeLimited, []structs.VisibilityString{"limited", "private"}}, ` [service] DEFAULT_USER_VISIBILITY = my_type ALLOWED_USER_VISIBILITY_MODES = limited,private -`: func() { - assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, +`: {structs.VisibleTypeLimited, []structs.VisibilityString{"limited", "private"}}, ` [service] DEFAULT_USER_VISIBILITY = my_type -`: func() { - assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("public", "limited", "private"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, +`: {structs.VisibleTypePublic, []structs.VisibilityString{"public", "limited", "private"}}, ` [service] DEFAULT_USER_VISIBILITY = public ALLOWED_USER_VISIBILITY_MODES = public, limit, privated -`: func() { - assert.Equal(t, structs.VisibleTypePublic, Service.DefaultUserVisibilityMode) - assert.Equal(t, visibleTypeSlice("public"), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) - }, +`: {structs.VisibleTypePublic, []structs.VisibilityString{"public"}}, } - for tc, fn := range testCases { + for tc, expected := range testCases { t.Run(tc, func(t *testing.T) { Service.AllowedUserVisibilityModesSlice = []bool{true, true, true} Service.DefaultUserVisibilityMode = structs.VisibleTypePublic cfg, err := NewConfigProviderFromData(tc) assert.NoError(t, err) loadServiceFrom(cfg) - fn() + assert.Equal(t, expected.defaultMode, Service.DefaultUserVisibilityMode) + assert.Equal(t, visibleTypeSlice(expected.allowedModes...), Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()) }) } } diff --git a/modules/setting/storage_test.go b/modules/setting/storage_test.go index 6f5a54c41c1..92f7208a111 100644 --- a/modules/setting/storage_test.go +++ b/modules/setting/storage_test.go @@ -156,185 +156,85 @@ func testLocalStoragePath(t *testing.T, appDataPath, iniStr string, cases []test } func Test_getStorageInheritStorageTypeLocal(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` + for _, tc := range []struct { + name, ini, basePath, archivePath string + }{ + {"Default", ` [storage] STORAGE_TYPE = local -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"}, - {loadLFSFrom, &LFS.Storage, "/appdata/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/appdata/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/repo-archive"}, - {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPath(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/appdata", "/appdata/repo-archive"}, + {"Path", ` [storage] STORAGE_TYPE = local PATH = /data/gitea -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"}, - {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"}, - {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalRelativePath(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/data/gitea", "/data/gitea/repo-archive"}, + {"RelativePath", ` [storage] STORAGE_TYPE = local PATH = storages -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/appdata/storages/attachments"}, - {loadLFSFrom, &LFS.Storage, "/appdata/storages/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/storages/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/appdata/storages/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/storages/repo-archive"}, - {loadActionsFrom, &Actions.LogStorage, "/appdata/storages/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/appdata/storages/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/storages/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPathOverride(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/appdata/storages", "/appdata/storages/repo-archive"}, + {"PathOverride", ` [storage] STORAGE_TYPE = local PATH = /data/gitea [repo-archive] PATH = /data/gitea/the-archives-dir -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"}, - {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"}, - {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPathOverrideEmpty(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/data/gitea", "/data/gitea/the-archives-dir"}, + {"PathOverrideEmpty", ` [storage] STORAGE_TYPE = local PATH = /data/gitea [repo-archive] -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"}, - {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/repo-archive"}, - {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalRelativePathOverride(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/data/gitea", "/data/gitea/repo-archive"}, + {"RelativePathOverride", ` [storage] STORAGE_TYPE = local PATH = /data/gitea [repo-archive] PATH = the-archives-dir -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/data/gitea/attachments"}, - {loadLFSFrom, &LFS.Storage, "/data/gitea/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/data/gitea/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/data/gitea/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/the-archives-dir"}, - {loadActionsFrom, &Actions.LogStorage, "/data/gitea/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/data/gitea/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/data/gitea/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPathOverride3(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/data/gitea", "/data/gitea/the-archives-dir"}, + {"PathOverride3", ` [storage.repo-archive] STORAGE_TYPE = local PATH = /data/gitea/archives -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"}, - {loadLFSFrom, &LFS.Storage, "/appdata/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/appdata/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"}, - {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPathOverride3_5(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/appdata", "/data/gitea/archives"}, + {"PathOverride3_5", ` [storage.repo-archive] STORAGE_TYPE = local PATH = a-relative-path -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"}, - {loadLFSFrom, &LFS.Storage, "/appdata/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/appdata/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/appdata/a-relative-path"}, - {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPathOverride4(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/appdata", "/appdata/a-relative-path"}, + {"PathOverride4", ` [storage.repo-archive] STORAGE_TYPE = local PATH = /data/gitea/archives [repo-archive] PATH = /tmp/gitea/archives -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"}, - {loadLFSFrom, &LFS.Storage, "/appdata/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/appdata/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/tmp/gitea/archives"}, - {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"}, - }) -} - -func Test_getStorageInheritStorageTypeLocalPathOverride5(t *testing.T) { - testLocalStoragePath(t, "/appdata", ` +`, "/appdata", "/tmp/gitea/archives"}, + {"PathOverride5", ` [storage.repo-archive] STORAGE_TYPE = local PATH = /data/gitea/archives [repo-archive] -`, []testLocalStoragePathCase{ - {loadAttachmentFrom, &Attachment.Storage, "/appdata/attachments"}, - {loadLFSFrom, &LFS.Storage, "/appdata/lfs"}, - {loadActionsFrom, &Actions.ArtifactStorage, "/appdata/actions_artifacts"}, - {loadPackagesFrom, &Packages.Storage, "/appdata/packages"}, - {loadRepoArchiveFrom, &RepoArchive.Storage, "/data/gitea/archives"}, - {loadActionsFrom, &Actions.LogStorage, "/appdata/actions_log"}, - {loadAvatarsFrom, &Avatar.Storage, "/appdata/avatars"}, - {loadRepoAvatarFrom, &RepoAvatar.Storage, "/appdata/repo-avatars"}, - }) +`, "/appdata", "/data/gitea/archives"}, + } { + t.Run(tc.name, func(t *testing.T) { + testLocalStoragePath(t, "/appdata", tc.ini, []testLocalStoragePathCase{ + {loadAttachmentFrom, &Attachment.Storage, tc.basePath + "/attachments"}, + {loadLFSFrom, &LFS.Storage, tc.basePath + "/lfs"}, + {loadActionsFrom, &Actions.ArtifactStorage, tc.basePath + "/actions_artifacts"}, + {loadPackagesFrom, &Packages.Storage, tc.basePath + "/packages"}, + {loadRepoArchiveFrom, &RepoArchive.Storage, tc.archivePath}, + {loadActionsFrom, &Actions.LogStorage, tc.basePath + "/actions_log"}, + {loadAvatarsFrom, &Avatar.Storage, tc.basePath + "/avatars"}, + {loadRepoAvatarFrom, &RepoAvatar.Storage, tc.basePath + "/repo-avatars"}, + }) + }) + } } func Test_getStorageInheritStorageTypeLocalPathOverride72(t *testing.T) { diff --git a/modules/sitemap/sitemap_test.go b/modules/sitemap/sitemap_test.go index 9ff97939014..75660a219f9 100644 --- a/modules/sitemap/sitemap_test.go +++ b/modules/sitemap/sitemap_test.go @@ -15,151 +15,90 @@ import ( func TestNewSitemap(t *testing.T) { ts := time.Unix(1651322008, 0).UTC() - - tests := []struct { - name string - urls []URL - want string - wantErr string + for _, sitemap := range []struct { + name string + newSitemap func() *Sitemap + root, entry string + wantManyErr string + wantSizeErr string }{ { - name: "empty", - urls: []URL{}, - want: xml.Header + `` + - "" + - "\n", + "NewSitemap", NewSitemap, "urlset", "url", + "sitemap contains 50001 URLs, but only 50000 are allowed", + "sitemap has 52428932 bytes, but only 52428800 are allowed", }, { - name: "regular", - urls: []URL{ - {URL: "https://gitea.io/test1", LastMod: &ts}, - }, - want: xml.Header + `` + - "https://gitea.io/test12022-04-30T12:33:28Z" + - "\n", + "NewSitemapIndex", NewSitemapIndex, "sitemapindex", "sitemap", + "sitemap contains 50001 sub-sitemaps, but only 50000 are allowed", + "sitemap has 52428952 bytes, but only 52428800 are allowed", }, - { - name: "without lastmod", - urls: []URL{ - {URL: "https://gitea.io/test1"}, - }, - want: xml.Header + `` + - "https://gitea.io/test1" + - "\n", - }, - { - name: "multiple", - urls: []URL{ - {URL: "https://gitea.io/test1", LastMod: &ts}, - {URL: "https://gitea.io/test2", LastMod: nil}, - }, - want: xml.Header + `` + - "https://gitea.io/test12022-04-30T12:33:28Z" + - "https://gitea.io/test2" + - "\n", - }, - { - name: "too many urls", - urls: make([]URL, 50001), - wantErr: "sitemap contains 50001 URLs, but only 50000 are allowed", - }, - { - name: "too big file", - urls: []URL{ - {URL: strings.Repeat("b", 50*1024*1024+1)}, - }, - wantErr: "sitemap has 52428932 bytes, but only 52428800 are allowed", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := NewSitemap() - for _, url := range tt.urls { - s.Add(url) + } { + t.Run(sitemap.name, func(t *testing.T) { + entryStart, entryEnd := "<"+sitemap.entry+">", "" + tests := []struct { + name string + urls []URL + want string + wantErr string + }{ + { + name: "empty", + urls: []URL{}, + }, + { + name: "regular", + urls: []URL{ + {URL: "https://gitea.io/test1", LastMod: &ts}, + }, + want: entryStart + "https://gitea.io/test12022-04-30T12:33:28Z" + entryEnd, + }, + { + name: "without lastmod", + urls: []URL{ + {URL: "https://gitea.io/test1"}, + }, + want: entryStart + "https://gitea.io/test1" + entryEnd, + }, + { + name: "multiple", + urls: []URL{ + {URL: "https://gitea.io/test1", LastMod: &ts}, + {URL: "https://gitea.io/test2", LastMod: nil}, + }, + want: entryStart + "https://gitea.io/test12022-04-30T12:33:28Z" + entryEnd + + entryStart + "https://gitea.io/test2" + entryEnd, + }, + { + name: "too many " + sitemap.entry + "s", + urls: make([]URL, 50001), + wantErr: sitemap.wantManyErr, + }, + { + name: "too big file", + urls: []URL{ + {URL: strings.Repeat("b", 50*1024*1024+1)}, + }, + wantErr: sitemap.wantSizeErr, + }, } - buf := &bytes.Buffer{} - _, err := s.WriteTo(buf) - if tt.wantErr != "" { - assert.EqualError(t, err, tt.wantErr) - } else { - assert.NoError(t, err) - assert.Equalf(t, tt.want, buf.String(), "NewSitemap()") - } - }) - } -} - -func TestNewSitemapIndex(t *testing.T) { - ts := time.Unix(1651322008, 0).UTC() - - tests := []struct { - name string - urls []URL - want string - wantErr string - }{ - { - name: "empty", - urls: []URL{}, - want: xml.Header + `` + - "" + - "\n", - }, - { - name: "regular", - urls: []URL{ - {URL: "https://gitea.io/test1", LastMod: &ts}, - }, - want: xml.Header + `` + - "https://gitea.io/test12022-04-30T12:33:28Z" + - "\n", - }, - { - name: "without lastmod", - urls: []URL{ - {URL: "https://gitea.io/test1"}, - }, - want: xml.Header + `` + - "https://gitea.io/test1" + - "\n", - }, - { - name: "multiple", - urls: []URL{ - {URL: "https://gitea.io/test1", LastMod: &ts}, - {URL: "https://gitea.io/test2", LastMod: nil}, - }, - want: xml.Header + `` + - "https://gitea.io/test12022-04-30T12:33:28Z" + - "https://gitea.io/test2" + - "\n", - }, - { - name: "too many sitemaps", - urls: make([]URL, 50001), - wantErr: "sitemap contains 50001 sub-sitemaps, but only 50000 are allowed", - }, - { - name: "too big file", - urls: []URL{ - {URL: strings.Repeat("b", 50*1024*1024+1)}, - }, - wantErr: "sitemap has 52428952 bytes, but only 52428800 are allowed", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := NewSitemapIndex() - for _, url := range tt.urls { - s.Add(url) - } - buf := &bytes.Buffer{} - _, err := s.WriteTo(buf) - if tt.wantErr != "" { - assert.EqualError(t, err, tt.wantErr) - } else { - assert.NoError(t, err) - assert.Equalf(t, tt.want, buf.String(), "NewSitemapIndex()") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := sitemap.newSitemap() + for _, url := range tt.urls { + s.Add(url) + } + buf := &bytes.Buffer{} + _, err := s.WriteTo(buf) + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) + } else { + assert.NoError(t, err) + assert.Equalf(t, + xml.Header+"<"+sitemap.root+` xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`+tt.want+"\n", + buf.String(), sitemap.name+"()", + ) + } + }) } }) } diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index 0116e89026f..0c597078f9d 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -52,18 +52,21 @@ func TestMinioCredentials(t *testing.T) { FakeEndpoint = "http://localhost" ) - t.Run("Static Credentials", func(t *testing.T) { - cfg := setting.MinioStorageConfig{ - AccessKeyID: ExpectedAccessKey, - SecretAccessKey: ExpectedSecretAccessKey, - IamEndpoint: FakeEndpoint, - } + checkCredentials := func(t *testing.T, cfg setting.MinioStorageConfig, suffix string) { creds := buildMinioCredentials(cfg) v, err := creds.Get() assert.NoError(t, err) - assert.Equal(t, ExpectedAccessKey, v.AccessKeyID) - assert.Equal(t, ExpectedSecretAccessKey, v.SecretAccessKey) + assert.Equal(t, ExpectedAccessKey+suffix, v.AccessKeyID) + assert.Equal(t, ExpectedSecretAccessKey+suffix, v.SecretAccessKey) + } + + t.Run("Static Credentials", func(t *testing.T) { + checkCredentials(t, setting.MinioStorageConfig{ + AccessKeyID: ExpectedAccessKey, + SecretAccessKey: ExpectedSecretAccessKey, + IamEndpoint: FakeEndpoint, + }, "") }) t.Run("Chain", func(t *testing.T) { @@ -75,24 +78,14 @@ func TestMinioCredentials(t *testing.T) { t.Setenv("MINIO_ACCESS_KEY", ExpectedAccessKey+"Minio") t.Setenv("MINIO_SECRET_KEY", ExpectedSecretAccessKey+"Minio") - creds := buildMinioCredentials(cfg) - v, err := creds.Get() - - assert.NoError(t, err) - assert.Equal(t, ExpectedAccessKey+"Minio", v.AccessKeyID) - assert.Equal(t, ExpectedSecretAccessKey+"Minio", v.SecretAccessKey) + checkCredentials(t, cfg, "Minio") }) t.Run("EnvAWS", func(t *testing.T) { t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS") t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS") - creds := buildMinioCredentials(cfg) - v, err := creds.Get() - - assert.NoError(t, err) - assert.Equal(t, ExpectedAccessKey+"AWS", v.AccessKeyID) - assert.Equal(t, ExpectedSecretAccessKey+"AWS", v.SecretAccessKey) + checkCredentials(t, cfg, "AWS") }) t.Run("FileMinio", func(t *testing.T) { @@ -100,12 +93,7 @@ func TestMinioCredentials(t *testing.T) { t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json") t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake") - creds := buildMinioCredentials(cfg) - v, err := creds.Get() - - assert.NoError(t, err) - assert.Equal(t, ExpectedAccessKey+"MinioFile", v.AccessKeyID) - assert.Equal(t, ExpectedSecretAccessKey+"MinioFile", v.SecretAccessKey) + checkCredentials(t, cfg, "MinioFile") }) t.Run("FileAWS", func(t *testing.T) { @@ -113,12 +101,7 @@ func TestMinioCredentials(t *testing.T) { t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json") t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials") - creds := buildMinioCredentials(cfg) - v, err := creds.Get() - - assert.NoError(t, err) - assert.Equal(t, ExpectedAccessKey+"AWSFile", v.AccessKeyID) - assert.Equal(t, ExpectedSecretAccessKey+"AWSFile", v.SecretAccessKey) + checkCredentials(t, cfg, "AWSFile") }) t.Run("IAM", func(t *testing.T) { @@ -139,14 +122,9 @@ func TestMinioCredentials(t *testing.T) { defer server.Close() // Use the provided EC2 Instance Metadata server - creds := buildMinioCredentials(setting.MinioStorageConfig{ + checkCredentials(t, setting.MinioStorageConfig{ IamEndpoint: server.URL, - }) - v, err := creds.Get() - - assert.NoError(t, err) - assert.Equal(t, ExpectedAccessKey+"IAM", v.AccessKeyID) - assert.Equal(t, ExpectedSecretAccessKey+"IAM", v.SecretAccessKey) + }, "IAM") }) }) } diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go index 68665eb4b51..1a23050402c 100644 --- a/modules/storage/storage_test.go +++ b/modules/storage/storage_test.go @@ -15,37 +15,22 @@ import ( ) func TestObjectStoragePath(t *testing.T) { - base := "" - assert.Empty(t, buildObjectStorePath(base, "/")) - assert.Empty(t, buildObjectStorePath(base, ".")) - assert.Equal(t, "a", buildObjectStorePath(base, "/a")) - assert.Equal(t, "a/b", buildObjectStorePath(base, "/a/b/")) - assert.Empty(t, buildObjectStorePathPrefix(base, "")) - assert.Equal(t, "a/", buildObjectStorePathPrefix(base, "/a/")) - - base = "/" - assert.Empty(t, buildObjectStorePath(base, "/")) - assert.Empty(t, buildObjectStorePath(base, ".")) - assert.Equal(t, "a", buildObjectStorePath(base, "/a")) - assert.Equal(t, "a/b", buildObjectStorePath(base, "/a/b/")) - assert.Empty(t, buildObjectStorePathPrefix(base, "")) - assert.Equal(t, "a/", buildObjectStorePathPrefix(base, "/a/")) - - base = "/base" - assert.Equal(t, "base", buildObjectStorePath(base, "/")) - assert.Equal(t, "base", buildObjectStorePath(base, ".")) - assert.Equal(t, "base/a", buildObjectStorePath(base, "/a")) - assert.Equal(t, "base/a/b", buildObjectStorePath(base, "/a/b/")) - assert.Equal(t, "base/", buildObjectStorePathPrefix(base, "")) - assert.Equal(t, "base/a/", buildObjectStorePathPrefix(base, "/a/")) - - base = "/base/" - assert.Equal(t, "base", buildObjectStorePath(base, "/")) - assert.Equal(t, "base", buildObjectStorePath(base, ".")) - assert.Equal(t, "base/a", buildObjectStorePath(base, "/a")) - assert.Equal(t, "base/a/b", buildObjectStorePath(base, "/a/b/")) - assert.Equal(t, "base/", buildObjectStorePathPrefix(base, "")) - assert.Equal(t, "base/a/", buildObjectStorePathPrefix(base, "/a/")) + for _, base := range []string{"", "/"} { + assert.Empty(t, buildObjectStorePath(base, "/")) + assert.Empty(t, buildObjectStorePath(base, ".")) + assert.Equal(t, "a", buildObjectStorePath(base, "/a")) + assert.Equal(t, "a/b", buildObjectStorePath(base, "/a/b/")) + assert.Empty(t, buildObjectStorePathPrefix(base, "")) + assert.Equal(t, "a/", buildObjectStorePathPrefix(base, "/a/")) + } + for _, base := range []string{"/base", "/base/"} { + assert.Equal(t, "base", buildObjectStorePath(base, "/")) + assert.Equal(t, "base", buildObjectStorePath(base, ".")) + assert.Equal(t, "base/a", buildObjectStorePath(base, "/a")) + assert.Equal(t, "base/a/b", buildObjectStorePath(base, "/a/b/")) + assert.Equal(t, "base/", buildObjectStorePathPrefix(base, "")) + assert.Equal(t, "base/a/", buildObjectStorePathPrefix(base, "/a/")) + } } func testStorageAccess(t *testing.T, objStore ObjectStorage) { diff --git a/modules/structs/hook_test.go b/modules/structs/hook_test.go index 269b3933975..2e4c4745a64 100644 --- a/modules/structs/hook_test.go +++ b/modules/structs/hook_test.go @@ -12,21 +12,33 @@ import ( "github.com/stretchr/testify/require" ) -func TestPullRequestPayloadSynchronizeBeforeAfter(t *testing.T) { - payload := &PullRequestPayload{ - Action: HookIssueSynchronized, - Before: "1111111111111111111111111111111111111111", - After: "2222222222222222222222222222222222222222", - Index: 12, - } - - data, err := json.Marshal(payload) - require.NoError(t, err) - - assert.JSONEq(t, `{ +func TestPullRequestPayload(t *testing.T) { + for _, tt := range []struct { + payload PullRequestPayload + want string + }{ + { + payload: PullRequestPayload{ + Action: HookIssueSynchronized, + Before: "1111111111111111111111111111111111111111", + After: "2222222222222222222222222222222222222222", + Index: 12, + }, + want: ` "action": "synchronized", "before": "1111111111111111111111111111111111111111", - "after": "2222222222222222222222222222222222222222", + "after": "2222222222222222222222222222222222222222",`, + }, + { + payload: PullRequestPayload{Action: HookIssueOpened, Index: 12}, + want: ` + "action": "opened",`, + }, + } { + t.Run(string(tt.payload.Action), func(t *testing.T) { + data, err := json.Marshal(&tt.payload) + require.NoError(t, err) + assert.JSONEq(t, `{`+tt.want+` "number": 12, "commit_id": "", "pull_request": null, @@ -35,25 +47,6 @@ func TestPullRequestPayloadSynchronizeBeforeAfter(t *testing.T) { "review": null, "sender": null }`, string(data)) -} - -func TestPullRequestPayloadNonSynchronizeOmitsBeforeAfter(t *testing.T) { - payload := &PullRequestPayload{ - Action: HookIssueOpened, - Index: 12, + }) } - - data, err := json.Marshal(payload) - require.NoError(t, err) - - assert.JSONEq(t, `{ - "action": "opened", - "number": 12, - "commit_id": "", - "pull_request": null, - "repository": null, - "requested_reviewer": null, - "review": null, - "sender": null - }`, string(data)) } diff --git a/modules/structs/issue_test.go b/modules/structs/issue_test.go index 19ac643d4fc..17af4833d4d 100644 --- a/modules/structs/issue_test.go +++ b/modules/structs/issue_test.go @@ -46,39 +46,22 @@ func TestIssueTemplateStringSlice_UnmarshalYAML(t *testing.T) { tests := []struct { name string content string - tmpl *IssueTemplate - want *IssueTemplate + labels []string wantErr string }{ { name: "array", content: `labels: ["a", "b", "c"]`, - tmpl: &IssueTemplate{ - Labels: []string{"should_be_overwrote"}, - }, - want: &IssueTemplate{ - Labels: []string{"a", "b", "c"}, - }, + labels: []string{"a", "b", "c"}, }, { name: "string", content: `labels: "a,b,c"`, - tmpl: &IssueTemplate{ - Labels: []string{"should_be_overwrote"}, - }, - want: &IssueTemplate{ - Labels: []string{"a", "b", "c"}, - }, + labels: []string{"a", "b", "c"}, }, { name: "empty", content: `labels:`, - tmpl: &IssueTemplate{ - Labels: []string{"should_be_overwrote"}, - }, - want: &IssueTemplate{ - Labels: nil, - }, }, { name: "error", @@ -87,18 +70,21 @@ labels: a: aa b: bb `, - tmpl: &IssueTemplate{}, wantErr: "yaml: construct errors: line 3: cannot unmarshal !!map into IssueTemplateStringSlice", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := yaml.Unmarshal([]byte(tt.content), tt.tmpl) + tmpl := &IssueTemplate{} + if tt.wantErr == "" { + tmpl.Labels = []string{"should_be_overwrote"} + } + err := yaml.Unmarshal([]byte(tt.content), tmpl) if tt.wantErr != "" { assert.EqualError(t, err, tt.wantErr) } else { assert.NoError(t, err) - assert.Equal(t, tt.want, tt.tmpl) + assert.Equal(t, &IssueTemplate{Labels: tt.labels}, tmpl) } }) } diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go index f04724bfe67..36be81e8786 100644 --- a/modules/templates/helper_test.go +++ b/modules/templates/helper_test.go @@ -15,47 +15,29 @@ import ( ) func TestSubjectBodySeparator(t *testing.T) { - test := func(input, subject, body string) { - loc := mailSubjectSplit.FindStringIndex(input) + for _, testCase := range []struct { + input, subject, body string + }{ + {"Simple\n---------------\nCase", "Simple\n", "\nCase"}, + {"Only\nBody", "", "Only\nBody"}, + {"Minimal\n---\nseparator", "Minimal\n", "\nseparator"}, + {"False --- separator", "", "False --- separator"}, + {"False\n--- separator", "", "False\n--- separator"}, + {"False ---\nseparator", "", "False ---\nseparator"}, + {"With extra spaces\n----- \t \nBody", "With extra spaces\n", "\nBody"}, + {"With leading spaces\n -------\nOnly body", "", "With leading spaces\n -------\nOnly body"}, + {"Multiple\n---\n-------\n---\nSeparators", "Multiple\n", "\n-------\n---\nSeparators"}, + {"Insufficient\n--\nSeparators", "", "Insufficient\n--\nSeparators"}, + } { + loc := mailSubjectSplit.FindStringIndex(testCase.input) if loc == nil { - assert.Empty(t, subject, "no subject found, but one expected") - assert.Equal(t, body, input) + assert.Empty(t, testCase.subject, "no subject found, but one expected") + assert.Equal(t, testCase.body, testCase.input) } else { - assert.Equal(t, subject, input[0:loc[0]]) - assert.Equal(t, body, input[loc[1]:]) + assert.Equal(t, testCase.subject, testCase.input[0:loc[0]]) + assert.Equal(t, testCase.body, testCase.input[loc[1]:]) } } - - test("Simple\n---------------\nCase", - "Simple\n", - "\nCase") - test("Only\nBody", - "", - "Only\nBody") - test("Minimal\n---\nseparator", - "Minimal\n", - "\nseparator") - test("False --- separator", - "", - "False --- separator") - test("False\n--- separator", - "", - "False\n--- separator") - test("False ---\nseparator", - "", - "False ---\nseparator") - test("With extra spaces\n----- \t \nBody", - "With extra spaces\n", - "\nBody") - test("With leading spaces\n -------\nOnly body", - "", - "With leading spaces\n -------\nOnly body") - test("Multiple\n---\n-------\n---\nSeparators", - "Multiple\n", - "\n-------\n---\nSeparators") - test("Insufficient\n--\nSeparators", - "", - "Insufficient\n--\nSeparators") } func TestSanitizeHTML(t *testing.T) { @@ -148,33 +130,20 @@ func TestQueryBuild(t *testing.T) { assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2))) }) - t.Run("replace", func(t *testing.T) { - assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1))) - assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1))) - assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1))) - assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1))) - }) - - t.Run("replace-&", func(t *testing.T) { - assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1))) - assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1))) - assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1))) - assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1))) - }) - - t.Run("delete", func(t *testing.T) { - assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", ""))) - assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", ""))) - assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", ""))) - assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", ""))) - }) - - t.Run("delete-&", func(t *testing.T) { - assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", ""))) - assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", ""))) - assert.Equal(t, "&a=b&c=d", string(QueryBuild("&a=b&c=d&e=f", "e", ""))) - assert.Equal(t, "&a=b&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "k", ""))) - }) + for _, prefix := range []string{"", "&"} { + t.Run("replace"+prefix, func(t *testing.T) { + assert.Equal(t, prefix+"a=1&c=d&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "a", 1))) + assert.Equal(t, prefix+"a=b&c=1&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "c", 1))) + assert.Equal(t, prefix+"a=b&c=d&e=1", string(QueryBuild(prefix+"a=b&c=d&e=f", "e", 1))) + assert.Equal(t, prefix+"a=b&c=d&e=f&k=1", string(QueryBuild(prefix+"a=b&c=d&e=f", "k", 1))) + }) + t.Run("delete"+prefix, func(t *testing.T) { + assert.Equal(t, prefix+"c=d&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "a", ""))) + assert.Equal(t, prefix+"a=b&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "c", ""))) + assert.Equal(t, prefix+"a=b&c=d", string(QueryBuild(prefix+"a=b&c=d&e=f", "e", ""))) + assert.Equal(t, prefix+"a=b&c=d&e=f", string(QueryBuild(prefix+"a=b&c=d&e=f", "k", ""))) + }) + } } const queryNonASCII = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" // all non-letter & non-number chars diff --git a/modules/templates/util_date_test.go b/modules/templates/util_date_test.go index 577d1bba5d5..4e4b448a3e9 100644 --- a/modules/templates/util_date_test.go +++ b/modules/templates/util_date_test.go @@ -39,21 +39,10 @@ func TestDateTime(t *testing.T) { actual = du.FullTime(refTimeStamp) assert.EqualValues(t, `2017-12-31 19:00:00 -05:00`, actual) -} -func TestTimeSince(t *testing.T) { - testTz, _ := time.LoadLocation("America/New_York") - defer test.MockVariableValue(&setting.DefaultUILocation, testTz)() - defer test.MockVariableValue(&setting.IsProd, true)() - defer test.MockVariableValue(&setting.IsInTesting, false)() - - du := NewDateUtils() assert.EqualValues(t, "-", du.TimeSince(nil)) - refTimeStr := "2018-01-01T00:00:00Z" - refTime, _ := time.Parse(time.RFC3339, refTimeStr) - - actual := du.TimeSince(refTime) + actual = du.TimeSince(refTime) assert.EqualValues(t, `2018-01-01 00:00:00 +00:00`, actual) actual = timeSinceTo(&refTime, time.Time{}) diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 7a05284f7a8..b1901b5cf76 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -77,40 +77,18 @@ func TestRenderRepoComment(t *testing.T) { } t.Run("RenderCommitBody", func(t *testing.T) { defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() - type args struct { - msg string - } tests := []struct { name string - args args - want template.HTML + msg string }{ - { - name: "multiple lines", - args: args{ - msg: "first line\nsecond line", - }, - want: "second line", - }, - { - name: "multiple lines with leading newlines", - args: args{ - msg: "\n\n\n\nfirst line\nsecond line", - }, - want: "second line", - }, - { - name: "multiple lines with trailing newlines", - args: args{ - msg: "first line\nsecond line\n\n\n", - }, - want: "second line", - }, + {"multiple lines", "first line\nsecond line"}, + {"multiple lines with leading newlines", "\n\n\n\nfirst line\nsecond line"}, + {"multiple lines with trailing newlines", "first line\nsecond line\n\n\n"}, } ut := newTestRenderUtils(t) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.args.msg, nil) + assert.Equalf(t, template.HTML("second line"), ut.RenderCommitBody(tt.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.msg, nil) }) } diff --git a/modules/util/diff_slice_test.go b/modules/util/diff_slice_test.go index cd61fb571b1..1141cd5d5f8 100644 --- a/modules/util/diff_slice_test.go +++ b/modules/util/diff_slice_test.go @@ -10,65 +10,36 @@ import ( ) func TestDiffSliceBasic(t *testing.T) { - // Typical integer cases - t.Run("additions", func(t *testing.T) { - added, removed := DiffSlice([]int{1, 2}, []int{1, 2, 3}) - assert.Equal(t, []int{3}, added) - assert.Empty(t, removed) - }) - - t.Run("removals", func(t *testing.T) { - added, removed := DiffSlice([]int{1, 2, 3}, []int{1, 2}) - assert.Empty(t, added) - assert.Equal(t, []int{3}, removed) - }) - - t.Run("no changes", func(t *testing.T) { - added, removed := DiffSlice([]int{1, 2}, []int{1, 2}) - assert.Empty(t, added) - assert.Empty(t, removed) - }) - - t.Run("empty slices", func(t *testing.T) { - added, removed := DiffSlice([]int{}, []int{}) - assert.Empty(t, added) - assert.Empty(t, removed) - }) - - t.Run("overlapping elements", func(t *testing.T) { - added, removed := DiffSlice([]int{1, 2, 4}, []int{2, 3, 4}) - assert.Equal(t, []int{3}, added) - assert.Equal(t, []int{1}, removed) - }) -} - -func TestDiffSliceOrderAndDuplicates(t *testing.T) { - oldSlice := []int{1, 2, 2, 3} - newSlice := []int{2, 4, 2, 5} - - added, removed := DiffSlice(oldSlice, newSlice) - assert.Equal(t, []int{4, 5}, added) - assert.Equal(t, []int{1, 3}, removed) -} - -func TestDiffSliceDeduplicatesOutput(t *testing.T) { - // Test case from issue: newSlice contains [4, 4, 5] and oldSlice is [1] - // added should return [4, 5], not [4, 4, 5] - t.Run("deduplicates added", func(t *testing.T) { - added, removed := DiffSlice([]int{1}, []int{4, 4, 5}) - assert.Equal(t, []int{4, 5}, added) - assert.Equal(t, []int{1}, removed) - }) - - t.Run("deduplicates removed", func(t *testing.T) { - added, removed := DiffSlice([]int{1, 1, 2}, []int{3}) - assert.Equal(t, []int{3}, added) - assert.Equal(t, []int{1, 2}, removed) - }) - - t.Run("deduplicates both", func(t *testing.T) { - added, removed := DiffSlice([]int{1, 1, 2, 2}, []int{3, 3, 4, 4}) - assert.Equal(t, []int{3, 4}, added) - assert.Equal(t, []int{1, 2}, removed) - }) + for _, testCase := range []struct { + name string + oldSlice, newSlice []int + expectedAdded, expectedRemoved []int + }{ + // Typical integer cases + {"additions", []int{1, 2}, []int{1, 2, 3}, []int{3}, nil}, + {"removals", []int{1, 2, 3}, []int{1, 2}, nil, []int{3}}, + {"no changes", []int{1, 2}, []int{1, 2}, nil, nil}, + {"empty slices", []int{}, []int{}, nil, nil}, + {"overlapping elements", []int{1, 2, 4}, []int{2, 3, 4}, []int{3}, []int{1}}, + {"order and duplicates", []int{1, 2, 2, 3}, []int{2, 4, 2, 5}, []int{4, 5}, []int{1, 3}}, + // Test case from issue: newSlice contains [4, 4, 5] and oldSlice is [1] + // added should return [4, 5], not [4, 4, 5] + {"deduplicates added", []int{1}, []int{4, 4, 5}, []int{4, 5}, []int{1}}, + {"deduplicates removed", []int{1, 1, 2}, []int{3}, []int{3}, []int{1, 2}}, + {"deduplicates both", []int{1, 1, 2, 2}, []int{3, 3, 4, 4}, []int{3, 4}, []int{1, 2}}, + } { + t.Run(testCase.name, func(t *testing.T) { + added, removed := DiffSlice(testCase.oldSlice, testCase.newSlice) + if testCase.expectedAdded == nil { + assert.Empty(t, added) + } else { + assert.Equal(t, testCase.expectedAdded, added) + } + if testCase.expectedRemoved == nil { + assert.Empty(t, removed) + } else { + assert.Equal(t, testCase.expectedRemoved, removed) + } + }) + } } diff --git a/modules/util/filebuffer/file_backed_buffer_test.go b/modules/util/filebuffer/file_backed_buffer_test.go index 3f13c6ac7bb..721f0fe993c 100644 --- a/modules/util/filebuffer/file_backed_buffer_test.go +++ b/modules/util/filebuffer/file_backed_buffer_test.go @@ -12,24 +12,16 @@ import ( ) func TestFileBackedBuffer(t *testing.T) { - cases := []struct { - MaxMemorySize int - Data string - }{ - {5, "test"}, - {5, "testtest"}, - } - - for _, c := range cases { - buf := New(c.MaxMemorySize, t.TempDir()) - _, err := io.Copy(buf, strings.NewReader(c.Data)) + for _, content := range []string{"test", "testtest"} { + buf := New(5, t.TempDir()) + _, err := io.Copy(buf, strings.NewReader(content)) assert.NoError(t, err) - assert.EqualValues(t, len(c.Data), buf.Size()) + assert.EqualValues(t, len(content), buf.Size()) data, err := io.ReadAll(buf) assert.NoError(t, err) - assert.Equal(t, c.Data, string(data)) + assert.Equal(t, content, string(data)) assert.NoError(t, buf.Close()) } diff --git a/modules/util/io_test.go b/modules/util/io_test.go index 275575463a6..a8cc5a31e38 100644 --- a/modules/util/io_test.go +++ b/modules/util/io_test.go @@ -26,28 +26,23 @@ func TestReadWithLimit(t *testing.T) { bs := []byte("0123456789abcdef") // normal test - buf, err := readWithLimit(bytes.NewBuffer(bs), 5, 2) - assert.NoError(t, err) - assert.Equal(t, []byte("01"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 5) - assert.NoError(t, err) - assert.Equal(t, []byte("01234"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 6) - assert.NoError(t, err) - assert.Equal(t, []byte("012345"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, len(bs)) - assert.NoError(t, err) - assert.Equal(t, []byte("0123456789abcdef"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 100) - assert.NoError(t, err) - assert.Equal(t, []byte("0123456789abcdef"), buf) + for _, testCase := range []struct { + limit int + expected string + }{ + {2, "01"}, + {5, "01234"}, + {6, "012345"}, + {len(bs), "0123456789abcdef"}, + {100, "0123456789abcdef"}, + } { + buf, err := readWithLimit(bytes.NewBuffer(bs), 5, testCase.limit) + assert.NoError(t, err) + assert.Equal(t, []byte(testCase.expected), buf) + } // test with error - buf, err = readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 10) + buf, err := readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 10) assert.NoError(t, err) assert.Equal(t, []byte("0123456789"), buf) diff --git a/modules/util/keypair_test.go b/modules/util/keypair_test.go index f513bce815d..53c7abab3ef 100644 --- a/modules/util/keypair_test.go +++ b/modules/util/keypair_test.go @@ -25,11 +25,6 @@ func TestKeygen(t *testing.T) { assert.Regexp(t, "^-----BEGIN RSA PRIVATE KEY-----.*", priv) assert.Regexp(t, "^-----BEGIN PUBLIC KEY-----.*", pub) -} - -func TestSignUsingKeys(t *testing.T) { - priv, pub, err := GenerateKeyPair(2048) - assert.NoError(t, err) privPem, _ := pem.Decode([]byte(priv)) if privPem == nil || privPem.Type != "RSA PRIVATE KEY" { diff --git a/modules/util/path_test.go b/modules/util/path_test.go index cb38644479d..8b024516263 100644 --- a/modules/util/path_test.go +++ b/modules/util/path_test.go @@ -159,34 +159,19 @@ func TestCleanPath(t *testing.T) { {[]string{`a`}, `a`}, {[]string{`/a/`}, `a`}, {[]string{`../a/`, `../b`, `c/..`, `d`}, `a/b/d`}, - {[]string{`a\..\b`}, `a\..\b`}, {[]string{`a`, ``, `b`}, `a/b`}, {[]string{`a`, `..`, `b`}, `a/b`}, {[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`}, } for _, c := range cases { assert.Equal(t, c.expected, PathJoinRel(c.elems...), "case: %v", c.elems) - } - - cases = []struct { - elems []string - expected string - }{ - {[]string{}, ``}, - {[]string{``}, ``}, - {[]string{`..`}, `.`}, - {[]string{`a`}, `a`}, - {[]string{`/a/`}, `a`}, - {[]string{`../a/`, `../b`, `c/..`, `d`}, `a/b/d`}, - {[]string{`a\..\b`}, `b`}, - {[]string{`a`, ``, `b`}, `a/b`}, - {[]string{`a`, `..`, `b`}, `a/b`}, - {[]string{`lfs`, `repo/..`, `user/../path`}, `lfs/path`}, - } - for _, c := range cases { assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems) } + backslashPath := []string{`a\..\b`} + assert.Equal(t, `a\..\b`, PathJoinRel(backslashPath...), "case: %v", backslashPath) + assert.Equal(t, `b`, PathJoinRelX(backslashPath...), "case: %v", backslashPath) + // for POSIX only, but the result is similar on Windows, because the first element must be an absolute path if consts.IsWindows { cases = []struct { diff --git a/modules/util/slice_test.go b/modules/util/slice_test.go index a910f5edfe1..f01424ebedd 100644 --- a/modules/util/slice_test.go +++ b/modules/util/slice_test.go @@ -27,7 +27,6 @@ func TestSliceSortedEqual(t *testing.T) { assert.True(t, SliceSortedEqual([]int{}, []int{})) assert.True(t, SliceSortedEqual([]int(nil), nil)) assert.True(t, SliceSortedEqual([]int(nil), []int{})) - assert.True(t, SliceSortedEqual([]int{}, []int{})) assert.True(t, SliceSortedEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"})) assert.True(t, SliceSortedEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3})) @@ -47,7 +46,6 @@ func TestSliceRemoveAll(t *testing.T) { assert.ElementsMatch(t, []int{2, 0, 2, 3}, SliceRemoveAll([]int{2, 0, 2, 3}, 4)) assert.Empty(t, SliceRemoveAll([]int{}, 0)) assert.ElementsMatch(t, []int(nil), SliceRemoveAll([]int(nil), 0)) - assert.Empty(t, SliceRemoveAll([]int{}, 0)) assert.ElementsMatch(t, []string{"2", "2", "3"}, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0")) assert.ElementsMatch(t, []float64{2, 2, 3}, SliceRemoveAll([]float64{2, 0, 2, 3}, 0)) diff --git a/modules/util/sync_test.go b/modules/util/sync_test.go index 297328bd91f..51dc8d4717c 100644 --- a/modules/util/sync_test.go +++ b/modules/util/sync_test.go @@ -10,40 +10,31 @@ import ( ) func TestOnceValue(t *testing.T) { - t.Run("RepeatCall", func(t *testing.T) { - callCount := 0 - o := OnceValue[int]{Func: func() int { - callCount++ - return 42 - }} - assert.Equal(t, 42, o.Value()) - assert.Equal(t, 42, o.Value()) - assert.Equal(t, 1, callCount) - o.Reset() - assert.Equal(t, 42, o.Value()) - assert.Equal(t, 2, callCount) - assert.Equal(t, 42, o.Value()) - assert.Equal(t, 2, callCount) - }) - - t.Run("Panic", func(t *testing.T) { - callCount := 0 - doPanic := true - o := OnceValue[int]{Func: func() int { - callCount++ + for _, name := range []string{"RepeatCall", "Panic"} { + t.Run(name, func(t *testing.T) { + callCount := 0 + doPanic := name == "Panic" + o := OnceValue[int]{Func: func() int { + callCount++ + if doPanic { + panic("some error") + } + return 42 + }} if doPanic { - panic("some error") + assert.PanicsWithValue(t, "some error", func() { o.Value() }) + assert.PanicsWithValue(t, "some error", func() { o.Value() }) + } else { + assert.Equal(t, 42, o.Value()) + assert.Equal(t, 42, o.Value()) } - return 42 - }} - assert.PanicsWithValue(t, "some error", func() { o.Value() }) - assert.PanicsWithValue(t, "some error", func() { o.Value() }) - assert.Equal(t, 1, callCount) - doPanic = false - o.Reset() - assert.Equal(t, 42, o.Value()) - assert.Equal(t, 2, callCount) - assert.Equal(t, 42, o.Value()) - assert.Equal(t, 2, callCount) - }) + assert.Equal(t, 1, callCount) + doPanic = false + o.Reset() + assert.Equal(t, 42, o.Value()) + assert.Equal(t, 2, callCount) + assert.Equal(t, 42, o.Value()) + assert.Equal(t, 2, callCount) + }) + } } diff --git a/modules/util/util_test.go b/modules/util/util_test.go index 50daaedaf8f..d87b4f2a3b9 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -40,26 +40,13 @@ func Test_NormalizeEOL(t *testing.T) { "Ends without EOLtoo.", } - buildEOLData := func(data []string, eol string) []byte { - return []byte(strings.Join(data, eol)) + for _, data := range [][]string{data1, data2} { + unix := []byte(strings.Join(data, "\n")) + for _, input := range [][]byte{[]byte(strings.Join(data, "\r\n")), []byte(strings.Join(data, "\r")), unix} { + assert.Equal(t, unix, NormalizeEOL(input)) + } } - dos := buildEOLData(data1, "\r\n") - unix := buildEOLData(data1, "\n") - mac := buildEOLData(data1, "\r") - - assert.Equal(t, unix, NormalizeEOL(dos)) - assert.Equal(t, unix, NormalizeEOL(mac)) - assert.Equal(t, unix, NormalizeEOL(unix)) - - dos = buildEOLData(data2, "\r\n") - unix = buildEOLData(data2, "\n") - mac = buildEOLData(data2, "\r") - - assert.Equal(t, unix, NormalizeEOL(dos)) - assert.Equal(t, unix, NormalizeEOL(mac)) - assert.Equal(t, unix, NormalizeEOL(unix)) - assert.Equal(t, []byte("one liner"), NormalizeEOL([]byte("one liner"))) assert.Equal(t, []byte("\n"), NormalizeEOL([]byte("\n"))) assert.Equal(t, []byte("\ntwo liner"), NormalizeEOL([]byte("\ntwo liner"))) @@ -103,17 +90,11 @@ func Test_RandomString(t *testing.T) { } func Test_RandomBytes(t *testing.T) { - bytes1 := CryptoRandomBytes(32) - - bytes2 := CryptoRandomBytes(32) - - assert.NotEqual(t, bytes1, bytes2) - - bytes3 := CryptoRandomBytes(256) - - bytes4 := CryptoRandomBytes(256) - - assert.NotEqual(t, bytes3, bytes4) + for _, length := range []int64{32, 256} { + bytes1 := CryptoRandomBytes(length) + bytes2 := CryptoRandomBytes(length) + assert.NotEqual(t, bytes1, bytes2) + } } // Test case for any function which accepts and returns a single string. diff --git a/modules/validation/refname_test.go b/modules/validation/refname_test.go index 19e5cc5b915..3cf6c51aacb 100644 --- a/modules/validation/refname_test.go +++ b/modules/validation/refname_test.go @@ -8,249 +8,33 @@ import ( ) func Test_GitRefNameValidation(t *testing.T) { - gitRefNameValidationTestCases := []validationTestCase{ - { - description: "Reference name contains only characters", - data: &TestForm{ - BranchName: "test", - }, - }, - { - description: "Reference name contains single slash", - data: &TestForm{ - BranchName: "feature/test", - }, - }, - { - description: "Reference name has allowed special characters", - data: &TestForm{ - BranchName: "debian/1%1.6.0-2", - }, - }, - { - description: "Reference name contains backslash", - data: &TestForm{ - BranchName: "feature\\test", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name starts with dot", - data: &TestForm{ - BranchName: ".test", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name ends with dot", - data: &TestForm{ - BranchName: "test.", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name starts with slash", - data: &TestForm{ - BranchName: "/test", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name ends with slash", - data: &TestForm{ - BranchName: "test/", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name ends with .lock", - data: &TestForm{ - BranchName: "test.lock", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name contains multiple consecutive dots", - data: &TestForm{ - BranchName: "te..st", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name contains multiple consecutive slashes", - data: &TestForm{ - BranchName: "te//st", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name is single @", - data: &TestForm{ - BranchName: "@", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has @{", - data: &TestForm{ - BranchName: "branch@{", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character ~", - data: &TestForm{ - BranchName: "~debian/1%1.6.0-2", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character *", - data: &TestForm{ - BranchName: "*debian/1%1.6.0-2", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character ?", - data: &TestForm{ - BranchName: "?debian/1%1.6.0-2", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character ^", - data: &TestForm{ - BranchName: "^debian/1%1.6.0-2", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character :", - data: &TestForm{ - BranchName: "debian:jessie", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character (whitespace)", - data: &TestForm{ - BranchName: "debian jessie", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - { - description: "Reference name has unallowed special character [", - data: &TestForm{ - BranchName: "debian[jessie", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"BranchName"}, - Classification: ErrGitRefName, - Message: "GitRefName", - }, - }, - }, - } - - for _, testCase := range gitRefNameValidationTestCases { + expectedErrors := BindingErrors{{ + FieldNames: []string{"BranchName"}, + Classification: ErrGitRefName, + Message: "GitRefName", + }} + for _, testCase := range []validationTestCase{ + {"Reference name contains only characters", &TestForm{BranchName: "test"}, nil}, + {"Reference name contains single slash", &TestForm{BranchName: "feature/test"}, nil}, + {"Reference name has allowed special characters", &TestForm{BranchName: "debian/1%1.6.0-2"}, nil}, + {"Reference name contains backslash", &TestForm{BranchName: "feature\\test"}, expectedErrors}, + {"Reference name starts with dot", &TestForm{BranchName: ".test"}, expectedErrors}, + {"Reference name ends with dot", &TestForm{BranchName: "test."}, expectedErrors}, + {"Reference name starts with slash", &TestForm{BranchName: "/test"}, expectedErrors}, + {"Reference name ends with slash", &TestForm{BranchName: "test/"}, expectedErrors}, + {"Reference name ends with .lock", &TestForm{BranchName: "test.lock"}, expectedErrors}, + {"Reference name contains multiple consecutive dots", &TestForm{BranchName: "te..st"}, expectedErrors}, + {"Reference name contains multiple consecutive slashes", &TestForm{BranchName: "te//st"}, expectedErrors}, + {"Reference name is single @", &TestForm{BranchName: "@"}, expectedErrors}, + {"Reference name has @{", &TestForm{BranchName: "branch@{"}, expectedErrors}, + {"Reference name has unallowed special character ~", &TestForm{BranchName: "~debian/1%1.6.0-2"}, expectedErrors}, + {"Reference name has unallowed special character *", &TestForm{BranchName: "*debian/1%1.6.0-2"}, expectedErrors}, + {"Reference name has unallowed special character ?", &TestForm{BranchName: "?debian/1%1.6.0-2"}, expectedErrors}, + {"Reference name has unallowed special character ^", &TestForm{BranchName: "^debian/1%1.6.0-2"}, expectedErrors}, + {"Reference name has unallowed special character :", &TestForm{BranchName: "debian:jessie"}, expectedErrors}, + {"Reference name has unallowed special character (whitespace)", &TestForm{BranchName: "debian jessie"}, expectedErrors}, + {"Reference name has unallowed special character [", &TestForm{BranchName: "debian[jessie"}, expectedErrors}, + } { t.Run(testCase.description, func(t *testing.T) { performValidationTest(t, testCase) }) diff --git a/modules/validation/validurl_test.go b/modules/validation/validurl_test.go index d2f0eba3994..6dd27015554 100644 --- a/modules/validation/validurl_test.go +++ b/modules/validation/validurl_test.go @@ -8,92 +8,22 @@ import ( ) func Test_ValidURLValidation(t *testing.T) { - urlValidationTestCases := []validationTestCase{ - { - description: "Empty URL", - data: &TestForm{ - URL: "", - }, - }, - { - description: "URL without port", - data: &TestForm{ - URL: "http://test.lan/", - }, - }, - { - description: "URL with port", - data: &TestForm{ - URL: "http://test.lan:3000/", - }, - }, - { - description: "URL with IPv6 address without port", - data: &TestForm{ - URL: "http://[::1]/", - }, - }, - { - description: "URL with IPv6 address with port", - data: &TestForm{ - URL: "http://[::1]:3000/", - }, - }, - { - description: "Invalid URL", - data: &TestForm{ - URL: "http//test.lan/", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"URL"}, - Classification: "UrlError", - Message: "Url", - }, - }, - }, - { - description: "Invalid schema", - data: &TestForm{ - URL: "ftp://test.lan/", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"URL"}, - Classification: "UrlError", - Message: "Url", - }, - }, - }, - { - description: "Invalid port", - data: &TestForm{ - URL: "http://test.lan:3x4/", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"URL"}, - Classification: "UrlError", - Message: "Url", - }, - }, - }, - { - description: "Invalid port with IPv6 address", - data: &TestForm{ - URL: "http://[::1]:3x4/", - }, - expectedErrors: BindingErrors{ - BindingError{ - FieldNames: []string{"URL"}, - Classification: "UrlError", - Message: "Url", - }, - }, - }, - } - - for _, testCase := range urlValidationTestCases { + expectedErrors := BindingErrors{{ + FieldNames: []string{"URL"}, + Classification: "UrlError", + Message: "Url", + }} + for _, testCase := range []validationTestCase{ + {"Empty URL", &TestForm{URL: ""}, nil}, + {"URL without port", &TestForm{URL: "http://test.lan/"}, nil}, + {"URL with port", &TestForm{URL: "http://test.lan:3000/"}, nil}, + {"URL with IPv6 address without port", &TestForm{URL: "http://[::1]/"}, nil}, + {"URL with IPv6 address with port", &TestForm{URL: "http://[::1]:3000/"}, nil}, + {"Invalid URL", &TestForm{URL: "http//test.lan/"}, expectedErrors}, + {"Invalid schema", &TestForm{URL: "ftp://test.lan/"}, expectedErrors}, + {"Invalid port", &TestForm{URL: "http://test.lan:3x4/"}, expectedErrors}, + {"Invalid port with IPv6 address", &TestForm{URL: "http://[::1]:3x4/"}, expectedErrors}, + } { t.Run(testCase.description, func(t *testing.T) { performValidationTest(t, testCase) }) diff --git a/modules/web/routemock_test.go b/modules/web/routemock_test.go index 0db19f7827b..9f660882376 100644 --- a/modules/web/routemock_test.go +++ b/modules/web/routemock_test.go @@ -28,43 +28,31 @@ func TestRouteMock(t *testing.T) { } r.Get("/foo", middleware1, RouterMockPoint("mock-point"), middleware2, handler) - // normal request - recorder := httptest.NewRecorder() - req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) - assert.NoError(t, err) - r.ServeHTTP(recorder, req) - assert.Len(t, recorder.Header(), 3) - assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2")) - assert.Equal(t, "h", recorder.Header().Get("X-Test-Handler")) - RouteMockReset() - - // mock at "mock-point" - RouteMock("mock-point", func(resp http.ResponseWriter, req *http.Request) { - resp.Header().Set("X-Test-MockPoint", "a") - resp.WriteHeader(http.StatusOK) - }) - recorder = httptest.NewRecorder() - req, err = http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) - assert.NoError(t, err) - r.ServeHTTP(recorder, req) - assert.Len(t, recorder.Header(), 2) - assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.Equal(t, "a", recorder.Header().Get("X-Test-MockPoint")) - RouteMockReset() - - // mock at MockAfterMiddlewares - RouteMock(MockAfterMiddlewares, func(resp http.ResponseWriter, req *http.Request) { - resp.Header().Set("X-Test-MockPoint", "b") - resp.WriteHeader(http.StatusOK) - }) - recorder = httptest.NewRecorder() - req, err = http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) - assert.NoError(t, err) - r.ServeHTTP(recorder, req) - assert.Len(t, recorder.Header(), 3) - assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2")) - assert.Equal(t, "b", recorder.Header().Get("X-Test-MockPoint")) - RouteMockReset() + for _, tc := range []struct { + mockPoint, mockValue string + headers [][2]string + }{ + // normal request + {headers: [][2]string{{"X-Test-Middleware1", "m1"}, {"X-Test-Middleware2", "m2"}, {"X-Test-Handler", "h"}}}, + // mock at "mock-point" + {mockPoint: "mock-point", mockValue: "a", headers: [][2]string{{"X-Test-Middleware1", "m1"}, {"X-Test-MockPoint", "a"}}}, + // mock at MockAfterMiddlewares + {mockPoint: MockAfterMiddlewares, mockValue: "b", headers: [][2]string{{"X-Test-Middleware1", "m1"}, {"X-Test-Middleware2", "m2"}, {"X-Test-MockPoint", "b"}}}, + } { + if tc.mockPoint != "" { + RouteMock(tc.mockPoint, func(resp http.ResponseWriter, req *http.Request) { + resp.Header().Set("X-Test-MockPoint", tc.mockValue) + resp.WriteHeader(http.StatusOK) + }) + } + recorder := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) + assert.NoError(t, err) + r.ServeHTTP(recorder, req) + assert.Len(t, recorder.Header(), len(tc.headers)) + for _, header := range tc.headers { + assert.Equal(t, header[1], recorder.Header().Get(header[0])) + } + RouteMockReset() + } } diff --git a/modules/web/router_test.go b/modules/web/router_test.go index 2dceedbb276..c1453217232 100644 --- a/modules/web/router_test.go +++ b/modules/web/router_test.go @@ -208,30 +208,18 @@ func TestRouter(t *testing.T) { method: "GET", handlerMarks: []string{"not-found:/api/v1"}, }) - testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches", resultStruct{ - method: "GET", - pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"}, - }) - - testRoute(t, "POST /api/v1/repos/the-user/the-repo/branches", resultStruct{ - method: "POST", - pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"}, - }) - - testRoute(t, "GET /api/v1/repos/the-user/the-repo/branches/master", resultStruct{ - method: "GET", - pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"}, - }) - - testRoute(t, "PATCH /api/v1/repos/the-user/the-repo/branches/master", resultStruct{ - method: "PATCH", - pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"}, - }) - - testRoute(t, "DELETE /api/v1/repos/the-user/the-repo/branches/master", resultStruct{ - method: "DELETE", - pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"}, - }) + for _, method := range []string{"GET", "POST"} { + testRoute(t, method+" /api/v1/repos/the-user/the-repo/branches", resultStruct{ + method: method, + pathParams: map[string]string{"username": "the-user", "reponame": "the-repo"}, + }) + } + for _, method := range []string{"GET", "PATCH", "DELETE"} { + testRoute(t, method+" /api/v1/repos/the-user/the-repo/branches/master", resultStruct{ + method: method, + pathParams: map[string]string{"username": "the-user", "reponame": "the-repo", "name": "master"}, + }) + } }) t.Run("MatchPath", func(t *testing.T) { diff --git a/modules/zstd/zstd_test.go b/modules/zstd/zstd_test.go index 4c3f059103f..f7da0af0f7b 100644 --- a/modules/zstd/zstd_test.go +++ b/modules/zstd/zstd_test.go @@ -21,47 +21,35 @@ func TestWriterReader(t *testing.T) { result := bytes.NewBuffer(nil) - t.Run("regular", func(t *testing.T) { - result.Reset() - writer, err := NewWriter(result) - require.NoError(t, err) + for _, testCase := range []struct { + name string + writerOptions []WriterOption + readerOptions []ReaderOption + }{ + {"regular", nil, nil}, + {"with options", []WriterOption{WithEncoderLevel(SpeedBestCompression)}, []ReaderOption{WithDecoderLowmem(true)}}, + } { + t.Run(testCase.name, func(t *testing.T) { + result.Reset() + writer, err := NewWriter(result, testCase.writerOptions...) + require.NoError(t, err) - _, err = io.Copy(writer, bytes.NewReader(testData)) - require.NoError(t, err) - require.NoError(t, writer.Close()) + _, err = io.Copy(writer, bytes.NewReader(testData)) + require.NoError(t, err) + require.NoError(t, writer.Close()) - t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) + t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) - reader, err := NewReader(result) - require.NoError(t, err) + reader, err := NewReader(result, testCase.readerOptions...) + require.NoError(t, err) - data, err := io.ReadAll(reader) - require.NoError(t, err) - require.NoError(t, reader.Close()) + data, err := io.ReadAll(reader) + require.NoError(t, err) + require.NoError(t, reader.Close()) - assert.Equal(t, testData, data) - }) - - t.Run("with options", func(t *testing.T) { - result.Reset() - writer, err := NewWriter(result, WithEncoderLevel(SpeedBestCompression)) - require.NoError(t, err) - - _, err = io.Copy(writer, bytes.NewReader(testData)) - require.NoError(t, err) - require.NoError(t, writer.Close()) - - t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) - - reader, err := NewReader(result, WithDecoderLowmem(true)) - require.NoError(t, err) - - data, err := io.ReadAll(reader) - require.NoError(t, err) - require.NoError(t, reader.Close()) - - assert.Equal(t, testData, data) - }) + assert.Equal(t, testData, data) + }) + } t.Run("rejects window above 128 MiB", func(t *testing.T) { frameWith256MiBWindow := []byte{0x28, 0xb5, 0x2f, 0xfd, 0x00, 0x90, 0x01, 0x00, 0x00} @@ -77,10 +65,8 @@ func TestSeekableWriterReader(t *testing.T) { result := bytes.NewBuffer(nil) - t.Run("regular", func(t *testing.T) { + write := func(t *testing.T, testData []byte, blockSize int) { result.Reset() - blockSize := 10000 - writer, err := NewSeekableWriter(result, blockSize) require.NoError(t, err) @@ -89,6 +75,10 @@ func TestSeekableWriterReader(t *testing.T) { require.NoError(t, writer.Close()) t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) + } + + t.Run("regular", func(t *testing.T) { + write(t, testData, 10000) reader, err := NewSeekableReader(bytes.NewReader(result.Bytes())) require.NoError(t, err) @@ -101,17 +91,8 @@ func TestSeekableWriterReader(t *testing.T) { }) t.Run("seek read", func(t *testing.T) { - result.Reset() blockSize := 10000 - - writer, err := NewSeekableWriter(result, blockSize) - require.NoError(t, err) - - _, err = io.Copy(writer, bytes.NewReader(testData)) - require.NoError(t, err) - require.NoError(t, writer.Close()) - - t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) + write(t, testData, blockSize) assertReader := &assertReadSeeker{r: bytes.NewReader(result.Bytes())} @@ -141,18 +122,7 @@ func TestSeekableWriterReader(t *testing.T) { t.Run("tidy data", func(t *testing.T) { testData := prepareTestData(t, 1000) // data size is less than a block - - result.Reset() - blockSize := 10000 - - writer, err := NewSeekableWriter(result, blockSize) - require.NoError(t, err) - - _, err = io.Copy(writer, bytes.NewReader(testData)) - require.NoError(t, err) - require.NoError(t, writer.Close()) - - t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) + write(t, testData, 10000) reader, err := NewSeekableReader(bytes.NewReader(result.Bytes())) require.NoError(t, err) @@ -165,17 +135,7 @@ func TestSeekableWriterReader(t *testing.T) { }) t.Run("tidy block", func(t *testing.T) { - result.Reset() - blockSize := 100 - - writer, err := NewSeekableWriter(result, blockSize) - require.NoError(t, err) - - _, err = io.Copy(writer, bytes.NewReader(testData)) - require.NoError(t, err) - require.NoError(t, writer.Close()) - - t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) + write(t, testData, 100) // A too small block size will cause a bad compression rate, // even the compressed data is larger than the original data. assert.Greater(t, result.Len(), len(testData)) @@ -191,17 +151,7 @@ func TestSeekableWriterReader(t *testing.T) { }) t.Run("compatible reader", func(t *testing.T) { - result.Reset() - blockSize := 10000 - - writer, err := NewSeekableWriter(result, blockSize) - require.NoError(t, err) - - _, err = io.Copy(writer, bytes.NewReader(testData)) - require.NoError(t, err) - require.NoError(t, writer.Close()) - - t.Logf("original size: %d, compressed size: %d, rate: %.2f%%", len(testData), result.Len(), float64(result.Len())/float64(len(testData))*100) + write(t, testData, 10000) // It should be able to read the data with a regular reader. reader, err := NewReader(bytes.NewReader(result.Bytes())) diff --git a/routers/api/packages/generic/generic_test.go b/routers/api/packages/generic/generic_test.go index 1acaafe576a..c8ccb55fee7 100644 --- a/routers/api/packages/generic/generic_test.go +++ b/routers/api/packages/generic/generic_test.go @@ -9,57 +9,32 @@ import ( "github.com/stretchr/testify/assert" ) -func TestValidatePackageName(t *testing.T) { - bad := []string{ - "", - ".", - "..", - "-", - "a?b", - "a b", - "a/b", - } - for _, name := range bad { - assert.False(t, isValidPackageName(name), "bad=%q", name) - } - - good := []string{ - "a", - "1", - "a-", - "a_b", - "c.d+", - } - for _, name := range good { - assert.True(t, isValidPackageName(name), "good=%q", name) - } -} - -func TestValidateFileName(t *testing.T) { - bad := []string{ - "", - ".", - "..", - "a?b", - "a/b", - " a", - "a ", - } - for _, name := range bad { - assert.False(t, isValidFileName(name), "bad=%q", name) - } - - good := []string{ - "-", - "a", - "1", - "a-", - "a_b", - "a b", - "c.d+", - `-_+=:;.()[]{}~!@#$%^& aA1`, - } - for _, name := range good { - assert.True(t, isValidFileName(name), "good=%q", name) +func TestValidateNames(t *testing.T) { + for _, test := range []struct { + name string + validate func(string) bool + bad, good []string + }{ + { + name: "package", + validate: isValidPackageName, + bad: []string{"", ".", "..", "-", "a?b", "a b", "a/b"}, + good: []string{"a", "1", "a-", "a_b", "c.d+"}, + }, + { + name: "file", + validate: isValidFileName, + bad: []string{"", ".", "..", "a?b", "a/b", " a", "a "}, + good: []string{"-", "a", "1", "a-", "a_b", "a b", "c.d+", `-_+=:;.()[]{}~!@#$%^& aA1`}, + }, + } { + t.Run(test.name, func(t *testing.T) { + for _, name := range test.bad { + assert.False(t, test.validate(name), "bad=%q", name) + } + for _, name := range test.good { + assert.True(t, test.validate(name), "good=%q", name) + } + }) } } diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index d8b83ea330f..c70573a5596 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -143,17 +143,11 @@ Here are some links to the most important topics. You can find the full list of } input := "[Link](test.md)\n![Image](image.png)" - testRenderMarkdown(t, "gfm", false, input, `

Link + response := `

Link Image

-`, http.StatusOK) - - testRenderMarkdown(t, "gfm", false, input, `

Link -Image

-`, http.StatusOK) - - testRenderMarkup(t, "gfm", false, "", input, `

Link -Image

-`, http.StatusOK) +` + testRenderMarkdown(t, "gfm", false, input, response, http.StatusOK) + testRenderMarkup(t, "gfm", false, "", input, response, http.StatusOK) testRenderMarkup(t, "file", false, "path/new-file.md", input, `

Link Image

diff --git a/routers/api/v1/utils/hook_test.go b/routers/api/v1/utils/hook_test.go index 9e7f3af9814..0dc54e461bf 100644 --- a/routers/api/v1/utils/hook_test.go +++ b/routers/api/v1/utils/hook_test.go @@ -17,66 +17,29 @@ import ( func TestTestHookValidation(t *testing.T) { unittest.PrepareTestEnv(t) - t.Run("Test Validation", func(t *testing.T) { - ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) + cases := []struct { + name string + hookType string + config map[string]string + status int + }{ + {"Test Validation", "gitea", map[string]string{"content_type": "json", "url": "https://example.com/webhook"}, 0}, // not written yet + {"Test Validation with invalid URL", "gitea", map[string]string{"content_type": "json", "url": "example.com/webhook"}, http.StatusUnprocessableEntity}, + {"Test Validation with invalid webhook type", "unknown", map[string]string{"content_type": "json", "url": "example.com/webhook"}, http.StatusUnprocessableEntity}, + {"Test Validation with empty content type", "unknown", map[string]string{"url": "https://example.com/webhook"}, http.StatusUnprocessableEntity}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) - checkCreateHookOption(ctx, &structs.CreateHookOption{ - Type: "gitea", - Config: map[string]string{ - "content_type": "json", - "url": "https://example.com/webhook", - }, + checkCreateHookOption(ctx, &structs.CreateHookOption{ + Type: tc.hookType, + Config: tc.config, + }) + assert.Equal(t, tc.status, ctx.Resp.WrittenStatus()) }) - assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet - }) - - t.Run("Test Validation with invalid URL", func(t *testing.T) { - ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - - checkCreateHookOption(ctx, &structs.CreateHookOption{ - Type: "gitea", - Config: map[string]string{ - "content_type": "json", - "url": "example.com/webhook", - }, - }) - assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) - }) - - t.Run("Test Validation with invalid webhook type", func(t *testing.T) { - ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - - checkCreateHookOption(ctx, &structs.CreateHookOption{ - Type: "unknown", - Config: map[string]string{ - "content_type": "json", - "url": "example.com/webhook", - }, - }) - assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) - }) - - t.Run("Test Validation with empty content type", func(t *testing.T) { - ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - - checkCreateHookOption(ctx, &structs.CreateHookOption{ - Type: "unknown", - Config: map[string]string{ - "url": "https://example.com/webhook", - }, - }) - assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus()) - }) + } } diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go index 9c88f34d724..e6fb885ae84 100644 --- a/routers/web/admin/users_test.go +++ b/routers/web/admin/users_test.go @@ -17,183 +17,55 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNewUserPost_MustChangePassword(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx, _ := contexttest.MockContext(t, "admin/users/new") +func TestNewUserPost(t *testing.T) { + for _, tc := range []struct { + name string + email string + mustChangePassword bool + visibility api.VisibleType + }{ + {name: "MustChangePassword", email: "gitea@gitea.io", mustChangePassword: true}, + {name: "MustChangePasswordFalseAndDefaultVisibility", email: "gitea@gitea.io"}, + {name: "InvalidEmail", email: "gitea@gitea.io\r\n"}, + {name: "VisibilityPrivate", email: "gitea@gitea.io", visibility: api.VisibleTypePrivate}, + } { + t.Run(tc.name, func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + ctx, _ := contexttest.MockContext(t, "admin/users/new") + ctx.Doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true, ID: 2}) - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{ - IsAdmin: true, - ID: 2, - }) + web.SetForm(ctx, &forms.AdminCreateUserForm{ + LoginType: "local", + LoginName: "local", + UserName: "gitea", + Email: tc.email, + Password: "abc123ABC!=$", + SendNotify: false, + MustChangePassword: tc.mustChangePassword, + Visibility: tc.visibility, + }) + NewUserPost(ctx) - ctx.Doer = u - - username := "gitea" - email := "gitea@gitea.io" - - form := forms.AdminCreateUserForm{ - LoginType: "local", - LoginName: "local", - UserName: username, - Email: email, - Password: "abc123ABC!=$", - SendNotify: false, - MustChangePassword: true, + if tc.name == "InvalidEmail" { + assert.NotEmpty(t, ctx.Flash.ErrorMsg) + return + } + assert.NotEmpty(t, ctx.Flash.SuccessMsg) + user, err := user_model.GetUserByName(ctx, "gitea") + assert.NoError(t, err) + assert.Equal(t, "gitea", user.Name) + assert.Equal(t, tc.email, user.Email) + switch tc.name { + case "MustChangePassword": + assert.True(t, user.MustChangePassword) + case "MustChangePasswordFalseAndDefaultVisibility": + assert.False(t, user.MustChangePassword) + // As default user visibility + assert.Equal(t, setting.Service.DefaultUserVisibilityMode, user.Visibility) + case "VisibilityPrivate": + // As default user visibility + assert.True(t, user.Visibility.IsPrivate()) + } + }) } - - web.SetForm(ctx, &form) - NewUserPost(ctx) - - assert.NotEmpty(t, ctx.Flash.SuccessMsg) - - u, err := user_model.GetUserByName(ctx, username) - - assert.NoError(t, err) - assert.Equal(t, username, u.Name) - assert.Equal(t, email, u.Email) - assert.True(t, u.MustChangePassword) -} - -func TestNewUserPost_MustChangePasswordFalse(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx, _ := contexttest.MockContext(t, "admin/users/new") - - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{ - IsAdmin: true, - ID: 2, - }) - - ctx.Doer = u - - username := "gitea" - email := "gitea@gitea.io" - - form := forms.AdminCreateUserForm{ - LoginType: "local", - LoginName: "local", - UserName: username, - Email: email, - Password: "abc123ABC!=$", - SendNotify: false, - MustChangePassword: false, - } - - web.SetForm(ctx, &form) - NewUserPost(ctx) - - assert.NotEmpty(t, ctx.Flash.SuccessMsg) - - u, err := user_model.GetUserByName(ctx, username) - - assert.NoError(t, err) - assert.Equal(t, username, u.Name) - assert.Equal(t, email, u.Email) - assert.False(t, u.MustChangePassword) -} - -func TestNewUserPost_InvalidEmail(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx, _ := contexttest.MockContext(t, "admin/users/new") - - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{ - IsAdmin: true, - ID: 2, - }) - - ctx.Doer = u - - username := "gitea" - email := "gitea@gitea.io\r\n" - - form := forms.AdminCreateUserForm{ - LoginType: "local", - LoginName: "local", - UserName: username, - Email: email, - Password: "abc123ABC!=$", - SendNotify: false, - MustChangePassword: false, - } - - web.SetForm(ctx, &form) - NewUserPost(ctx) - - assert.NotEmpty(t, ctx.Flash.ErrorMsg) -} - -func TestNewUserPost_VisibilityDefaultPublic(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx, _ := contexttest.MockContext(t, "admin/users/new") - - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{ - IsAdmin: true, - ID: 2, - }) - - ctx.Doer = u - - username := "gitea" - email := "gitea@gitea.io" - - form := forms.AdminCreateUserForm{ - LoginType: "local", - LoginName: "local", - UserName: username, - Email: email, - Password: "abc123ABC!=$", - SendNotify: false, - MustChangePassword: false, - } - - web.SetForm(ctx, &form) - NewUserPost(ctx) - - assert.NotEmpty(t, ctx.Flash.SuccessMsg) - - u, err := user_model.GetUserByName(ctx, username) - - assert.NoError(t, err) - assert.Equal(t, username, u.Name) - assert.Equal(t, email, u.Email) - // As default user visibility - assert.Equal(t, setting.Service.DefaultUserVisibilityMode, u.Visibility) -} - -func TestNewUserPost_VisibilityPrivate(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx, _ := contexttest.MockContext(t, "admin/users/new") - - u := unittest.AssertExistsAndLoadBean(t, &user_model.User{ - IsAdmin: true, - ID: 2, - }) - - ctx.Doer = u - - username := "gitea" - email := "gitea@gitea.io" - - form := forms.AdminCreateUserForm{ - LoginType: "local", - LoginName: "local", - UserName: username, - Email: email, - Password: "abc123ABC!=$", - SendNotify: false, - MustChangePassword: false, - Visibility: api.VisibleTypePrivate, - } - - web.SetForm(ctx, &form) - NewUserPost(ctx) - - assert.NotEmpty(t, ctx.Flash.SuccessMsg) - - u, err := user_model.GetUserByName(ctx, username) - - assert.NoError(t, err) - assert.Equal(t, username, u.Name) - assert.Equal(t, email, u.Email) - // As default user visibility - assert.True(t, u.Visibility.IsPrivate()) } diff --git a/routers/web/repo/actions/view_test.go b/routers/web/repo/actions/view_test.go index af8ccfe3fdc..c5001fa6b60 100644 --- a/routers/web/repo/actions/view_test.go +++ b/routers/web/repo/actions/view_test.go @@ -77,73 +77,56 @@ func TestViewSummaryBranchFromRun(t *testing.T) { } func TestConvertToViewModel(t *testing.T) { - task := &actions_model.ActionTask{ - Status: actions_model.StatusSuccess, - Steps: []*actions_model.ActionTaskStep{ - {Name: "Run step-name", Index: 0, Status: actions_model.StatusSuccess, LogLength: 1, Started: timeutil.TimeStamp(1), Stopped: timeutil.TimeStamp(5)}, + for _, c := range []struct { + name string + task actions_model.ActionTask + expectedViewJobs []*ViewJobStep + }{ + { + name: "success", + task: actions_model.ActionTask{ + Status: actions_model.StatusSuccess, + Steps: []*actions_model.ActionTaskStep{ + {Name: "Run step-name", Index: 0, Status: actions_model.StatusSuccess, LogLength: 1, Started: timeutil.TimeStamp(1), Stopped: timeutil.TimeStamp(5)}, + }, + Stopped: timeutil.TimeStamp(20), + }, + expectedViewJobs: []*ViewJobStep{ + {Summary: "Set up job", Duration: "0s", Status: "success"}, + {Summary: "Run step-name", Duration: "4s", Status: "success"}, + {Summary: "Complete job", Duration: "15s", Status: "success"}, + }, }, - Stopped: timeutil.TimeStamp(20), + { + name: "cancelling task does not render running steps", + task: actions_model.ActionTask{ + Status: actions_model.StatusCancelling, + Steps: []*actions_model.ActionTaskStep{ + {Name: "Run step-name", Index: 0, Status: actions_model.StatusRunning, LogLength: 1}, + }, + }, + expectedViewJobs: []*ViewJobStep{ + {Summary: "Set up job", Duration: "0s", Status: "success"}, + {Summary: "Run step-name", Duration: "0s", Status: "cancelling"}, + {Summary: "Complete job", Duration: "0s", Status: "pending"}, + }, + }, + } { + t.Run(c.name, func(t *testing.T) { + viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, &c.task) + require.NoError(t, err) + + if c.task.Status == actions_model.StatusSuccess { + t.Run("out of range cursor step", func(t *testing.T) { + cursors := []LogCursor{{Step: -1, Expanded: true}, {Step: 3, Expanded: true}} + _, logs, err := convertToViewModel(t.Context(), translation.MockLocale{}, cursors, &c.task) + require.NoError(t, err) + assert.Empty(t, logs) + }) + } + assert.Equal(t, c.expectedViewJobs, viewJobSteps) + }) } - - viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, task) - require.NoError(t, err) - - t.Run("out of range cursor step", func(t *testing.T) { - cursors := []LogCursor{{Step: -1, Expanded: true}, {Step: 3, Expanded: true}} - _, logs, err := convertToViewModel(t.Context(), translation.MockLocale{}, cursors, task) - require.NoError(t, err) - assert.Empty(t, logs) - }) - - expectedViewJobs := []*ViewJobStep{ - { - Summary: "Set up job", - Duration: "0s", - Status: "success", - }, - { - Summary: "Run step-name", - Duration: "4s", - Status: "success", - }, - { - Summary: "Complete job", - Duration: "15s", - Status: "success", - }, - } - assert.Equal(t, expectedViewJobs, viewJobSteps) -} - -func TestConvertToViewModelCancellingTaskDoesNotRenderRunningSteps(t *testing.T) { - task := &actions_model.ActionTask{ - Status: actions_model.StatusCancelling, - Steps: []*actions_model.ActionTaskStep{ - {Name: "Run step-name", Index: 0, Status: actions_model.StatusRunning, LogLength: 1}, - }, - } - - viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, task) - require.NoError(t, err) - - expectedViewJobs := []*ViewJobStep{ - { - Summary: "Set up job", - Duration: "0s", - Status: "success", - }, - { - Summary: "Run step-name", - Duration: "0s", - Status: "cancelling", - }, - { - Summary: "Complete job", - Duration: "0s", - Status: "pending", - }, - } - assert.Equal(t, expectedViewJobs, viewJobSteps) } func TestPendingNeeds(t *testing.T) { diff --git a/routers/web/repo/issue_test.go b/routers/web/repo/issue_test.go index 05a23fdbfc3..b59ff137a32 100644 --- a/routers/web/repo/issue_test.go +++ b/routers/web/repo/issue_test.go @@ -8,6 +8,7 @@ import ( issues_model "gitea.dev/models/issues" "gitea.dev/modules/references" + "gitea.dev/modules/timeutil" "github.com/stretchr/testify/assert" ) @@ -36,6 +37,20 @@ func TestCombineXRefComments(t *testing.T) { } func TestCombineLabelComments(t *testing.T) { + labelComment := func(posterID int64, content, labelName string, createdUnix timeutil.TimeStamp, addedLabels, removedLabels []*issues_model.Label) *issues_model.Comment { + return &issues_model.Comment{ + Type: issues_model.CommentTypeLabel, + PosterID: posterID, + Content: content, + Label: &issues_model.Label{Name: labelName}, + CreatedUnix: createdUnix, + AddedLabels: addedLabels, + RemovedLabels: removedLabels, + } + } + comment := func(posterID int64, content string) *issues_model.Comment { + return &issues_model.Comment{Type: issues_model.CommentTypeComment, PosterID: posterID, Content: content, CreatedUnix: 0} + } kases := []struct { name string beforeCombined []*issues_model.Comment @@ -44,178 +59,39 @@ func TestCombineLabelComments(t *testing.T) { { name: "kase 1", beforeCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 1, - Content: "test", - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, nil, nil), + labelComment(1, "", "kind/bug", 0, nil, nil), + comment(1, "test"), }, afterCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - CreatedUnix: 0, - AddedLabels: []*issues_model.Label{}, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 1, - Content: "test", - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{}, nil), + comment(1, "test"), }, }, { name: "kase 2", beforeCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 70, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 1, - Content: "test", - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, nil, nil), + labelComment(1, "", "kind/bug", 70, nil, nil), + comment(1, "test"), }, afterCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - CreatedUnix: 0, - AddedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - }, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "", - CreatedUnix: 70, - RemovedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - }, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 1, - Content: "test", - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "kind/bug"}}, nil), + labelComment(1, "", "kind/bug", 70, nil, []*issues_model.Label{{Name: "kind/bug"}}), + comment(1, "test"), }, }, { name: "kase 3", beforeCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 2, - Content: "", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 1, - Content: "test", - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, nil, nil), + labelComment(2, "", "kind/bug", 0, nil, nil), + comment(1, "test"), }, afterCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - CreatedUnix: 0, - AddedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - }, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 2, - Content: "", - CreatedUnix: 0, - RemovedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - }, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 1, - Content: "test", - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "kind/bug"}}, nil), + labelComment(2, "", "kind/bug", 0, nil, []*issues_model.Label{{Name: "kind/bug"}}), + comment(1, "test"), }, }, { @@ -234,168 +110,36 @@ func TestCombineLabelComments(t *testing.T) { { name: "kase 4", beforeCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/backport", - }, - CreatedUnix: 10, - }, + labelComment(1, "1", "kind/bug", 0, nil, nil), + labelComment(1, "1", "kind/backport", 10, nil, nil), }, afterCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - CreatedUnix: 10, - AddedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - { - Name: "kind/backport", - }, - }, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - }, + labelComment(1, "1", "kind/bug", 10, []*issues_model.Label{{Name: "kind/bug"}, {Name: "kind/backport"}}, nil), }, }, { name: "kase 5", beforeCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 2, - Content: "testtest", - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, nil, nil), + comment(2, "testtest"), + labelComment(1, "", "kind/bug", 0, nil, nil), }, afterCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - AddedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeComment, - PosterID: 2, - Content: "testtest", - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "", - RemovedLabels: []*issues_model.Label{ - { - Name: "kind/bug", - }, - }, - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "kind/bug"}}, nil), + comment(2, "testtest"), + labelComment(1, "", "kind/bug", 0, nil, []*issues_model.Label{{Name: "kind/bug"}}), }, }, { name: "kase 6", beforeCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "reviewed/confirmed", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - CreatedUnix: 0, - }, - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/feature", - }, - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, nil, nil), + labelComment(1, "1", "reviewed/confirmed", 0, nil, nil), + labelComment(1, "", "kind/bug", 0, nil, nil), + labelComment(1, "1", "kind/feature", 0, nil, nil), }, afterCombined: []*issues_model.Comment{ - { - Type: issues_model.CommentTypeLabel, - PosterID: 1, - Content: "1", - Label: &issues_model.Label{ - Name: "kind/bug", - }, - AddedLabels: []*issues_model.Label{ - { - Name: "reviewed/confirmed", - }, - { - Name: "kind/feature", - }, - }, - CreatedUnix: 0, - }, + labelComment(1, "1", "kind/bug", 0, []*issues_model.Label{{Name: "reviewed/confirmed"}, {Name: "kind/feature"}}, nil), }, }, { diff --git a/routers/web/repo/middlewares_test.go b/routers/web/repo/middlewares_test.go index 0a16590ded5..f6a86924391 100644 --- a/routers/web/repo/middlewares_test.go +++ b/routers/web/repo/middlewares_test.go @@ -31,29 +31,19 @@ func TestDiffViewStyle(t *testing.T) { }) t.Run("SignedInUser", func(t *testing.T) { - ctx, _ := contexttest.MockContext(t, "/any") - contexttest.LoadUser(t, ctx, 2) - SetDiffViewStyle(ctx) - assert.Equal(t, gitdiff.DiffStyleUnified, GetDiffViewStyle(ctx)) - - ctx, _ = contexttest.MockContext(t, "/any?style=split") - contexttest.LoadUser(t, ctx, 2) - SetDiffViewStyle(ctx) - assert.Equal(t, gitdiff.DiffStyleSplit, GetDiffViewStyle(ctx)) - - ctx, _ = contexttest.MockContext(t, "/any") - contexttest.LoadUser(t, ctx, 2) - SetDiffViewStyle(ctx) - assert.Equal(t, gitdiff.DiffStyleSplit, GetDiffViewStyle(ctx)) - - ctx, _ = contexttest.MockContext(t, "/any?style=unified") - contexttest.LoadUser(t, ctx, 2) - SetDiffViewStyle(ctx) - assert.Equal(t, gitdiff.DiffStyleUnified, GetDiffViewStyle(ctx)) - - ctx, _ = contexttest.MockContext(t, "/any") - contexttest.LoadUser(t, ctx, 2) - SetDiffViewStyle(ctx) - assert.Equal(t, gitdiff.DiffStyleUnified, GetDiffViewStyle(ctx)) + for _, testCase := range []struct { + requestPath, expected string + }{ + {"/any", gitdiff.DiffStyleUnified}, + {"/any?style=split", gitdiff.DiffStyleSplit}, + {"/any", gitdiff.DiffStyleSplit}, + {"/any?style=unified", gitdiff.DiffStyleUnified}, + {"/any", gitdiff.DiffStyleUnified}, + } { + ctx, _ := contexttest.MockContext(t, testCase.requestPath) + contexttest.LoadUser(t, ctx, 2) + SetDiffViewStyle(ctx) + assert.Equal(t, testCase.expected, GetDiffViewStyle(ctx)) + } }) } diff --git a/routers/web/repo/pull_review_test.go b/routers/web/repo/pull_review_test.go index 691be213e61..40ed8de6ef5 100644 --- a/routers/web/repo/pull_review_test.go +++ b/routers/web/repo/pull_review_test.go @@ -53,40 +53,29 @@ func TestRenderConversation(t *testing.T) { }) require.NotNil(t, preparedComment) - run("diff with outdated", func(t *testing.T, ctx *context.Context, resp *httptest.ResponseRecorder) { - ctx.Data["ShowOutdatedComments"] = true - renderConversation(ctx, preparedComment, "diff") - assert.Contains(t, resp.Body.String(), `
`) - - setting.AppSubURL = "" - out, err = ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page}) - assert.NoError(t, err) - assert.Contains(t, out, ``) + for _, subURL := range []string{"/SubPath", ""} { + setting.AppSubURL = subURL + out, err := ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page}) + assert.NoError(t, err) + assert.Contains(t, out, ``) + } } diff --git a/routers/web/user/setting/security/openid_test.go b/routers/web/user/setting/security/openid_test.go index 2490272e76e..f0c7432decb 100644 --- a/routers/web/user/setting/security/openid_test.go +++ b/routers/web/user/setting/security/openid_test.go @@ -17,6 +17,7 @@ import ( "gitea.dev/modules/setting" "gitea.dev/modules/test" "gitea.dev/modules/web" + "gitea.dev/services/context" "gitea.dev/services/contexttest" "gitea.dev/services/forms" @@ -101,18 +102,12 @@ func TestOpenIDAssociation(t *testing.T) { assert.Equal(t, "Nonce already used", ctx.Flash.ErrorMsg) } -func TestDeleteOpenIDReturnsNotFoundForOtherUsersAddress(t *testing.T) { +func TestOpenIDReturnsNotFoundForOtherUsersAddress(t *testing.T) { unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "POST /user/settings/security?id=1") - contexttest.LoadUser(t, ctx, 2) - DeleteOpenID(ctx) - assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) -} - -func TestToggleOpenIDVisibilityReturnsNotFoundForOtherUsersAddress(t *testing.T) { - unittest.PrepareTestEnv(t) - ctx, _ := contexttest.MockContext(t, "POST /user/settings/security?id=1") - contexttest.LoadUser(t, ctx, 2) - ToggleOpenIDVisibility(ctx) - assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) + for _, handler := range []func(*context.Context){DeleteOpenID, ToggleOpenIDVisibility} { + ctx, _ := contexttest.MockContext(t, "POST /user/settings/security?id=1") + contexttest.LoadUser(t, ctx, 2) + handler(ctx) + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) + } } diff --git a/services/actions/clear_tasks_test.go b/services/actions/clear_tasks_test.go index 923218a9137..299e73decae 100644 --- a/services/actions/clear_tasks_test.go +++ b/services/actions/clear_tasks_test.go @@ -212,15 +212,7 @@ func TestStopEndlessTasksSkipsCancelling(t *testing.T) { var seq int64 newTaskWithJob := func(status actions_model.Status) *actions_model.ActionTask { seq++ - run := &actions_model.ActionRun{ - RepoID: 1, OwnerID: 2, TriggerUserID: 2, WorkflowID: "test.yml", - Index: 99500 + seq, Ref: "refs/heads/main", Status: actions_model.StatusRunning, - } - require.NoError(t, db.Insert(t.Context(), run)) - attempt := &actions_model.ActionRunAttempt{ - RepoID: run.RepoID, RunID: run.ID, Attempt: 1, TriggerUserID: run.TriggerUserID, Status: actions_model.StatusRunning, - } - require.NoError(t, db.Insert(t.Context(), attempt)) + run, attempt := createRunAttempt(t, 99500+seq, "", actions_model.StatusRunning) job := &actions_model.ActionRunJob{ RunID: run.ID, RunAttemptID: attempt.ID, AttemptJobID: 1, RepoID: run.RepoID, OwnerID: run.OwnerID, CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", Name: "j", JobID: "j", Status: status, diff --git a/services/actions/context_test.go b/services/actions/context_test.go index ffaa36f802b..92d11b07375 100644 --- a/services/actions/context_test.go +++ b/services/actions/context_test.go @@ -439,32 +439,22 @@ func TestGenerateGiteaContextRefProtected(t *testing.T) { NamePattern: "v*", })) - gitCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{ - RepoID: repo.ID, - Repo: repo, - TriggerUser: &user_model.User{Name: "test-user"}, - Ref: "refs/heads/master", - }, nil, nil) - - assert.Equal(t, true, gitCtx["ref_protected"]) - - tagCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{ - RepoID: repo.ID, - Repo: repo, - TriggerUser: &user_model.User{Name: "test-user"}, - Ref: "refs/tags/v1.0.0", - }, nil, nil) - - assert.Equal(t, true, tagCtx["ref_protected"]) - - unprotectedTagCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{ - RepoID: repo.ID, - Repo: repo, - TriggerUser: &user_model.User{Name: "test-user"}, - Ref: "refs/tags/other", - }, nil, nil) - - assert.Equal(t, false, unprotectedTagCtx["ref_protected"]) + for _, tc := range []struct { + ref string + protected bool + }{ + {"refs/heads/master", true}, + {"refs/tags/v1.0.0", true}, + {"refs/tags/other", false}, + } { + gitCtx := GenerateGiteaContext(t.Context(), &actions_model.ActionRun{ + RepoID: repo.ID, + Repo: repo, + TriggerUser: &user_model.User{Name: "test-user"}, + Ref: tc.ref, + }, nil, nil) + assert.Equal(t, tc.protected, gitCtx["ref_protected"]) + } } // TestGenerateGiteaContext_NilAttempt verifies that, with no explicit attempt, diff --git a/services/actions/helper_test.go b/services/actions/helper_test.go index eb7c822119e..2ffffde0287 100644 --- a/services/actions/helper_test.go +++ b/services/actions/helper_test.go @@ -99,47 +99,30 @@ func TestPullRequestTargetBaseSHA(t *testing.T) { return string(payload) } - t.Run("pull_request_target with base SHA", func(t *testing.T) { - run := &actions_model.ActionRun{ - Event: webhook_module.HookEventPullRequest, - TriggerEvent: actions_module.GithubEventPullRequestTarget, - EventPayload: prPayload("base-sha"), - } - got, ok := pullRequestTargetBaseSHA(run) - assert.True(t, ok) - assert.Equal(t, "base-sha", got) - }) - - t.Run("non pull_request_target trigger", func(t *testing.T) { - run := &actions_model.ActionRun{ - Event: webhook_module.HookEventPullRequest, - TriggerEvent: actions_module.GithubEventPullRequest, - EventPayload: prPayload("base-sha"), - } - got, ok := pullRequestTargetBaseSHA(run) - assert.False(t, ok) - assert.Empty(t, got) - }) - - t.Run("missing base SHA", func(t *testing.T) { - run := &actions_model.ActionRun{ - Event: webhook_module.HookEventPullRequest, - TriggerEvent: actions_module.GithubEventPullRequestTarget, - EventPayload: prPayload(""), - } - got, ok := pullRequestTargetBaseSHA(run) - assert.False(t, ok) - assert.Empty(t, got) - }) - - t.Run("invalid payload", func(t *testing.T) { - run := &actions_model.ActionRun{ - Event: webhook_module.HookEventPullRequest, - TriggerEvent: actions_module.GithubEventPullRequestTarget, - EventPayload: "{", - } - got, ok := pullRequestTargetBaseSHA(run) - assert.False(t, ok) - assert.Empty(t, got) - }) + for _, test := range []struct { + name string + triggerEvent string + payload string + wantSHA string + }{ + {"pull_request_target with base SHA", actions_module.GithubEventPullRequestTarget, prPayload("base-sha"), "base-sha"}, + {"non pull_request_target trigger", actions_module.GithubEventPullRequest, prPayload("base-sha"), ""}, + {"missing base SHA", actions_module.GithubEventPullRequestTarget, prPayload(""), ""}, + {"invalid payload", actions_module.GithubEventPullRequestTarget, "{", ""}, + } { + t.Run(test.name, func(t *testing.T) { + got, ok := pullRequestTargetBaseSHA(&actions_model.ActionRun{ + Event: webhook_module.HookEventPullRequest, + TriggerEvent: test.triggerEvent, + EventPayload: test.payload, + }) + if test.wantSHA != "" { + assert.True(t, ok) + assert.Equal(t, test.wantSHA, got) + } else { + assert.False(t, ok) + assert.Empty(t, got) + } + }) + } } diff --git a/services/actions/job_emitter_test.go b/services/actions/job_emitter_test.go index 252b055ae51..4ed72345f65 100644 --- a/services/actions/job_emitter_test.go +++ b/services/actions/job_emitter_test.go @@ -431,94 +431,95 @@ func Test_maxParallelConverges(t *testing.T) { // ConcurrencyGroup has already been checked at the run level, the same group is not // re-checked for individual jobs. func Test_checkRunConcurrency_NoDuplicateConcurrencyGroupCheck(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() + for _, test := range []struct { + name string + index int64 + status actions_model.Status + }{ + // Run A: the triggering run of attempt A. It is done, so it no longer holds "test-cg", which is what lets checkRunConcurrency wake the blocked waiter. + // Attempt A: a done attempt of run A with concurrency group "test-cg" + {name: "free group", index: 9901, status: actions_model.StatusSuccess}, + // Test_checkRunConcurrency_HeldGroupDoesNotWake verifies that only an unoccupied concurrency group can wake up a blocked run/job. + // Run A holds "test-cg": its attempt is still running. + {name: "held group", index: 9911, status: actions_model.StatusRunning}, + } { + t.Run(test.name, func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + insertRun := func(index int64, status actions_model.Status) (*actions_model.ActionRun, *actions_model.ActionRunAttempt) { + run := &actions_model.ActionRun{ + RepoID: 4, + OwnerID: 1, + TriggerUserID: 1, + WorkflowID: "test.yml", + Index: index, + Ref: "refs/heads/main", + Status: status, + } + assert.NoError(t, db.Insert(ctx, run)) + attempt := &actions_model.ActionRunAttempt{ + RepoID: 4, + RunID: run.ID, + Attempt: 1, + Status: status, + ConcurrencyGroup: "test-cg", + } + assert.NoError(t, db.Insert(ctx, attempt)) + _, err := db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", attempt.ID, run.ID) + assert.NoError(t, err) + return run, attempt + } - // Run A: the triggering run of attempt A. It is done, so it no longer holds "test-cg", which is what lets checkRunConcurrency wake the blocked waiter. - runA := &actions_model.ActionRun{ - RepoID: 4, - OwnerID: 1, - TriggerUserID: 1, - WorkflowID: "test.yml", - Index: 9901, - Ref: "refs/heads/main", - Status: actions_model.StatusSuccess, + runA, runAAttempt := insertRun(test.index, test.status) + if test.status == actions_model.StatusSuccess { + // A done job for run A with the same ConcurrencyGroup. + // This triggers the job-level concurrency check in checkRunConcurrency. + assert.NoError(t, db.Insert(ctx, &actions_model.ActionRunJob{ + RunID: runA.ID, + RunAttemptID: runAAttempt.ID, + AttemptJobID: 1, + RepoID: 4, + OwnerID: 1, + JobID: "job1", + Name: "job1", + Status: actions_model.StatusSuccess, + ConcurrencyGroup: "test-cg", + })) + } + + // Run B: a run blocked by concurrency + // Run B is blocked on the same group. + // Attempt B: an blocked attempt of run B + runB, runBAttempt := insertRun(test.index+1, actions_model.StatusBlocked) + if test.status == actions_model.StatusSuccess { + // A blocked job belonging to run B (no job-level concurrency group). + assert.NoError(t, db.Insert(ctx, &actions_model.ActionRunJob{ + RunID: runB.ID, + RunAttemptID: runBAttempt.ID, + AttemptJobID: 1, + RepoID: 4, + OwnerID: 1, + JobID: "job1", + Name: "job1", + Status: actions_model.StatusBlocked, + })) + } + + runA, _, _ = db.GetByID[actions_model.ActionRun](ctx, runA.ID) + result, err := checkRunConcurrency(ctx, runA) + assert.NoError(t, err) + if test.status == actions_model.StatusSuccess { + // "test-cg" is free, so the single blocked waiter (run B) is collected for re-emit. + if assert.Len(t, result.RunIDsToReEmit, 1) { + assert.Equal(t, runB.ID, result.RunIDsToReEmit[0]) + } + assert.Empty(t, result.Jobs) + } else { + // The group is held by run A, so run B must not be woken; A will wake it when it releases the group. + assert.Empty(t, result.RunIDsToReEmit) + } + }) } - assert.NoError(t, db.Insert(ctx, runA)) - - // Attempt A: a done attempt of run A with concurrency group "test-cg" - runAAttempt := &actions_model.ActionRunAttempt{ - RepoID: 4, - RunID: runA.ID, - Attempt: 1, - Status: actions_model.StatusSuccess, - ConcurrencyGroup: "test-cg", - } - assert.NoError(t, db.Insert(ctx, runAAttempt)) - _, err := db.Exec(t.Context(), "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", runAAttempt.ID, runA.ID) - assert.NoError(t, err) - - // A done job for run A with the same ConcurrencyGroup. - // This triggers the job-level concurrency check in checkRunConcurrency. - jobADone := &actions_model.ActionRunJob{ - RunID: runA.ID, - RunAttemptID: runAAttempt.ID, - AttemptJobID: 1, - RepoID: 4, - OwnerID: 1, - JobID: "job1", - Name: "job1", - Status: actions_model.StatusSuccess, - ConcurrencyGroup: "test-cg", - } - assert.NoError(t, db.Insert(ctx, jobADone)) - - // Run B: a run blocked by concurrency - runB := &actions_model.ActionRun{ - RepoID: 4, - OwnerID: 1, - TriggerUserID: 1, - WorkflowID: "test.yml", - Index: 9902, - Ref: "refs/heads/main", - Status: actions_model.StatusBlocked, - } - assert.NoError(t, db.Insert(ctx, runB)) - - // Attempt B: an blocked attempt of run B - runBAttempt := &actions_model.ActionRunAttempt{ - RepoID: 4, - RunID: runB.ID, - Attempt: 1, - Status: actions_model.StatusBlocked, - ConcurrencyGroup: "test-cg", - } - assert.NoError(t, db.Insert(ctx, runBAttempt)) - _, err = db.Exec(t.Context(), "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", runBAttempt.ID, runB.ID) - assert.NoError(t, err) - - // A blocked job belonging to run B (no job-level concurrency group). - jobBBlocked := &actions_model.ActionRunJob{ - RunID: runB.ID, - RunAttemptID: runBAttempt.ID, - AttemptJobID: 1, - RepoID: 4, - OwnerID: 1, - JobID: "job1", - Name: "job1", - Status: actions_model.StatusBlocked, - } - assert.NoError(t, db.Insert(ctx, jobBBlocked)) - - runA, _, _ = db.GetByID[actions_model.ActionRun](t.Context(), runA.ID) - result, err := checkRunConcurrency(ctx, runA) - assert.NoError(t, err) - - // "test-cg" is free, so the single blocked waiter (run B) is collected for re-emit. - if assert.Len(t, result.RunIDsToReEmit, 1) { - assert.Equal(t, runB.ID, result.RunIDsToReEmit[0]) - } - assert.Empty(t, result.Jobs) } // Test_checkJobsOfCurrentRunAttempt_RunLevelConcurrencyKeepsJobsBlocked verifies that @@ -732,45 +733,6 @@ func Test_checkJobsOfCurrentRunAttempt_SkippedCallerIsUpdated(t *testing.T) { assert.Equal(t, actions_model.StatusWaiting, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: after.ID}).Status) } -// Test_checkRunConcurrency_HeldGroupDoesNotWake verifies that only an unoccupied concurrency group can wake up a blocked run/job. -func Test_checkRunConcurrency_HeldGroupDoesNotWake(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - ctx := t.Context() - - // Run A holds "test-cg": its attempt is still running. - runA := &actions_model.ActionRun{ - RepoID: 4, OwnerID: 1, TriggerUserID: 1, WorkflowID: "test.yml", - Index: 9911, Ref: "refs/heads/main", Status: actions_model.StatusRunning, - } - assert.NoError(t, db.Insert(ctx, runA)) - runAAttempt := &actions_model.ActionRunAttempt{ - RepoID: 4, RunID: runA.ID, Attempt: 1, Status: actions_model.StatusRunning, ConcurrencyGroup: "test-cg", - } - assert.NoError(t, db.Insert(ctx, runAAttempt)) - _, err := db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", runAAttempt.ID, runA.ID) - assert.NoError(t, err) - - // Run B is blocked on the same group. - runB := &actions_model.ActionRun{ - RepoID: 4, OwnerID: 1, TriggerUserID: 1, WorkflowID: "test.yml", - Index: 9912, Ref: "refs/heads/main", Status: actions_model.StatusBlocked, - } - assert.NoError(t, db.Insert(ctx, runB)) - runBAttempt := &actions_model.ActionRunAttempt{ - RepoID: 4, RunID: runB.ID, Attempt: 1, Status: actions_model.StatusBlocked, ConcurrencyGroup: "test-cg", - } - assert.NoError(t, db.Insert(ctx, runBAttempt)) - _, err = db.Exec(ctx, "UPDATE `action_run` SET latest_attempt_id = ? WHERE id = ?", runBAttempt.ID, runB.ID) - assert.NoError(t, err) - - runA, _, _ = db.GetByID[actions_model.ActionRun](ctx, runA.ID) - result, err := checkRunConcurrency(ctx, runA) - assert.NoError(t, err) - - // The group is held by run A, so run B must not be woken; A will wake it when it releases the group. - assert.Empty(t, result.RunIDsToReEmit) -} - // Test_findConcurrencyWaiterToWake covers the finder's contract: it skips the run being processed (excludeRunID), // returns another blocked waiter when the group is free, and returns 0 while the group is still held. func Test_findConcurrencyWaiterToWake(t *testing.T) { diff --git a/services/actions/notifier_helper_test.go b/services/actions/notifier_helper_test.go index 0cb7a213a07..ae65cbf924a 100644 --- a/services/actions/notifier_helper_test.go +++ b/services/actions/notifier_helper_test.go @@ -41,41 +41,27 @@ func TestIfNeedApproval(t *testing.T) { normalUser := &user_model.User{ID: 10} restrictedUser := &user_model.User{ID: 11, IsRestricted: true} - t.Run("not a fork PR never needs approval", func(t *testing.T) { - need, err := ifNeedApprovalWith(t.Context(), nonForkRun, repo, normalUser, alwaysWrite, hasMerged) - require.NoError(t, err) - assert.False(t, need) - }) - - t.Run("pull_request_target never needs approval even when fork", func(t *testing.T) { - need, err := ifNeedApprovalWith(t.Context(), prTargetRun, repo, normalUser, alwaysWrite, hasMerged) - require.NoError(t, err) - assert.False(t, need) - }) - - t.Run("restricted user always needs approval", func(t *testing.T) { - need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, restrictedUser, alwaysWrite, hasMerged) - require.NoError(t, err) - assert.True(t, need) - }) - - t.Run("fork PR with write permission does not need approval", func(t *testing.T) { - need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, alwaysWrite, noMerged) - require.NoError(t, err) - assert.False(t, need) - }) - - t.Run("fork PR with merged PR but no write permission does not need approval", func(t *testing.T) { - need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, neverWrite, hasMerged) - require.NoError(t, err) - assert.False(t, need) - }) - - t.Run("fork PR with no write and no merged PR needs approval", func(t *testing.T) { - need, err := ifNeedApprovalWith(t.Context(), forkRun, repo, normalUser, neverWrite, noMerged) - require.NoError(t, err) - assert.True(t, need) - }) + for _, tt := range []struct { + name string + run *actions_model.ActionRun + user *user_model.User + canWrite func(context.Context, *repo_model.Repository, *user_model.User) (bool, error) + hasMerged func(context.Context, int64, int64) (bool, error) + check assert.BoolAssertionFunc + }{ + {"not a fork PR never needs approval", nonForkRun, normalUser, alwaysWrite, hasMerged, assert.False}, + {"pull_request_target never needs approval even when fork", prTargetRun, normalUser, alwaysWrite, hasMerged, assert.False}, + {"restricted user always needs approval", forkRun, restrictedUser, alwaysWrite, hasMerged, assert.True}, + {"fork PR with write permission does not need approval", forkRun, normalUser, alwaysWrite, noMerged, assert.False}, + {"fork PR with merged PR but no write permission does not need approval", forkRun, normalUser, neverWrite, hasMerged, assert.False}, + {"fork PR with no write and no merged PR needs approval", forkRun, normalUser, neverWrite, noMerged, assert.True}, + } { + t.Run(tt.name, func(t *testing.T) { + need, err := ifNeedApprovalWith(t.Context(), tt.run, repo, tt.user, tt.canWrite, tt.hasMerged) + require.NoError(t, err) + tt.check(t, need) + }) + } t.Run("canWriteActions error is propagated", func(t *testing.T) { failWrite := func(_ context.Context, _ *repo_model.Repository, _ *user_model.User) (bool, error) { diff --git a/services/actions/permission_parser_test.go b/services/actions/permission_parser_test.go index f6492c1c518..fe6e2cd07e9 100644 --- a/services/actions/permission_parser_test.go +++ b/services/actions/permission_parser_test.go @@ -16,21 +16,52 @@ import ( "go.yaml.in/yaml/v4" ) -func TestParseRawPermissions_ReadAll(t *testing.T) { - var rawPerms yaml.Node - err := yaml.Unmarshal([]byte(`read-all`), &rawPerms) - assert.NoError(t, err) - - result := parseRawPermissionsExplicit(&rawPerms) - require.NotNil(t, result) - - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeCode]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeIssues]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypePullRequests]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypePackages]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeActions]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeWiki]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeProjects]) +func TestParseRawPermissions_Scopes(t *testing.T) { + for _, tt := range []struct { + name string + input string + expectedModes [7]perm.AccessMode + idToken bool + }{ + {"ReadAll", "read-all", [7]perm.AccessMode{ + perm.AccessModeRead, perm.AccessModeRead, perm.AccessModeRead, + perm.AccessModeRead, perm.AccessModeRead, perm.AccessModeRead, perm.AccessModeRead, + }, false}, + {"WriteAll", "write-all", [7]perm.AccessMode{ + perm.AccessModeWrite, perm.AccessModeWrite, perm.AccessModeWrite, + perm.AccessModeWrite, perm.AccessModeWrite, perm.AccessModeWrite, perm.AccessModeWrite, + }, true}, + {"IndividualScopes", ` +contents: write +issues: read +pull-requests: none +packages: write +actions: read +wiki: write +projects: none +id-token: write +`, [7]perm.AccessMode{ + perm.AccessModeWrite, perm.AccessModeRead, perm.AccessModeNone, + perm.AccessModeWrite, perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeNone, + }, true}, + } { + t.Run(tt.name, func(t *testing.T) { + var rawPerms yaml.Node + err := yaml.Unmarshal([]byte(tt.input), &rawPerms) + assert.NoError(t, err) + result := parseRawPermissionsExplicit(&rawPerms) + require.NotNil(t, result) + for i, unitType := range []unit.Type{ + unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypePackages, + unit.TypeActions, unit.TypeWiki, unit.TypeProjects, + } { + assert.Equal(t, tt.expectedModes[i], result.UnitAccessModes[unitType]) + } + if tt.idToken { + assert.True(t, result.IDToken) + } + }) + } } // TestParseRawPermissions_GithubScopes verifies that all scopes that github supports are accounted for @@ -63,52 +94,6 @@ statuses: read` // No asserts for permissions set on purpose } -func TestParseRawPermissions_WriteAll(t *testing.T) { - var rawPerms yaml.Node - err := yaml.Unmarshal([]byte(`write-all`), &rawPerms) - assert.NoError(t, err) - - result := parseRawPermissionsExplicit(&rawPerms) - require.NotNil(t, result) - - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeCode]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeIssues]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypePullRequests]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypePackages]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeActions]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeWiki]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeProjects]) - assert.True(t, result.IDToken) -} - -func TestParseRawPermissions_IndividualScopes(t *testing.T) { - yamlContent := ` -contents: write -issues: read -pull-requests: none -packages: write -actions: read -wiki: write -projects: none -id-token: write -` - var rawPerms yaml.Node - err := yaml.Unmarshal([]byte(yamlContent), &rawPerms) - assert.NoError(t, err) - - result := parseRawPermissionsExplicit(&rawPerms) - require.NotNil(t, result) - - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeCode]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeIssues]) - assert.Equal(t, perm.AccessModeNone, result.UnitAccessModes[unit.TypePullRequests]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypePackages]) - assert.Equal(t, perm.AccessModeRead, result.UnitAccessModes[unit.TypeActions]) - assert.Equal(t, perm.AccessModeWrite, result.UnitAccessModes[unit.TypeWiki]) - assert.Equal(t, perm.AccessModeNone, result.UnitAccessModes[unit.TypeProjects]) - assert.True(t, result.IDToken) -} - func TestParseRawPermissions_Priority(t *testing.T) { t.Run("granular-wins-over-contents", func(t *testing.T) { yamlContent := ` diff --git a/services/actions/reusable_workflow_test.go b/services/actions/reusable_workflow_test.go index a3c9b0ac44d..b32a6b664ab 100644 --- a/services/actions/reusable_workflow_test.go +++ b/services/actions/reusable_workflow_test.go @@ -25,63 +25,38 @@ import ( ) func TestCheckCallerChain_Cycle(t *testing.T) { - t.Run("DirectCycle", func(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) + for _, testCase := range []struct { + name string + uses []string + sourceRepoID int64 + error string + }{ // A -> A: leaf's CallUses matches its direct parent's. - chain := buildCallerChain(t, - "./.gitea/workflows/a.yml", - "./.gitea/workflows/a.yml", - ) - err := checkCallerChain(t.Context(), chain[len(chain)-1]) - assert.ErrorContains(t, err, "cycle detected") - }) - - t.Run("IndirectCycle", func(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) + {"DirectCycle", []string{"./.gitea/workflows/a.yml", "./.gitea/workflows/a.yml"}, 0, "cycle detected"}, // A -> B -> A: leaf's CallUses matches its grandparent's. - chain := buildCallerChain(t, - "./.gitea/workflows/a.yml", - "./.gitea/workflows/b.yml", - "./.gitea/workflows/a.yml", - ) - err := checkCallerChain(t.Context(), chain[len(chain)-1]) - assert.ErrorContains(t, err, "cycle detected") - }) - - t.Run("MixedPrefixCycle", func(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) + {"IndirectCycle", []string{"./.gitea/workflows/a.yml", "./.gitea/workflows/b.yml", "./.gitea/workflows/a.yml"}, 0, "cycle detected"}, // A -> A written with both same-repo prefixes: they name the same file. - chain := buildCallerChain(t, - "./.gitea/workflows/a.yml", - "$/.gitea/workflows/a.yml", - ) - err := checkCallerChain(t.Context(), chain[len(chain)-1]) - assert.ErrorContains(t, err, "cycle detected") - assert.Equal(t, canonicalCallUses(&actions_model.ActionRunJob{CallUses: "owner/repo/.gitea/workflows/a.yml@v1"}), canonicalCallUses(&actions_model.ActionRunJob{CallUses: "self:owner/repo/.gitea/workflows/a.yml@v1"})) - }) - - t.Run("NoCycle", func(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) + {"MixedPrefixCycle", []string{"./.gitea/workflows/a.yml", "$/.gitea/workflows/a.yml"}, 0, "cycle detected"}, // Sanity: linear chain with distinct CallUses must not trip cycle detection. - chain := buildCallerChain(t, - "./.gitea/workflows/a.yml", - "./.gitea/workflows/b.yml", - "./.gitea/workflows/c.yml", - ) - require.NoError(t, checkCallerChain(t.Context(), chain[len(chain)-1])) - }) - - t.Run("SameLocalPathInOtherRepo", func(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - chain := buildCallerChain(t, - "./.gitea/workflows/a.yml", - "owner/lib/.gitea/workflows/lib.yml@v1", - "./.gitea/workflows/a.yml", - ) - leaf := chain[len(chain)-1] - leaf.WorkflowSourceRepoID = 2 - require.NoError(t, checkCallerChain(t.Context(), leaf)) - }) + {"NoCycle", []string{"./.gitea/workflows/a.yml", "./.gitea/workflows/b.yml", "./.gitea/workflows/c.yml"}, 0, ""}, + {"SameLocalPathInOtherRepo", []string{"./.gitea/workflows/a.yml", "owner/lib/.gitea/workflows/lib.yml@v1", "./.gitea/workflows/a.yml"}, 2, ""}, + } { + t.Run(testCase.name, func(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + chain := buildCallerChain(t, testCase.uses...) + leaf := chain[len(chain)-1] + leaf.WorkflowSourceRepoID = testCase.sourceRepoID + err := checkCallerChain(t.Context(), leaf) + if testCase.error == "" { + require.NoError(t, err) + } else { + assert.ErrorContains(t, err, testCase.error) + } + if testCase.name == "MixedPrefixCycle" { + assert.Equal(t, canonicalCallUses(&actions_model.ActionRunJob{CallUses: "owner/repo/.gitea/workflows/a.yml@v1"}), canonicalCallUses(&actions_model.ActionRunJob{CallUses: "self:owner/repo/.gitea/workflows/a.yml@v1"})) + } + }) + } t.Run("ResolvedIdentityCycle", func(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) @@ -205,20 +180,18 @@ func TestResolveUses(t *testing.T) { t.Run("DirectoryAllowlist", func(t *testing.T) { // SCOPED_WORKFLOW_DIRS is allowed (local and cross-repo). - ref, err := ResolveUses(ctx, "./.gitea/scoped_workflows/lib.yml") - require.NoError(t, err) - assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path) - - ref, err = ResolveUses(ctx, "owner/repo/.gitea/scoped_workflows/lib.yml@v1") - require.NoError(t, err) - assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path) - - ref, err = ResolveUses(ctx, "self:owner/repo/.gitea/scoped_workflows/lib.yml@v1") - require.NoError(t, err) - assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path) + for _, uses := range []string{ + "./.gitea/scoped_workflows/lib.yml", + "owner/repo/.gitea/scoped_workflows/lib.yml@v1", + "self:owner/repo/.gitea/scoped_workflows/lib.yml@v1", + } { + ref, err := ResolveUses(ctx, uses) + require.NoError(t, err) + assert.Equal(t, ".gitea/scoped_workflows/lib.yml", ref.Path) + } // A directory that is neither WORKFLOW_DIRS nor SCOPED_WORKFLOW_DIRS parses but is rejected by the allowlist. - _, err = ResolveUses(ctx, "./not-workflows/build.yml") + _, err := ResolveUses(ctx, "./not-workflows/build.yml") require.Error(t, err) _, err = ResolveUses(ctx, "owner/repo/lib/build.yml@v1") require.Error(t, err) diff --git a/services/agit/agit_test.go b/services/agit/agit_test.go index 81204fc7b79..49271f75428 100644 --- a/services/agit/agit_test.go +++ b/services/agit/agit_test.go @@ -25,37 +25,31 @@ func TestParseAgitPushOptionValue(t *testing.T) { func TestGetAgitBranchInfo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - _, _, err := GetAgitBranchInfo(t.Context(), 1, "non-exist-basebranch") - assert.ErrorIs(t, err, util.ErrNotExist) - - baseBranch, currentTopicBranch, err := GetAgitBranchInfo(t.Context(), 1, "master") - assert.NoError(t, err) - assert.Equal(t, "master", baseBranch) - assert.Empty(t, currentTopicBranch) - - baseBranch, currentTopicBranch, err = GetAgitBranchInfo(t.Context(), 1, "master/topicbranch") - assert.NoError(t, err) - assert.Equal(t, "master", baseBranch) - assert.Equal(t, "topicbranch", currentTopicBranch) - - baseBranch, currentTopicBranch, err = GetAgitBranchInfo(t.Context(), 1, "master/") - assert.NoError(t, err) - assert.Equal(t, "master", baseBranch) - assert.Empty(t, currentTopicBranch) - - _, _, err = GetAgitBranchInfo(t.Context(), 1, "/") - assert.ErrorIs(t, err, util.ErrNotExist) - - _, _, err = GetAgitBranchInfo(t.Context(), 1, "//") - assert.ErrorIs(t, err, util.ErrNotExist) - - baseBranch, currentTopicBranch, err = GetAgitBranchInfo(t.Context(), 1, "master/topicbranch/") - assert.NoError(t, err) - assert.Equal(t, "master", baseBranch) - assert.Equal(t, "topicbranch/", currentTopicBranch) - - baseBranch, currentTopicBranch, err = GetAgitBranchInfo(t.Context(), 1, "master/topicbranch/1") - assert.NoError(t, err) - assert.Equal(t, "master", baseBranch) - assert.Equal(t, "topicbranch/1", currentTopicBranch) + for _, tc := range []struct { + branch string + topic string + missing bool + }{ + {"non-exist-basebranch", "", true}, + {"master", "", false}, + {"master/topicbranch", "topicbranch", false}, + {"master/", "", false}, + {"/", "", true}, + {"//", "", true}, + {"master/topicbranch/", "topicbranch/", false}, + {"master/topicbranch/1", "topicbranch/1", false}, + } { + baseBranch, currentTopicBranch, err := GetAgitBranchInfo(t.Context(), 1, tc.branch) + if tc.missing { + assert.ErrorIs(t, err, util.ErrNotExist) + } else { + assert.NoError(t, err) + assert.Equal(t, "master", baseBranch) + if tc.topic == "" { + assert.Empty(t, currentTopicBranch) + } else { + assert.Equal(t, tc.topic, currentTopicBranch) + } + } + } } diff --git a/services/asymkey/ssh_key_test.go b/services/asymkey/ssh_key_test.go index 936201e6564..a73fafde228 100644 --- a/services/asymkey/ssh_key_test.go +++ b/services/asymkey/ssh_key_test.go @@ -21,50 +21,20 @@ func TestAddLdapSSHPublicKeys(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) s := &auth.Source{ID: 1} - testCases := []struct { + const ( + rsaKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=" + dssKey = "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=" + ) + for i, kase := range []struct { keyString string number int keyContents []string }{ - { - keyString: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - number: 1, - keyContents: []string{ - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", - }, - }, - { - keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment -ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`, - number: 2, - keyContents: []string{ - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", - "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=", - }, - }, - { - keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment -# comment asmdna,ndp -ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`, - number: 2, - keyContents: []string{ - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", - "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=", - }, - }, - { - keyString: `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment -382488320jasdj1lasmva/vasodifipi4193-fksma.cm -ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment`, - number: 2, - keyContents: []string{ - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=", - "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag=", - }, - }, - } - - for i, kase := range testCases { + {rsaKey + " nocomment\n", 1, []string{rsaKey}}, + {rsaKey + " nocomment\n" + dssKey + " nocomment", 2, []string{rsaKey, dssKey}}, + {rsaKey + " nocomment\n# comment asmdna,ndp\n" + dssKey + " nocomment", 2, []string{rsaKey, dssKey}}, + {rsaKey + " nocomment\n382488320jasdj1lasmva/vasodifipi4193-fksma.cm\n" + dssKey + " nocomment", 2, []string{rsaKey, dssKey}}, + } { s.ID = int64(i) + 20 asymkey_model.AddPublicKeysBySource(t.Context(), user, s, []string{kase.keyString}, false) keys, err := db.Find[asymkey_model.PublicKey](t.Context(), asymkey_model.FindPublicKeyOptions{ diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index f8818d63e45..6242c4f9eff 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -236,30 +236,21 @@ func TestRenderMessage(t *testing.T) { } }) - t.Run("ReservedPlaceholders", func(t *testing.T) { - assert.Equal(t, - "User Actor started impersonating user owner/repo.", - renderMessage(audit_model.UserImpersonation, actor, scope, nil), - ) - }) - - t.Run("NonStringMetadata", func(t *testing.T) { - assert.Equal(t, - "Removed external login from authentication source 7 for user owner/repo.", - renderMessage(audit_model.UserExternalLoginRemove, actor, scope, map[string]any{"auth_source_id": int64(7)}), - ) - }) - - t.Run("MissingMetadataKeepsTheKey", func(t *testing.T) { - assert.Equal(t, - "Added deploy key deploy_key for repository owner/repo.", - renderMessage(audit_model.RepositoryDeployKeyAdd, actor, scope, nil), - ) - }) - - t.Run("UnknownActionFallsBackToItsName", func(t *testing.T) { - assert.Equal(t, "not:an:action", renderMessage(audit_model.Action("not:an:action"), actor, scope, nil)) - }) + for _, tc := range []struct { + name string + action audit_model.Action + metadata map[string]any + message string + }{ + {"ReservedPlaceholders", audit_model.UserImpersonation, nil, "User Actor started impersonating user owner/repo."}, + {"NonStringMetadata", audit_model.UserExternalLoginRemove, map[string]any{"auth_source_id": int64(7)}, "Removed external login from authentication source 7 for user owner/repo."}, + {"MissingMetadataKeepsTheKey", audit_model.RepositoryDeployKeyAdd, nil, "Added deploy key deploy_key for repository owner/repo."}, + {"UnknownActionFallsBackToItsName", audit_model.Action("not:an:action"), nil, "not:an:action"}, + } { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.message, renderMessage(tc.action, actor, scope, tc.metadata)) + }) + } } func TestActionFilters(t *testing.T) { diff --git a/services/audit/notifier_test.go b/services/audit/notifier_test.go index 2f07970cfe3..46522166906 100644 --- a/services/audit/notifier_test.go +++ b/services/audit/notifier_test.go @@ -28,54 +28,42 @@ func TestAuditNotifier(t *testing.T) { SetRequestInfo(ctx, audit_model.OriginUI, "127.0.0.1") notifier := new(auditNotifier) - issue := &issues_model.Issue{ID: 10, Index: 5, Title: "Issue title", Poster: doer, Repo: repo} - notifier.NewIssue(ctx, issue, nil) - unittest.AssertExistsAndLoadBean(t, &audit_model.Event{ - Action: audit_model.IssueCreate, - ScopeType: audit_model.ScopeRepository, - ScopeID: repo.ID, - Origin: audit_model.OriginUI, - }) - - pr := &issues_model.PullRequest{ID: 11, Issue: &issues_model.Issue{ID: 12, Index: 6, Title: "PR title", Poster: doer, Repo: repo, IsPull: true}} - notifier.NewPullRequest(ctx, pr, nil) - unittest.AssertExistsAndLoadBean(t, &audit_model.Event{ - Action: audit_model.PullRequestCreate, - ScopeType: audit_model.ScopeRepository, - ScopeID: repo.ID, - Origin: audit_model.OriginUI, - }) - - notifier.NewWikiPage(ctx, doer, repo, "Home", "") - unittest.AssertExistsAndLoadBean(t, &audit_model.Event{ - Action: audit_model.WikiPageCreate, - ScopeType: audit_model.ScopeRepository, - ScopeID: repo.ID, - Origin: audit_model.OriginUI, - }) - - notifier.CreateRepository(ctx, doer, doer, repo) - unittest.AssertExistsAndLoadBean(t, &audit_model.Event{ - Action: audit_model.RepositoryCreate, - ActorID: doer.ID, - ScopeType: audit_model.ScopeRepository, - ScopeID: repo.ID, - Origin: audit_model.OriginUI, - }) - - notifier.TransferRepository(ctx, doer, repo, "previous_owner") - unittest.AssertExistsAndLoadBean(t, &audit_model.Event{ - Action: audit_model.RepositoryTransferFinish, - ActorID: doer.ID, - ScopeType: audit_model.ScopeRepository, - ScopeID: repo.ID, - Message: "Transferred repository owner/repo from previous_owner to owner.", - }) - - notifier.ChangeDefaultBranch(ctx, repo) - unittest.AssertExistsAndLoadBean(t, &audit_model.Event{ - Action: audit_model.RepositoryBranchDefault, - ScopeType: audit_model.ScopeRepository, - ScopeID: repo.ID, - }) + for _, tc := range []struct { + notify func() + event audit_model.Event + }{ + { + func() { + notifier.NewIssue(ctx, &issues_model.Issue{ID: 10, Index: 5, Title: "Issue title", Poster: doer, Repo: repo}, nil) + }, + audit_model.Event{Action: audit_model.IssueCreate, Origin: audit_model.OriginUI}, + }, + { + func() { + notifier.NewPullRequest(ctx, &issues_model.PullRequest{ID: 11, Issue: &issues_model.Issue{ID: 12, Index: 6, Title: "PR title", Poster: doer, Repo: repo, IsPull: true}}, nil) + }, + audit_model.Event{Action: audit_model.PullRequestCreate, Origin: audit_model.OriginUI}, + }, + { + func() { notifier.NewWikiPage(ctx, doer, repo, "Home", "") }, + audit_model.Event{Action: audit_model.WikiPageCreate, Origin: audit_model.OriginUI}, + }, + { + func() { notifier.CreateRepository(ctx, doer, doer, repo) }, + audit_model.Event{Action: audit_model.RepositoryCreate, ActorID: doer.ID, Origin: audit_model.OriginUI}, + }, + { + func() { notifier.TransferRepository(ctx, doer, repo, "previous_owner") }, + audit_model.Event{Action: audit_model.RepositoryTransferFinish, ActorID: doer.ID, Message: "Transferred repository owner/repo from previous_owner to owner."}, + }, + { + func() { notifier.ChangeDefaultBranch(ctx, repo) }, + audit_model.Event{Action: audit_model.RepositoryBranchDefault}, + }, + } { + tc.notify() + tc.event.ScopeType = audit_model.ScopeRepository + tc.event.ScopeID = repo.ID + unittest.AssertExistsAndLoadBean(t, &tc.event) + } } diff --git a/services/context/upload/upload_test.go b/services/context/upload/upload_test.go index f2c3242e9a4..68f0ee2b9a1 100644 --- a/services/context/upload/upload_test.go +++ b/services/context/upload/upload_test.go @@ -21,174 +21,25 @@ func TestUpload(t *testing.T) { kases := []struct { data []byte fileName string - allowedTypes string + allowedTypes []string err error }{ - { - data: testContent, - fileName: "test.txt", - allowedTypes: "", - err: nil, - }, - { - data: testContent, - fileName: "dir/test.txt", - allowedTypes: "", - err: nil, - }, - { - data: testContent, - fileName: "../../../test.txt", - allowedTypes: "", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: ",", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "|", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "*/*", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "*/*,", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "*/*|", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "text/plain", - err: nil, - }, - { - data: testContent, - fileName: "dir/test.txt", - allowedTypes: "text/plain", - err: nil, - }, - { - data: testContent, - fileName: "/dir.txt/test.js", - allowedTypes: ".js", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: " text/plain ", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: ".txt", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: " .txt,.js", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: " .txt|.js", - err: nil, - }, - { - data: testContent, - fileName: "../../test.txt", - allowedTypes: " .txt|.js", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: " .txt ,.js ", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "text/plain, .txt", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "text/*", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "text/*,.js", - err: nil, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "text/**", - err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: "application/x-gzip", - err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: ".zip", - err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: ".zip,.txtx", - err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, - }, - { - data: testContent, - fileName: "test.txt", - allowedTypes: ".zip|.txtx", - err: ErrFileTypeForbidden{"text/plain; charset=utf-8"}, - }, - { - data: b.Bytes(), - fileName: "test.txt", - allowedTypes: "application/x-gzip", - err: nil, - }, + {testContent, "test.txt", []string{""}, nil}, + {testContent, "dir/test.txt", []string{""}, nil}, + {testContent, "../../../test.txt", []string{""}, nil}, + {testContent, "test.txt", []string{"", ",", "|", "*/*", "*/*,", "*/*|", "text/plain"}, nil}, + {testContent, "dir/test.txt", []string{"text/plain"}, nil}, + {testContent, "/dir.txt/test.js", []string{".js"}, nil}, + {testContent, "test.txt", []string{" text/plain ", ".txt", " .txt,.js", " .txt|.js"}, nil}, + {testContent, "../../test.txt", []string{" .txt|.js"}, nil}, + {testContent, "test.txt", []string{" .txt ,.js ", "text/plain, .txt", "text/*", "text/*,.js"}, nil}, + {testContent, "test.txt", []string{"text/**", "application/x-gzip", ".zip", ".zip,.txtx", ".zip|.txtx"}, ErrFileTypeForbidden{"text/plain; charset=utf-8"}}, + {b.Bytes(), "test.txt", []string{"application/x-gzip"}, nil}, } for _, kase := range kases { - assert.Equal(t, kase.err, Verify(kase.data, kase.fileName, kase.allowedTypes)) + for _, allowedTypes := range kase.allowedTypes { + assert.Equal(t, kase.err, Verify(kase.data, kase.fileName, allowedTypes)) + } } } diff --git a/services/convert/git_commit_test.go b/services/convert/git_commit_test.go index ae4939af44a..085318a9c73 100644 --- a/services/convert/git_commit_test.go +++ b/services/convert/git_commit_test.go @@ -16,9 +16,9 @@ import ( "github.com/stretchr/testify/require" ) -func TestToCommitMeta(t *testing.T) { +func TestToCommitMetaAndTree(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) sha1 := git.Sha1ObjectFormat signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} tag := &git.Tag{ @@ -30,20 +30,15 @@ func TestToCommitMeta(t *testing.T) { CommitMessage: git.CommitMessage{MessageRaw: "Test Message"}, } - commitMeta := ToCommitMeta(headRepo, tag) + commitMeta := ToCommitMeta(repo, tag) assert.NotNil(t, commitMeta) assert.Equal(t, &api.CommitMeta{ SHA: sha1.EmptyObjectID().String(), - URL: headRepo.APIURL() + "/git/commits/" + sha1.EmptyObjectID().String(), + URL: repo.APIURL() + "/git/commits/" + sha1.EmptyObjectID().String(), Created: time.Unix(0, 0), }, commitMeta) -} -func TestToCommitTree(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - signature := &git.Signature{Name: "Test Signature", Email: "test@email.com", When: time.Unix(0, 0)} commit := &git.Commit{ ID: git.MustIDFromString("65f1bf27bc3bf70f64657658635e66094edbcb4d"), TreeID: git.MustIDFromString("2a2f1d4670728a2e10049e345bd7a276468beab6"), diff --git a/services/convert/notification_test.go b/services/convert/notification_test.go index 277ee83ec23..9d452e8b9b9 100644 --- a/services/convert/notification_test.go +++ b/services/convert/notification_test.go @@ -96,49 +96,38 @@ func TestToNotificationThread(t *testing.T) { assert.True(t, thread.Pinned) }) - t.Run("merged pull request returns merged state", func(t *testing.T) { + for _, test := range []struct { + name string + issueID int64 + notificationID int64 + state api.NotifySubjectStateType + }{ // Issue 2 is a pull request; pull_request 1 has has_merged=true. - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - - n := &activities_model.Notification{ - ID: 999, - UserID: 2, - RepoID: repo.ID, - Status: activities_model.NotificationStatusUnread, - Source: activities_model.NotificationSourcePullRequest, - IssueID: issue.ID, - Issue: issue, - Repository: repo, - } - - thread := ToNotificationThread(t.Context(), n) - require.NotNil(t, thread.Subject) - assert.Equal(t, api.NotifySubjectPull, thread.Subject.Type) - assert.Equal(t, api.NotifySubjectStateMerged, thread.Subject.State) - }) - - t.Run("open pull request returns open state", func(t *testing.T) { + {"merged pull request returns merged state", 2, 999, api.NotifySubjectStateMerged}, // Issue 3 is a pull request; pull_request 2 has has_merged=false. - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + {"open pull request returns open state", 3, 998, api.NotifySubjectStateOpen}, + } { + t.Run(test.name, func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: test.issueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - n := &activities_model.Notification{ - ID: 998, - UserID: 2, - RepoID: repo.ID, - Status: activities_model.NotificationStatusUnread, - Source: activities_model.NotificationSourcePullRequest, - IssueID: issue.ID, - Issue: issue, - Repository: repo, - } + n := &activities_model.Notification{ + ID: test.notificationID, + UserID: 2, + RepoID: repo.ID, + Status: activities_model.NotificationStatusUnread, + Source: activities_model.NotificationSourcePullRequest, + IssueID: issue.ID, + Issue: issue, + Repository: repo, + } - thread := ToNotificationThread(t.Context(), n) - require.NotNil(t, thread.Subject) - assert.Equal(t, api.NotifySubjectPull, thread.Subject.Type) - assert.Equal(t, api.NotifySubjectStateOpen, thread.Subject.State) - }) + thread := ToNotificationThread(t.Context(), n) + require.NotNil(t, thread.Subject) + assert.Equal(t, api.NotifySubjectPull, thread.Subject.Type) + assert.Equal(t, test.state, thread.Subject.State) + }) + } } func newRepoNotification(t *testing.T, repoID, userID int64) *activities_model.Notification { diff --git a/services/cron/tasks_test.go b/services/cron/tasks_test.go index 9ffcd077273..86b452fe794 100644 --- a/services/cron/tasks_test.go +++ b/services/cron/tasks_test.go @@ -19,34 +19,27 @@ func TestAddTaskToScheduler(t *testing.T) { } }() - // no seconds - err := addTaskToScheduler(&Task{ - Name: "task 1", - config: &BaseConfig{ - Schedule: "5 4 * * *", - }, - }) - assert.NoError(t, err) - jobs := scheduler.Jobs() - assert.Len(t, jobs, 1) - assert.Equal(t, "task 1", jobs[0].Tags()[0]) - assert.Equal(t, "5 4 * * *", jobs[0].Tags()[1]) - - // with seconds - err = addTaskToScheduler(&Task{ - Name: "task 2", - config: &BaseConfig{ - Schedule: "30 5 4 * * *", - }, - }) - assert.NoError(t, err) - jobs = scheduler.Jobs() // the item order is not guaranteed, so we need to sort it before "assert" - sort.Slice(jobs, func(i, j int) bool { - return jobs[i].Tags()[0] < jobs[j].Tags()[0] - }) - assert.Len(t, jobs, 2) - assert.Equal(t, "task 2", jobs[1].Tags()[0]) - assert.Equal(t, "30 5 4 * * *", jobs[1].Tags()[1]) + for i, task := range []struct { + name, schedule string + }{ + {"task 1", "5 4 * * *"}, // no seconds + {"task 2", "30 5 4 * * *"}, // with seconds + } { + assert.NoError(t, addTaskToScheduler(&Task{ + Name: task.name, + config: &BaseConfig{Schedule: task.schedule}, + })) + jobs := scheduler.Jobs() + if i > 0 { + // the item order is not guaranteed, so we need to sort it before "assert" + sort.Slice(jobs, func(i, j int) bool { + return jobs[i].Tags()[0] < jobs[j].Tags()[0] + }) + } + assert.Len(t, jobs, i+1) + assert.Equal(t, task.name, jobs[i].Tags()[0]) + assert.Equal(t, task.schedule, jobs[i].Tags()[1]) + } } func TestScheduleHasSeconds(t *testing.T) { @@ -57,7 +50,6 @@ func TestScheduleHasSeconds(t *testing.T) { {"* * * * * *", true}, {"* * * * *", false}, {"5 4 * * *", false}, - {"5 4 * * *", false}, {"5,8 4 * * *", false}, {"* * * * * *", true}, {"5,8 4 * * *", false}, diff --git a/services/feed/feed_test.go b/services/feed/feed_test.go index b6dc4994e54..1b8aa0edfc5 100644 --- a/services/feed/feed_test.go +++ b/services/feed/feed_test.go @@ -16,33 +16,45 @@ import ( ) func TestGetFeeds(t *testing.T) { - // test with an individual user assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - actions, count, err := GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedUser: user, - Actor: user, - IncludePrivate: true, - OnlyPerformedBy: false, - IncludeDeleted: true, - }) - assert.NoError(t, err) - if assert.Len(t, actions, 1) { - assert.EqualValues(t, 1, actions[0].ID) - assert.Equal(t, user.ID, actions[0].UserID) + for _, test := range []struct { + requestedUser *user_model.User + actionID int + includeDeleted bool + }{ + {user, 1, false}, // test with an individual user + {org, 2, true}, // test with an organization user + } { + t.Run(test.requestedUser.Name, func(t *testing.T) { + actions, count, err := GetFeeds(t.Context(), activities_model.GetFeedsOptions{ + RequestedUser: test.requestedUser, + Actor: user, + IncludePrivate: true, + OnlyPerformedBy: false, + IncludeDeleted: true, + }) + assert.NoError(t, err) + if assert.Len(t, actions, 1) { + assert.EqualValues(t, test.actionID, actions[0].ID) + assert.Equal(t, test.requestedUser.ID, actions[0].UserID) + } + assert.Equal(t, int64(1), count) + + actions, count, err = GetFeeds(t.Context(), activities_model.GetFeedsOptions{ + RequestedUser: test.requestedUser, + Actor: user, + IncludePrivate: false, + OnlyPerformedBy: false, + IncludeDeleted: test.includeDeleted, + }) + assert.NoError(t, err) + assert.Empty(t, actions) + assert.Equal(t, int64(0), count) + }) } - assert.Equal(t, int64(1), count) - - actions, count, err = GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedUser: user, - Actor: user, - IncludePrivate: false, - OnlyPerformedBy: false, - }) - assert.NoError(t, err) - assert.Empty(t, actions) - assert.Equal(t, int64(0), count) } func TestGetFeedsForRepos(t *testing.T) { @@ -51,76 +63,29 @@ func TestGetFeedsForRepos(t *testing.T) { privRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) pubRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8}) - // private repo & no login - actions, count, err := GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedRepo: privRepo, - IncludePrivate: true, - }) - assert.NoError(t, err) - assert.Empty(t, actions) - assert.Equal(t, int64(0), count) - - // public repo & no login - actions, count, err = GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedRepo: pubRepo, - IncludePrivate: true, - }) - assert.NoError(t, err) - assert.Len(t, actions, 1) - assert.Equal(t, int64(1), count) - - // private repo and login - actions, count, err = GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedRepo: privRepo, - IncludePrivate: true, - Actor: user, - }) - assert.NoError(t, err) - assert.Len(t, actions, 1) - assert.Equal(t, int64(1), count) - - // public repo & login - actions, count, err = GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedRepo: pubRepo, - IncludePrivate: true, - Actor: user, - }) - assert.NoError(t, err) - assert.Len(t, actions, 1) - assert.Equal(t, int64(1), count) -} - -func TestGetFeeds2(t *testing.T) { - // test with an organization user - assert.NoError(t, unittest.PrepareTestDatabase()) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - actions, count, err := GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedUser: org, - Actor: user, - IncludePrivate: true, - OnlyPerformedBy: false, - IncludeDeleted: true, - }) - assert.NoError(t, err) - assert.Len(t, actions, 1) - if assert.Len(t, actions, 1) { - assert.EqualValues(t, 2, actions[0].ID) - assert.Equal(t, org.ID, actions[0].UserID) + for _, test := range []struct { + repo *repo_model.Repository + actor *user_model.User + count int64 + }{ + {privRepo, nil, 0}, // private repo & no login + {pubRepo, nil, 1}, // public repo & no login + {privRepo, user, 1}, // private repo and login + {pubRepo, user, 1}, // public repo & login + } { + actions, count, err := GetFeeds(t.Context(), activities_model.GetFeedsOptions{ + RequestedRepo: test.repo, + IncludePrivate: true, + Actor: test.actor, + }) + assert.NoError(t, err) + if test.count == 0 { + assert.Empty(t, actions) + } else { + assert.Len(t, actions, 1) + } + assert.Equal(t, test.count, count) } - assert.Equal(t, int64(1), count) - - actions, count, err = GetFeeds(t.Context(), activities_model.GetFeedsOptions{ - RequestedUser: org, - Actor: user, - IncludePrivate: false, - OnlyPerformedBy: false, - IncludeDeleted: true, - }) - assert.NoError(t, err) - assert.Empty(t, actions) - assert.Equal(t, int64(0), count) } func TestGetFeedsCorrupted(t *testing.T) { @@ -175,30 +140,14 @@ func TestNotifyWatchers(t *testing.T) { assert.NoError(t, NotifyWatchers(t.Context(), action)) // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 8, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 1, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 4, - RepoID: action.RepoID, - OpType: action.OpType, - }) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ActUserID: action.ActUserID, - UserID: 11, - RepoID: action.RepoID, - OpType: action.OpType, - }) + for _, userID := range []int64{8, 1, 4, 11} { + unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ + ActUserID: action.ActUserID, + UserID: userID, + RepoID: action.RepoID, + OpType: action.OpType, + }) + } } func TestNotifyWatchersRespectsWatchOptions(t *testing.T) { diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go index 214cbd05ed2..e2fed9f1d70 100644 --- a/services/forms/user_form_test.go +++ b/services/forms/user_form_test.go @@ -23,16 +23,8 @@ func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) { func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) { defer test.MockVariableValue(&setting.Service.EmailDomainAllowList, []glob.Glob{glob.MustCompile("gitea.io")})() - tt := []struct { - email string - }{ - {"invalid-email"}, - {"gitea.io"}, - } - - for _, v := range tt { - form := RegisterForm{Email: v.email} - + for _, email := range []string{"invalid-email", "gitea.io"} { + form := RegisterForm{Email: email} assert.False(t, form.IsEmailDomainAllowed()) } } @@ -87,23 +79,12 @@ func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) { func TestDetectInvalidOAuth2ApplicationRedirectURI(t *testing.T) { defer test.MockVariableValue(&setting.OAuth2.CustomSchemes)() setting.OAuth2.CustomSchemes = []string{"my-app"} - assertValid := func(t *testing.T, s string, valid bool) { - ret := DetectInvalidOAuth2ApplicationRedirectURI([]string{s}) - if valid { - assert.Empty(t, ret) - } else { - assert.Equal(t, s, ret) - } + for _, uri := range []string{"my-app:", "my-app:/foo", "http://foo", "https://foo"} { + assert.Empty(t, DetectInvalidOAuth2ApplicationRedirectURI([]string{uri})) + } + for _, uri := range []string{"my-app", "ftp:", "ftp://foo", "https://[invalid"} { + assert.Equal(t, uri, DetectInvalidOAuth2ApplicationRedirectURI([]string{uri})) } - assertValid(t, "my-app:", true) - assertValid(t, "my-app:/foo", true) - assertValid(t, "http://foo", true) - assertValid(t, "https://foo", true) - - assertValid(t, "my-app", false) - assertValid(t, "ftp:", false) - assertValid(t, "ftp://foo", false) - assertValid(t, "https://[invalid", false) ret := DetectInvalidOAuth2ApplicationRedirectURI([]string{"my-app:", "http://foo", "https://foo"}) assert.Empty(t, ret) diff --git a/services/gitdiff/csv_test.go b/services/gitdiff/csv_test.go index b67f2b6916a..0006e28550d 100644 --- a/services/gitdiff/csv_test.go +++ b/services/gitdiff/csv_test.go @@ -15,6 +15,11 @@ import ( ) func TestCSVDiff(t *testing.T) { + const diffHeader = `diff --git a/unittest.csv b/unittest.csv +--- a/unittest.csv ++++ b/unittest.csv +` + cases := []struct { diff string base string @@ -23,10 +28,7 @@ func TestCSVDiff(t *testing.T) { }{ // case 0 - initial commit of a csv { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -0,0 +1,2 @@ + diff: diffHeader + `@@ -0,0 +1,2 @@ +col1,col2 +a,a`, base: "", @@ -39,10 +41,7 @@ a,a`, }, // case 1 - adding 1 row at end { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,2 +1,3 @@ + diff: diffHeader + `@@ -1,2 +1,3 @@ col1,col2 -a,a +a,a @@ -60,10 +59,7 @@ b,b`, }, // case 2 - row deleted { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,3 +1,2 @@ + diff: diffHeader + `@@ -1,3 +1,2 @@ col1,col2 -a,a b,b`, @@ -80,10 +76,7 @@ b,b`, }, // case 3 - row changed { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,2 +1,2 @@ + diff: diffHeader + `@@ -1,2 +1,2 @@ col1,col2 -b,b +b,c`, @@ -98,10 +91,7 @@ b,c`, }, // case 4 - all deleted { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,2 +0,0 @@ + diff: diffHeader + `@@ -1,2 +0,0 @@ -col1,col2 -b,c`, base: `col1,col2 @@ -114,10 +104,7 @@ b,c`, }, // case 5 - renames first column { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,3 +1,3 @@ + diff: diffHeader + `@@ -1,3 +1,3 @@ -col1,col2,col3 +cola,col2,col3 a,b,c`, @@ -132,10 +119,7 @@ a,b,c`, }, // case 6 - inserts a column after first, deletes last column { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,2 +1,2 @@ + diff: diffHeader + `@@ -1,2 +1,2 @@ -col1,col2,col3 -a,b,c +col1,col1a,col2 @@ -151,10 +135,7 @@ a,d,b`, }, // case 7 - deletes first column, inserts column after last { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,2 +1,2 @@ + diff: diffHeader + `@@ -1,2 +1,2 @@ -col1,col2,col3 -a,b,c +col2,col3,col4 @@ -170,10 +151,7 @@ b,c,d`, }, // case 8 - two columns deleted, 2 added { - diff: `diff --git a/unittest.csv b/unittest.csv ---- a/unittest.csv -+++ b/unittest.csv -@@ -1,2 +1,2 @@ + diff: diffHeader + `@@ -1,2 +1,2 @@ -col1,col2,col -a,b,c +col3,col4,col5 diff --git a/services/gitdiff/git_diff_tree_test.go b/services/gitdiff/git_diff_tree_test.go index 84147f643a8..bdb70433760 100644 --- a/services/gitdiff/git_diff_tree_test.go +++ b/services/gitdiff/git_diff_tree_test.go @@ -17,19 +17,59 @@ import ( ) func TestGitDiffTree(t *testing.T) { + firstCommitDiff := &DiffTree{ + Files: []*DiffTreeRecord{ + { + Status: "added", + HeadPath: ".gitignore", + BasePath: ".gitignore", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "added", + HeadPath: "LICENSE", + BasePath: "LICENSE", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + { + Status: "added", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeNoEntry, + HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c", + BaseBlobID: "0000000000000000000000000000000000000000", + }, + }, + } + + readmeDiff := &DiffTreeRecord{ + Status: "modified", + HeadPath: "README.md", + BasePath: "README.md", + HeadMode: git.EntryModeBlob, + BaseMode: git.EntryModeBlob, + HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c", + BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba", + } + test := []struct { Name string - RepoPath string BaseSha string HeadSha string useMergeBase bool Expected *DiffTree }{ { - Name: "happy path", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", - BaseSha: "72866af952e98d02a73003501836074b286a78f6", - HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", + Name: "happy path", + BaseSha: "72866af952e98d02a73003501836074b286a78f6", + HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", Expected: &DiffTree{ Files: []*DiffTreeRecord{ { @@ -55,89 +95,25 @@ func TestGitDiffTree(t *testing.T) { }, { Name: "first commit (no parent)", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", HeadSha: "72866af952e98d02a73003501836074b286a78f6", - Expected: &DiffTree{ - Files: []*DiffTreeRecord{ - { - Status: "added", - HeadPath: ".gitignore", - BasePath: ".gitignore", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeNoEntry, - HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364", - BaseBlobID: "0000000000000000000000000000000000000000", - }, - { - Status: "added", - HeadPath: "LICENSE", - BasePath: "LICENSE", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeNoEntry, - HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", - BaseBlobID: "0000000000000000000000000000000000000000", - }, - { - Status: "added", - HeadPath: "README.md", - BasePath: "README.md", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeNoEntry, - HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c", - BaseBlobID: "0000000000000000000000000000000000000000", - }, - }, - }, + Expected: firstCommitDiff, }, { Name: "first commit (no parent), merge base = true", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", HeadSha: "72866af952e98d02a73003501836074b286a78f6", useMergeBase: true, - Expected: &DiffTree{ - Files: []*DiffTreeRecord{ - { - Status: "added", - HeadPath: ".gitignore", - BasePath: ".gitignore", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeNoEntry, - HeadBlobID: "f1c181ec9c5c921245027c6b452ecfc1d3626364", - BaseBlobID: "0000000000000000000000000000000000000000", - }, - { - Status: "added", - HeadPath: "LICENSE", - BasePath: "LICENSE", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeNoEntry, - HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", - BaseBlobID: "0000000000000000000000000000000000000000", - }, - { - Status: "added", - HeadPath: "README.md", - BasePath: "README.md", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeNoEntry, - HeadBlobID: "074e590b8e64898b02beef03ece83f962c94f54c", - BaseBlobID: "0000000000000000000000000000000000000000", - }, - }, - }, + Expected: firstCommitDiff, }, { - Name: "base and head same", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", - BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", - HeadSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", + Name: "base and head same", + BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", + HeadSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", Expected: &DiffTree{ Files: []*DiffTreeRecord{}, }, }, { Name: "useMergeBase false", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", HeadSha: "111cac04bd7d20301964e27a93698aabb5781b80", // this commit can be found on the update-readme branch useMergeBase: false, @@ -152,42 +128,23 @@ func TestGitDiffTree(t *testing.T) { HeadBlobID: "c996f4725be8fc8c1d1c776e58c97ddc5d03b336", BaseBlobID: "ed5119b3c1f45547b6785bc03eac7f87570fa17f", }, - - { - Status: "modified", - HeadPath: "README.md", - BasePath: "README.md", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeBlob, - HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c", - BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba", - }, + readmeDiff, }, }, }, { Name: "useMergeBase true", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", BaseSha: "ed8f4d2fa5b2420706580d191f5dd50c4e491f3f", HeadSha: "111cac04bd7d20301964e27a93698aabb5781b80", // this commit can be found on the update-readme branch useMergeBase: true, Expected: &DiffTree{ Files: []*DiffTreeRecord{ - { - Status: "modified", - HeadPath: "README.md", - BasePath: "README.md", - HeadMode: git.EntryModeBlob, - BaseMode: git.EntryModeBlob, - HeadBlobID: "fb39771a8865c9a67f2ab9b616c854805664553c", - BaseBlobID: "9dfc0a6257d8eff526f0cfaf6a8ea950f55a9dba", - }, + readmeDiff, }, }, }, { Name: "no base set", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", HeadSha: "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", // this commit can be found on the update-readme branch useMergeBase: false, Expected: &DiffTree{ @@ -208,7 +165,7 @@ func TestGitDiffTree(t *testing.T) { for _, tt := range test { t.Run(tt.Name, func(t *testing.T) { - gitRepo, err := git.OpenRepositoryLocal(t.Context(), tt.RepoPath) + gitRepo, err := git.OpenRepositoryLocal(t.Context(), "../../modules/git/tests/repos/repo5_pulls") assert.NoError(t, err) defer gitRepo.Close() @@ -444,33 +401,29 @@ func TestParseGitDiffTree(t *testing.T) { func TestGitDiffTreeErrors(t *testing.T) { test := []struct { - Name string - RepoPath string - BaseSha string - HeadSha string + Name string + BaseSha string + HeadSha string }{ { - Name: "head doesn't exist", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", - BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", - HeadSha: "asdfasdfasdf", + Name: "head doesn't exist", + BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + HeadSha: "asdfasdfasdf", }, { - Name: "base doesn't exist", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", - BaseSha: "asdfasdfasdf", - HeadSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + Name: "base doesn't exist", + BaseSha: "asdfasdfasdf", + HeadSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", }, { - Name: "head not set", - RepoPath: "../../modules/git/tests/repos/repo5_pulls", - BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", + Name: "head not set", + BaseSha: "f32b0a9dfd09a60f616f29158f772cedd89942d2", }, } for _, tt := range test { t.Run(tt.Name, func(t *testing.T) { - gitRepo, err := git.OpenRepositoryLocal(t.Context(), tt.RepoPath) + gitRepo, err := git.OpenRepositoryLocal(t.Context(), "../../modules/git/tests/repos/repo5_pulls") assert.NoError(t, err) defer gitRepo.Close() diff --git a/services/gitdiff/gitdiff_parse_test.go b/services/gitdiff/gitdiff_parse_test.go index 7c751c61d22..a252bdb9b62 100644 --- a/services/gitdiff/gitdiff_parse_test.go +++ b/services/gitdiff/gitdiff_parse_test.go @@ -13,36 +13,26 @@ func TestTryFixTruncatedString(t *testing.T) { assert.Equal(t, "", tryFixTruncatedString("")) t.Run("UTF8", func(t *testing.T) { - s := "🌞a🌛" - assert.Equal(t, s[:1], tryFixTruncatedString(s[:1])) - assert.Equal(t, s[:2], tryFixTruncatedString(s[:2])) - assert.Equal(t, s[:3], tryFixTruncatedString(s[:3])) - assert.Equal(t, "🌞", tryFixTruncatedString(s[:4])) - assert.Equal(t, "🌞a", tryFixTruncatedString(s[:5])) - assert.Equal(t, "🌞a", tryFixTruncatedString(s[:6])) - assert.Equal(t, "🌞a", tryFixTruncatedString(s[:7])) - assert.Equal(t, "🌞a", tryFixTruncatedString(s[:8])) - assert.Equal(t, "🌞a🌛", tryFixTruncatedString(s[:9])) - - s = "a🌞🌛" - assert.Equal(t, "a", tryFixTruncatedString(s[:1])) - assert.Equal(t, "a", tryFixTruncatedString(s[:2])) - assert.Equal(t, "a", tryFixTruncatedString(s[:3])) - assert.Equal(t, "a", tryFixTruncatedString(s[:4])) - assert.Equal(t, "a🌞", tryFixTruncatedString(s[:5])) - assert.Equal(t, "a🌞", tryFixTruncatedString(s[:6])) + for _, test := range []struct { + input string + expected []string + }{ + {"🌞a🌛", []string{"🌞"[:1], "🌞"[:2], "🌞"[:3], "🌞", "🌞a", "🌞a", "🌞a", "🌞a", "🌞a🌛"}}, + {"a🌞🌛", []string{"a", "a", "a", "a", "a🌞", "a🌞"}}, + } { + for i, expected := range test.expected { + assert.Equal(t, expected, tryFixTruncatedString(test.input[:i+1])) + } + } }) t.Run("Non-UTF8", func(t *testing.T) { s := "\xff\xee\xff\xeeb\xff\xee\xff\xee" - assert.Equal(t, "\xff", tryFixTruncatedString(s[:1])) - assert.Equal(t, "\xff\xee", tryFixTruncatedString(s[:2])) - assert.Equal(t, "\xff\xee\xff", tryFixTruncatedString(s[:3])) - assert.Equal(t, "\xff\xee\xff\xee", tryFixTruncatedString(s[:4])) - assert.Equal(t, "\xff\xee\xff\xeeb", tryFixTruncatedString(s[:5])) - assert.Equal(t, "\xff\xee\xff\xeeb", tryFixTruncatedString(s[:6])) - assert.Equal(t, "\xff\xee\xff\xeeb", tryFixTruncatedString(s[:7])) - assert.Equal(t, "\xff\xee\xff\xeeb", tryFixTruncatedString(s[:8])) - assert.Equal(t, "\xff\xee\xff\xeeb\xff\xee\xff\xee", tryFixTruncatedString(s[:9])) + for i, expected := range []string{ + "\xff", "\xff\xee", "\xff\xee\xff", "\xff\xee\xff\xee", + "\xff\xee\xff\xeeb", "\xff\xee\xff\xeeb", "\xff\xee\xff\xeeb", "\xff\xee\xff\xeeb", "\xff\xee\xff\xeeb\xff\xee\xff\xee", + } { + assert.Equal(t, expected, tryFixTruncatedString(s[:i+1])) + } }) } diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 97e556bc99d..382dc2f28ab 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -269,27 +269,22 @@ index 0000000..6bb8f39 diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n") } diff = diffBuilder.String() - result, err := ParsePatch(t.Context(), 20, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) - if err != nil { - t.Errorf("There should not be an error: %v", err) - } - if !result.Files[0].IsIncomplete { - t.Errorf("Files should be incomplete! %v", result.Files[0]) - } - result, err = ParsePatch(t.Context(), 40, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) - if err != nil { - t.Errorf("There should not be an error: %v", err) - } - if result.Files[0].IsIncomplete { - t.Errorf("Files should not be incomplete! %v", result.Files[0]) - } - result, err = ParsePatch(t.Context(), 40, 5, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) - if err != nil { - t.Errorf("There should not be an error: %v", err) - } - if result.Files[0].IsIncomplete { - t.Errorf("Files should not be incomplete! %v", result.Files[0]) + checkLimits := func(maxLines, maxCharacters int, incomplete bool) { + result, err := ParsePatch(t.Context(), maxLines, maxCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) + if err != nil { + t.Errorf("There should not be an error: %v", err) + } + if result.Files[0].IsIncomplete != incomplete { + if incomplete { + t.Errorf("Files should be incomplete! %v", result.Files[0]) + } else { + t.Errorf("Files should not be incomplete! %v", result.Files[0]) + } + } } + checkLimits(20, setting.Git.MaxGitDiffLineCharacters, true) + checkLimits(40, setting.Git.MaxGitDiffLineCharacters, false) + checkLimits(40, 5, false) // Test max characters diff = `diff --git a/newfile2 b/newfile2 @@ -314,72 +309,24 @@ index 0000000..6bb8f39 diffBuilder.WriteString("+line" + strconv.Itoa(35) + "\n") diff = diffBuilder.String() - result, err = ParsePatch(t.Context(), 20, 4096, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) - if err != nil { - t.Errorf("There should not be an error: %v", err) - } - if !result.Files[0].IsIncomplete { - t.Errorf("Files should be incomplete! %v", result.Files[0]) - } - result, err = ParsePatch(t.Context(), 40, 4096, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) - if err != nil { - t.Errorf("There should not be an error: %v", err) - } - if result.Files[0].IsIncomplete { - t.Errorf("Files should not be incomplete! %v", result.Files[0]) - } + checkLimits(20, 4096, true) + checkLimits(40, 4096, false) - diff = `diff --git "a/README.md" "b/README.md" + for _, header := range []string{ + `diff --git "a/README.md" "b/README.md" --- a/README.md -+++ b/README.md -@@ -1,3 +1,6 @@ - # gitea-github-migrator -+ -+ Build Status -- Latest Release - Docker Pulls -+ cut off -+ cut off` - _, err = ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff)) - if err != nil { - t.Errorf("ParsePatch failed: %s", err) - } - - diff2 := `diff --git "a/A \\ B" "b/A \\ B" ++++ b/README.md`, + `diff --git "a/A \\ B" "b/A \\ B" --- "a/A \\ B" -+++ "b/A \\ B" -@@ -1,3 +1,6 @@ - # gitea-github-migrator -+ -+ Build Status -- Latest Release - Docker Pulls -+ cut off -+ cut off` - _, err = ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff2)) - if err != nil { - t.Errorf("ParsePatch failed: %s", err) - } - - diff2a := `diff --git "a/A \\ B" b/A/B ++++ "b/A \\ B"`, + `diff --git "a/A \\ B" b/A/B --- "a/A \\ B" -+++ b/A/B -@@ -1,3 +1,6 @@ - # gitea-github-migrator -+ -+ Build Status -- Latest Release - Docker Pulls -+ cut off -+ cut off` - _, err = ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff2a)) - if err != nil { - t.Errorf("ParsePatch failed: %s", err) - } - - diff3 := `diff --git a/README.md b/README.md ++++ b/A/B`, + `diff --git a/README.md b/README.md --- a/README.md -+++ b/README.md ++++ b/README.md`, + } { + _, err := ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(header+` @@ -1,3 +1,6 @@ # gitea-github-migrator + @@ -387,10 +334,10 @@ index 0000000..6bb8f39 - Latest Release Docker Pulls + cut off -+ cut off` - _, err = ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(diff3)) - if err != nil { - t.Errorf("ParsePatch failed: %s", err) ++ cut off`)) + if err != nil { + t.Errorf("ParsePatch failed: %s", err) + } } } @@ -506,24 +453,21 @@ func setupDefaultDiff() *Diff { } } -func TestDiff_LoadCommentsNoOutdated(t *testing.T) { +func TestDiff_LoadComments(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - diff := setupDefaultDiff() - assert.NoError(t, diff.LoadComments(t.Context(), issue, user, false)) - assert.Len(t, diff.Files[0].Sections[0].Lines[0].Comments, 2) -} - -func TestDiff_LoadCommentsWithOutdated(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - diff := setupDefaultDiff() - assert.NoError(t, diff.LoadComments(t.Context(), issue, user, true)) - assert.Len(t, diff.Files[0].Sections[0].Lines[0].Comments, 3) + for _, test := range []struct { + showOutdated bool + comments int + }{{false, 2}, {true, 3}} { + t.Run(strconv.FormatBool(test.showOutdated), func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + diff := setupDefaultDiff() + assert.NoError(t, diff.LoadComments(t.Context(), issue, user, test.showOutdated)) + assert.Len(t, diff.Files[0].Sections[0].Lines[0].Comments, test.comments) + }) + } } func TestDiffLine_CanComment(t *testing.T) { @@ -574,21 +518,9 @@ func TestGetDiffRangeWithWhitespaceBehavior(t *testing.T) { } func TestNoCrashes(t *testing.T) { - type testcase struct { - gitdiff string - } - - tests := []testcase{ - { - gitdiff: "diff --git \n--- a\t\n", - }, - { - gitdiff: "diff --git \"0\n", - }, - } - for _, testcase := range tests { + for _, patch := range []string{"diff --git \n--- a\t\n", "diff --git \"0\n"} { // It shouldn't crash, so don't care about the output. - ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff)) + ParsePatch(t.Context(), setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(patch)) } } @@ -712,18 +644,15 @@ func TestGeneratePatchForUnchangedLineFromReader(t *testing.T) { func TestCalculateHiddenCommentIDsForLine(t *testing.T) { tests := []struct { name string - line *DiffLine + sectionInfo *DiffLineSectionInfo lineComments map[int64][]*issues_model.Comment expected []int64 }{ { name: "comments in hidden range", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 10, - RightIdx: 20, - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 10, + RightIdx: 20, }, lineComments: map[int64][]*issues_model.Comment{ 15: {{ID: 100}, {ID: 101}}, @@ -733,12 +662,9 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { }, { name: "comments outside hidden range", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 10, - RightIdx: 20, - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 10, + RightIdx: 20, }, lineComments: map[int64][]*issues_model.Comment{ 5: {{ID: 100}}, @@ -748,12 +674,9 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { }, { name: "negative line numbers (left side)", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 10, - RightIdx: 20, - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 10, + RightIdx: 20, }, lineComments: map[int64][]*issues_model.Comment{ -15: {{ID: 100}}, // Left-side comment, should NOT be counted @@ -763,13 +686,10 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { }, { name: "boundary conditions - normal expansion (both boundaries exclusive)", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 10, - RightIdx: 20, - RightHunkSize: 5, // Normal case: next section has content - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 10, + RightIdx: 20, + RightHunkSize: 5, // Normal case: next section has content }, lineComments: map[int64][]*issues_model.Comment{ 10: {{ID: 100}}, // at LastRightIdx (visible line), should NOT be included @@ -781,13 +701,10 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { }, { name: "boundary conditions - end of file expansion (RightIdx inclusive)", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 54, - RightIdx: 70, - RightHunkSize: 0, // End of file: no more content after - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 54, + RightIdx: 70, + RightHunkSize: 0, // End of file: no more content after }, lineComments: map[int64][]*issues_model.Comment{ 54: {{ID: 54}}, // at LastRightIdx (visible line), should NOT be included @@ -798,13 +715,10 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { }, { name: "real-world scenario - start of file with hunk", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 0, // No previous visible section - RightIdx: 26, // Line 26 is first visible line of hunk - RightHunkSize: 9, // Normal hunk with content - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 0, // No previous visible section + RightIdx: 26, // Line 26 is first visible line of hunk + RightHunkSize: 9, // Normal hunk with content }, lineComments: map[int64][]*issues_model.Comment{ 1: {{ID: 1}}, // Line 1 is hidden @@ -818,8 +732,9 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - FillHiddenCommentIDsForDiffLine(tt.line, tt.lineComments) - assert.ElementsMatch(t, tt.expected, tt.line.SectionInfo.HiddenCommentIDs) + line := &DiffLine{Type: DiffLineSection, SectionInfo: tt.sectionInfo} + FillHiddenCommentIDsForDiffLine(line, tt.lineComments) + assert.ElementsMatch(t, tt.expected, line.SectionInfo.HiddenCommentIDs) }) } } @@ -827,7 +742,7 @@ func TestCalculateHiddenCommentIDsForLine(t *testing.T) { func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { tests := []struct { name string - line *DiffLine + sectionInfo *DiffLineSectionInfo fileNameHash string data *DiffBlobExcerptData expectContains []string @@ -835,17 +750,14 @@ func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { }{ { name: "expand up button with hidden comments", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 0, - RightIdx: 26, - LeftIdx: 26, - LastLeftIdx: 0, - LeftHunkSize: 0, - RightHunkSize: 0, - HiddenCommentIDs: []int64{100}, - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 0, + RightIdx: 26, + LeftIdx: 26, + LastLeftIdx: 0, + LeftHunkSize: 0, + RightHunkSize: 0, + HiddenCommentIDs: []int64{100}, }, fileNameHash: "abc123", data: &DiffBlobExcerptData{ @@ -862,17 +774,14 @@ func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { }, { name: "expand up and down buttons with pull request", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 10, - RightIdx: 50, - LeftIdx: 10, - LastLeftIdx: 5, - LeftHunkSize: 5, - RightHunkSize: 5, - HiddenCommentIDs: []int64{200, 201}, - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 10, + RightIdx: 50, + LeftIdx: 10, + LastLeftIdx: 5, + LeftHunkSize: 5, + RightHunkSize: 5, + HiddenCommentIDs: []int64{200, 201}, }, fileNameHash: "def456", data: &DiffBlobExcerptData{ @@ -893,17 +802,14 @@ func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { }, { name: "no hidden comments", - line: &DiffLine{ - Type: DiffLineSection, - SectionInfo: &DiffLineSectionInfo{ - LastRightIdx: 10, - RightIdx: 20, - LeftIdx: 10, - LastLeftIdx: 5, - LeftHunkSize: 5, - RightHunkSize: 5, - HiddenCommentIDs: nil, - }, + sectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 10, + RightIdx: 20, + LeftIdx: 10, + LastLeftIdx: 5, + LeftHunkSize: 5, + RightHunkSize: 5, + HiddenCommentIDs: nil, }, fileNameHash: "ghi789", data: &DiffBlobExcerptData{ @@ -921,7 +827,7 @@ func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := tt.line.RenderBlobExcerptButtons(tt.fileNameHash, tt.data) + result := (&DiffLine{Type: DiffLineSection, SectionInfo: tt.sectionInfo}).RenderBlobExcerptButtons(tt.fileNameHash, tt.data) resultStr := string(result) for _, expected := range tt.expectContains { @@ -1084,63 +990,33 @@ func TestDiffSection_GetComputedInlineDiffFor(t *testing.T) { } func TestHighlightCodeLines(t *testing.T) { + highlightLines := func(name string, lines []*DiffLine, content string) map[int]template.HTML { + return highlightCodeLinesForDiffFile(&DiffFile{ + Name: name, + Sections: []*DiffSection{{Lines: lines}}, + }, true, []byte(content)) + } t.Run("CharsetDetecting", func(t *testing.T) { - diffFile := &DiffFile{ - Name: "a.c", - Sections: []*DiffSection{ - { - Lines: []*DiffLine{{LeftIdx: 1}}, - }, - }, - } - ret := highlightCodeLinesForDiffFile(diffFile, true, []byte("// abc\xcc def\xcd")) // ISO-8859-1 bytes + ret := highlightLines("a.c", []*DiffLine{{LeftIdx: 1}}, "// abc\xcc def\xcd") // ISO-8859-1 bytes assert.Equal(t, "// abcÌ defÍ\n", string(ret[0])) }) t.Run("LeftLines", func(t *testing.T) { - diffFile := &DiffFile{ - Name: "a.c", - Sections: []*DiffSection{ - { - Lines: []*DiffLine{ - {LeftIdx: 1}, - {LeftIdx: 2}, - {LeftIdx: 3}, - }, - }, - }, - } const nl = "\n" - ret := highlightCodeLinesForDiffFile(diffFile, true, []byte("a\nb\n")) + ret := highlightLines("a.c", []*DiffLine{{LeftIdx: 1}, {LeftIdx: 2}, {LeftIdx: 3}}, "a\nb\n") assert.Equal(t, map[int]template.HTML{ 0: `a` + nl, 1: `b` + nl, }, ret) }) t.Run("CharCR", func(t *testing.T) { - diffFile := &DiffFile{ - Name: "a.txt", - Sections: []*DiffSection{ - { - Lines: []*DiffLine{{LeftIdx: 1}, {LeftIdx: 2}}, - }, - }, - } - ret := highlightCodeLinesForDiffFile(diffFile, true, []byte("a\rb\r\nc")) + ret := highlightLines("a.txt", []*DiffLine{{LeftIdx: 1}, {LeftIdx: 2}}, "a\rb\r\nc") assert.Equal(t, "a␍b\n", string(ret[0])) assert.Equal(t, `c`, string(ret[1])) }) t.Run("LongLineTruncated", func(t *testing.T) { - diffFile := &DiffFile{ - Name: "a.c", - Sections: []*DiffSection{ - { - Lines: []*DiffLine{{LeftIdx: 1, IsTruncated: true}}, - }, - }, - } - ret := highlightCodeLinesForDiffFile(diffFile, true, []byte("// anything")) + ret := highlightLines("a.c", []*DiffLine{{LeftIdx: 1, IsTruncated: true}}, "// anything") assert.Empty(t, ret) }) } diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go index 9d7daff2402..fe103c935e6 100644 --- a/services/gitdiff/submodule_test.go +++ b/services/gitdiff/submodule_test.go @@ -14,6 +14,26 @@ import ( ) func TestParseSubmoduleInfo(t *testing.T) { + const ( + deletedGitmodules = `diff --git a/.gitmodules b/.gitmodules +index 0510edd..e69de29 100644 +--- a/.gitmodules ++++ b/.gitmodules +@@ -1,3 +0,0 @@ +-[submodule "gitea-mirror"] +- path = gitea +- url = https://gitea.com/gitea/gitea-mirror +` + deletedSubmodule = `diff --git a/gitea b/gitea +deleted file mode 160000 +index c8ffe77..0000000 +--- a/gitea ++++ /dev/null +@@ -1 +0,0 @@ +-Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d +` + ) + type testcase struct { name string gitdiff string @@ -79,23 +99,8 @@ rename to gitea `, }, { - name: "deleted", - gitdiff: `diff --git a/.gitmodules b/.gitmodules -index 0510edd..e69de29 100644 ---- a/.gitmodules -+++ b/.gitmodules -@@ -1,3 +0,0 @@ --[submodule "gitea-mirror"] -- path = gitea -- url = https://gitea.com/gitea/gitea-mirror -diff --git a/gitea b/gitea -deleted file mode 160000 -index c8ffe77..0000000 ---- a/gitea -+++ /dev/null -@@ -1 +0,0 @@ --Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d -`, + name: "deleted", + gitdiff: deletedGitmodules + deletedSubmodule, infos: map[int]SubmoduleDiffInfo{ 1: { PreviousRefID: "c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d", @@ -113,14 +118,7 @@ index 0510edd..bced3d8 100644 - path = gitea + path = gitea-1.22 url = https://gitea.com/gitea/gitea-mirror -diff --git a/gitea b/gitea -deleted file mode 160000 -index c8ffe77..0000000 ---- a/gitea -+++ /dev/null -@@ -1 +0,0 @@ --Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d -diff --git a/gitea-1.22 b/gitea-1.22 +` + deletedSubmodule + `diff --git a/gitea-1.22 b/gitea-1.22 new file mode 160000 index 0000000..8eefa1f --- /dev/null @@ -139,22 +137,7 @@ index 0000000..8eefa1f }, { name: "converted to file", - gitdiff: `diff --git a/.gitmodules b/.gitmodules -index 0510edd..e69de29 100644 ---- a/.gitmodules -+++ b/.gitmodules -@@ -1,3 +0,0 @@ --[submodule "gitea-mirror"] -- path = gitea -- url = https://gitea.com/gitea/gitea-mirror -diff --git a/gitea b/gitea -deleted file mode 160000 -index c8ffe77..0000000 ---- a/gitea -+++ /dev/null -@@ -1 +0,0 @@ --Subproject commit c8ffe777cf9c5bb47a38e3e0b3a3b5de6cd8813d -diff --git a/gitea b/gitea + gitdiff: deletedGitmodules + deletedSubmodule + `diff --git a/gitea b/gitea new file mode 100644 index 0000000..33a9488 --- /dev/null diff --git a/services/issue/commit_test.go b/services/issue/commit_test.go index 3c7d753c886..459d878a44d 100644 --- a/services/issue/commit_test.go +++ b/services/issue/commit_test.go @@ -186,73 +186,45 @@ func TestUpdateIssuesCommit_Issue5957(t *testing.T) { } func TestUpdateIssuesCommit_AnotherRepo(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + for _, message := range []string{ + "close user2/repo1#1", + "close " + setting.AppURL + "user2/repo1/issues/1", + } { + t.Run(message, func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Test that a push to default branch closes issue in another repo - // If the user also has push permissions to that repo - pushCommits := []*repository.PushCommit{ - { - Sha1: "abcdef1", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user2@example.com", - AuthorName: "User Two", - Message: "close user2/repo1#1", - }, + // Test that a push to default branch closes issue in another repo + // If the user also has push permissions to that repo + pushCommits := []*repository.PushCommit{ + { + Sha1: "abcdef1", + CommitterEmail: "user2@example.com", + CommitterName: "User Two", + AuthorEmail: "user2@example.com", + AuthorName: "User Two", + Message: message, + }, + } + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + commentBean := &issues_model.Comment{ + Type: issues_model.CommentTypeCommitRef, + CommitSHA: "abcdef1", + PosterID: user.ID, + IssueID: 1, + } + + issueBean := &issues_model.Issue{RepoID: 1, Index: 1, ID: 1} + + unittest.AssertNotExistsBean(t, commentBean) + unittest.AssertNotExistsBean(t, issueBean, "is_closed=1") + assert.NoError(t, UpdateIssuesCommit(t.Context(), user, repo, pushCommits, repo.DefaultBranch)) + unittest.AssertExistsAndLoadBean(t, commentBean) + unittest.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") + unittest.CheckConsistencyFor(t, &activities_model.Action{}) + }) } - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - commentBean := &issues_model.Comment{ - Type: issues_model.CommentTypeCommitRef, - CommitSHA: "abcdef1", - PosterID: user.ID, - IssueID: 1, - } - - issueBean := &issues_model.Issue{RepoID: 1, Index: 1, ID: 1} - - unittest.AssertNotExistsBean(t, commentBean) - unittest.AssertNotExistsBean(t, issueBean, "is_closed=1") - assert.NoError(t, UpdateIssuesCommit(t.Context(), user, repo, pushCommits, repo.DefaultBranch)) - unittest.AssertExistsAndLoadBean(t, commentBean) - unittest.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") - unittest.CheckConsistencyFor(t, &activities_model.Action{}) -} - -func TestUpdateIssuesCommit_AnotherRepo_FullAddress(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // Test that a push to default branch closes issue in another repo - // If the user also has push permissions to that repo - pushCommits := []*repository.PushCommit{ - { - Sha1: "abcdef1", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user2@example.com", - AuthorName: "User Two", - Message: "close " + setting.AppURL + "user2/repo1/issues/1", - }, - } - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - commentBean := &issues_model.Comment{ - Type: issues_model.CommentTypeCommitRef, - CommitSHA: "abcdef1", - PosterID: user.ID, - IssueID: 1, - } - - issueBean := &issues_model.Issue{RepoID: 1, Index: 1, ID: 1} - - unittest.AssertNotExistsBean(t, commentBean) - unittest.AssertNotExistsBean(t, issueBean, "is_closed=1") - assert.NoError(t, UpdateIssuesCommit(t.Context(), user, repo, pushCommits, repo.DefaultBranch)) - unittest.AssertExistsAndLoadBean(t, commentBean) - unittest.AssertExistsAndLoadBean(t, issueBean, "is_closed=1") - unittest.CheckConsistencyFor(t, &activities_model.Action{}) } func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { diff --git a/services/issue/reaction_test.go b/services/issue/reaction_test.go index 3d85e603ebc..21444c57e77 100644 --- a/services/issue/reaction_test.go +++ b/services/issue/reaction_test.go @@ -36,15 +36,6 @@ func TestIssueAddReaction(t *testing.T) { addReaction(t, user1, issue, nil, "heart") unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue.ID}) -} - -func TestIssueAddDuplicateReaction(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - - addReaction(t, user1, issue, nil, "heart") reaction, err := CreateIssueReaction(t.Context(), user1, issue, "heart") assert.Error(t, err) @@ -52,17 +43,8 @@ func TestIssueAddDuplicateReaction(t *testing.T) { existingR := unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue.ID}) assert.Equal(t, existingR.ID, reaction.ID) -} -func TestIssueDeleteReaction(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - - addReaction(t, user1, issue, nil, "heart") - - err := issues_model.DeleteIssueReaction(t.Context(), user1.ID, issue.ID, "heart") + err = issues_model.DeleteIssueReaction(t.Context(), user1.ID, issue.ID, "heart") assert.NoError(t, err) unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: issue.ID}) @@ -117,6 +99,9 @@ func TestIssueCommentAddReaction(t *testing.T) { addReaction(t, user1, nil, comment, "heart") unittest.AssertExistsAndLoadBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: comment.IssueID, CommentID: comment.ID}) + assert.NoError(t, issues_model.DeleteCommentReaction(t.Context(), user1.ID, comment.IssueID, comment.ID, "heart")) + + unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: comment.IssueID, CommentID: comment.ID}) } func TestIssueCommentDeleteReaction(t *testing.T) { @@ -142,15 +127,3 @@ func TestIssueCommentDeleteReaction(t *testing.T) { assert.Len(t, reactions["heart"], 3) assert.Len(t, reactions["+1"], 1) } - -func TestIssueCommentReactionCount(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1}) - - addReaction(t, user1, nil, comment, "heart") - assert.NoError(t, issues_model.DeleteCommentReaction(t.Context(), user1.ID, comment.IssueID, comment.ID, "heart")) - - unittest.AssertNotExistsBean(t, &issues_model.Reaction{Type: "heart", UserID: user1.ID, IssueID: comment.IssueID, CommentID: comment.ID}) -} diff --git a/services/lfs/server_test.go b/services/lfs/server_test.go index 8ccf777caff..31830bf0280 100644 --- a/services/lfs/server_test.go +++ b/services/lfs/server_test.go @@ -29,37 +29,34 @@ func TestAuthenticate(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - getUserToken := func(op string, userID int64, repo *repo_model.Repository) string { - s, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: op, UserID: userID, RepoID: repo.ID}) + getUserToken := func(op string, userID int64, repo *repo_model.Repository, extDoerData string) string { + s, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: op, UserID: userID, RepoID: repo.ID, UserExtDoerData: extDoerData}) _, token, _ := strings.Cut(s, " ") return token } t.Run("handleLFSToken", func(t *testing.T) { - u, err := handleLFSToken(ctx, "", repo1, perm_model.AccessModeRead) - require.Error(t, err) - assert.Nil(t, u) + for _, token := range []string{"", "invalid"} { + u, err := handleLFSToken(ctx, token, repo1, perm_model.AccessModeRead) + require.Error(t, err) + assert.Nil(t, u) + } - u, err = handleLFSToken(ctx, "invalid", repo1, perm_model.AccessModeRead) - require.Error(t, err) - assert.Nil(t, u) - - u, err = handleLFSToken(ctx, getUserToken("download", 2, repo1), repo1, perm_model.AccessModeRead) + u, err := handleLFSToken(ctx, getUserToken("download", 2, repo1, ""), repo1, perm_model.AccessModeRead) require.NoError(t, err) assert.EqualValues(t, 2, u.ID) }) t.Run("authenticate", func(t *testing.T) { const prefixBearer = "Bearer " - token := getUserToken("download", 2, repo1) + token := getUserToken("download", 2, repo1, "") assert.False(t, authenticate(ctx, repo1, "", true, false)) assert.False(t, authenticate(ctx, repo1, prefixBearer+"invalid", true, false)) assert.True(t, authenticate(ctx, repo1, prefixBearer+token, true, false)) }) handleLFSTokenTestPerm := func(op string, userID int64, repo *repo_model.Repository, accessMode perm_model.AccessMode) error { - token := getUserToken(op, userID, repo) - u, err := handleLFSToken(ctx, token, repo, accessMode) + u, err := handleLFSToken(ctx, getUserToken(op, userID, repo, ""), repo, accessMode) if err == nil { assert.Equal(t, userID, u.ID) } @@ -108,17 +105,12 @@ func TestAuthenticate(t *testing.T) { key, err := deploykey_model.AddDeployKeyToken(t.Context(), repo1.ID, "lfs", perm_model.AccessModeRead) require.NoError(t, err) doer := user_model.NewDeployKeyUserWithKeyID(key.ID) - getDoerToken := func(op string) string { - s, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: op, UserID: doer.ID, UserExtDoerData: doer.ExtDoerData.EncodeToString(), RepoID: repo1.ID}) - _, token, _ := strings.Cut(s, " ") - return token - } - u, err := handleLFSToken(ctx, getDoerToken("download"), repo1, perm_model.AccessModeRead) + u, err := handleLFSToken(ctx, getUserToken("download", doer.ID, repo1, doer.ExtDoerData.EncodeToString()), repo1, perm_model.AccessModeRead) require.NoError(t, err) assert.Equal(t, user_model.DeployKeyUserID, u.ID) - _, err = handleLFSToken(ctx, getDoerToken("upload"), repo1, perm_model.AccessModeWrite) + _, err = handleLFSToken(ctx, getUserToken("upload", doer.ID, repo1, doer.ExtDoerData.EncodeToString()), repo1, perm_model.AccessModeWrite) assert.ErrorContains(t, err, "no permission to access the repository") }) } diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index eab9a97c306..3ae571a110e 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -395,103 +395,57 @@ func TestGenerateMessageIDForIssue(t *testing.T) { _, _, pullIssue, _ := prepareMailerTest(t) pullIssue.IsPull = true - type args struct { + for _, testCase := range []struct { + name string issue *issues_model.Issue comment *issues_model.Comment actionType activities_model.ActionType - } - tests := []struct { - name string - args args - prefix string + prefix string }{ { - name: "Open Issue", - args: args{ - issue: issue, - actionType: activities_model.ActionCreateIssue, - }, - prefix: fmt.Sprintf("<%s/issues/%d@%s>", issue.Repo.FullName(), issue.Index, setting.AppDomain), + "Open Issue", issue, nil, activities_model.ActionCreateIssue, + fmt.Sprintf("<%s/issues/%d@%s>", issue.Repo.FullName(), issue.Index, setting.AppDomain), }, { - name: "Open Pull", - args: args{ - issue: pullIssue, - actionType: activities_model.ActionCreatePullRequest, - }, - prefix: fmt.Sprintf("<%s/pulls/%d@%s>", issue.Repo.FullName(), issue.Index, setting.AppDomain), + "Open Pull", pullIssue, nil, activities_model.ActionCreatePullRequest, + fmt.Sprintf("<%s/pulls/%d@%s>", issue.Repo.FullName(), issue.Index, setting.AppDomain), }, { - name: "Comment Issue", - args: args{ - issue: issue, - comment: comment, - actionType: activities_model.ActionCommentIssue, - }, - prefix: fmt.Sprintf("<%s/issues/%d/comment/%d@%s>", issue.Repo.FullName(), issue.Index, comment.ID, setting.AppDomain), + "Comment Issue", issue, comment, activities_model.ActionCommentIssue, + fmt.Sprintf("<%s/issues/%d/comment/%d@%s>", issue.Repo.FullName(), issue.Index, comment.ID, setting.AppDomain), }, { - name: "Comment Pull", - args: args{ - issue: pullIssue, - comment: comment, - actionType: activities_model.ActionCommentPull, - }, - prefix: fmt.Sprintf("<%s/pulls/%d/comment/%d@%s>", issue.Repo.FullName(), issue.Index, comment.ID, setting.AppDomain), + "Comment Pull", pullIssue, comment, activities_model.ActionCommentPull, + fmt.Sprintf("<%s/pulls/%d/comment/%d@%s>", issue.Repo.FullName(), issue.Index, comment.ID, setting.AppDomain), }, { - name: "Close Issue", - args: args{ - issue: issue, - actionType: activities_model.ActionCloseIssue, - }, - prefix: fmt.Sprintf("<%s/issues/%d/close/", issue.Repo.FullName(), issue.Index), + "Close Issue", issue, nil, activities_model.ActionCloseIssue, + fmt.Sprintf("<%s/issues/%d/close/", issue.Repo.FullName(), issue.Index), }, { - name: "Close Pull", - args: args{ - issue: pullIssue, - actionType: activities_model.ActionClosePullRequest, - }, - prefix: fmt.Sprintf("<%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index), + "Close Pull", pullIssue, nil, activities_model.ActionClosePullRequest, + fmt.Sprintf("<%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index), }, { - name: "Reopen Issue", - args: args{ - issue: issue, - actionType: activities_model.ActionReopenIssue, - }, - prefix: fmt.Sprintf("<%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index), + "Reopen Issue", issue, nil, activities_model.ActionReopenIssue, + fmt.Sprintf("<%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index), }, { - name: "Reopen Pull", - args: args{ - issue: pullIssue, - actionType: activities_model.ActionReopenPullRequest, - }, - prefix: fmt.Sprintf("<%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index), + "Reopen Pull", pullIssue, nil, activities_model.ActionReopenPullRequest, + fmt.Sprintf("<%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index), }, { - name: "Merge Pull", - args: args{ - issue: pullIssue, - actionType: activities_model.ActionMergePullRequest, - }, - prefix: fmt.Sprintf("<%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index), + "Merge Pull", pullIssue, nil, activities_model.ActionMergePullRequest, + fmt.Sprintf("<%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index), }, { - name: "Ready Pull", - args: args{ - issue: pullIssue, - actionType: activities_model.ActionPullRequestReadyForReview, - }, - prefix: fmt.Sprintf("<%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index), + "Ready Pull", pullIssue, nil, activities_model.ActionPullRequestReadyForReview, + fmt.Sprintf("<%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index), }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := generateMessageIDForIssue(tt.args.issue, tt.args.comment, tt.args.actionType) - assert.True(t, strings.HasPrefix(got, tt.prefix), "%v, want %v", got, tt.prefix) + } { + t.Run(testCase.name, func(t *testing.T) { + got := generateMessageIDForIssue(testCase.issue, testCase.comment, testCase.actionType) + assert.True(t, strings.HasPrefix(got, testCase.prefix), "%v, want %v", got, testCase.prefix) }) } } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 04ff593bcae..933a45e579f 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -160,44 +160,61 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { // call remapLocalUser uploader.sameApp = true - externalID := int64(1234567) - externalName := "username" - source := base.Release{ - PublisherID: externalID, - PublisherName: externalName, + for _, tc := range []struct { + source base.Release + expectedUserID int64 + }{ + // + // The externalID does not match any existing user, everything + // belongs to the doer + // + {base.Release{PublisherID: 1234567, PublisherName: "username"}, doer.ID}, + // + // The externalID matches a known user but the name does not match, + // everything belongs to the doer + // + {base.Release{PublisherID: user.ID, PublisherName: "username"}, doer.ID}, + // + // The externalID and externalName match an existing user, everything + // belongs to the existing user + // + {base.Release{PublisherID: user.ID, PublisherName: user.Name}, user.ID}, + } { + target := repo_model.Release{} + uploader.userMap = make(map[int64]int64) + err := uploader.remapUser(ctx, &tc.source, &target) + assert.NoError(t, err) + assert.Equal(t, tc.expectedUserID, target.GetUserID()) } - // - // The externalID does not match any existing user, everything - // belongs to the doer - // - target := repo_model.Release{} - uploader.userMap = make(map[int64]int64) - err := uploader.remapUser(ctx, &source, &target) - assert.NoError(t, err) - assert.Equal(t, doer.ID, target.GetUserID()) + t.Run("TimelineLabelAndMilestoneComments", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) + milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - // - // The externalID matches a known user but the name does not match, - // everything belongs to the doer - // - source.PublisherID = user.ID - target = repo_model.Release{} - uploader.userMap = make(map[int64]int64) - err = uploader.remapUser(ctx, &source, &target) - assert.NoError(t, err) - assert.Equal(t, doer.ID, target.GetUserID()) + uploader := NewGiteaLocalUploader(ctx, doer, doer.Name, "migrated") + uploader.issues[issue.Index] = issue + uploader.labels[label.Name] = label + uploader.milestones[milestone.Name] = milestone.ID - // - // The externalID and externalName match an existing user, everything - // belongs to the existing user - // - source.PublisherName = user.Name - target = repo_model.Release{} - uploader.userMap = make(map[int64]int64) - err = uploader.remapUser(ctx, &source, &target) - assert.NoError(t, err) - assert.Equal(t, user.ID, target.GetUserID()) + require.NoError(t, uploader.CreateComments(ctx, + &base.Comment{IssueIndex: issue.Index, CommentType: "label", Content: "1", Meta: map[string]any{"LabelName": label.Name}}, + &base.Comment{IssueIndex: issue.Index, CommentType: "label", Meta: map[string]any{"LabelName": "deleted-upstream"}}, + &base.Comment{IssueIndex: issue.Index, CommentType: "milestone", Meta: map[string]any{"MilestoneTitle": milestone.Name}}, + &base.Comment{IssueIndex: issue.Index, CommentType: "milestone", Meta: map[string]any{"MilestoneTitle": milestone.Name, "Removed": true}}, + &base.Comment{IssueIndex: issue.Index, CommentType: "delete_branch", Content: "feature"}, + )) + + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeLabel, LabelID: label.ID, Content: "1"}) + unittest.AssertCountByCond(t, "comment", builder.Eq{ + "issue_id": issue.ID, + "type": issues_model.CommentTypeLabel, + "label_id": 0, + }, 0) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeMilestone, MilestoneID: milestone.ID}) + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeMilestone, OldMilestoneID: milestone.ID}) + assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeDeleteBranch, OldRef: "feature"}).Content) + }) } func TestGiteaUploadRemapExternalUser(t *testing.T) { @@ -332,35 +349,3 @@ func TestGiteaUploadForkPullRequestHeadInRepository(t *testing.T) { require.NoError(t, err) assert.Empty(t, strings.TrimSpace(remotes), "no remote is added for a local head") } - -func TestGiteaUploadTimelineLabelAndMilestoneComments(t *testing.T) { - unittest.PrepareTestEnv(t) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - - ctx := t.Context() - uploader := NewGiteaLocalUploader(ctx, doer, doer.Name, "migrated") - uploader.issues[issue.Index] = issue - uploader.labels[label.Name] = label - uploader.milestones[milestone.Name] = milestone.ID - - require.NoError(t, uploader.CreateComments(ctx, - &base.Comment{IssueIndex: issue.Index, CommentType: "label", Content: "1", Meta: map[string]any{"LabelName": label.Name}}, - &base.Comment{IssueIndex: issue.Index, CommentType: "label", Meta: map[string]any{"LabelName": "deleted-upstream"}}, - &base.Comment{IssueIndex: issue.Index, CommentType: "milestone", Meta: map[string]any{"MilestoneTitle": milestone.Name}}, - &base.Comment{IssueIndex: issue.Index, CommentType: "milestone", Meta: map[string]any{"MilestoneTitle": milestone.Name, "Removed": true}}, - &base.Comment{IssueIndex: issue.Index, CommentType: "delete_branch", Content: "feature"}, - )) - - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeLabel, LabelID: label.ID, Content: "1"}) - unittest.AssertCountByCond(t, "comment", builder.Eq{ - "issue_id": issue.ID, - "type": issues_model.CommentTypeLabel, - "label_id": 0, - }, 0) - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeMilestone, MilestoneID: milestone.ID}) - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeMilestone, OldMilestoneID: milestone.ID}) - assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issue.ID, Type: issues_model.CommentTypeDeleteBranch, OldRef: "feature"}).Content) -} diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go index 18289ffc337..0042c32745b 100644 --- a/services/migrations/gitlab_test.go +++ b/services/migrations/gitlab_test.go @@ -599,73 +599,40 @@ func TestNoteToComment(t *testing.T) { downloader := &GitlabDownloader{} now := time.Now() - makeTestNote := func(id int64, body string, system bool) gitlab.Note { - return gitlab.Note{ - ID: id, + for _, testCase := range []struct { + id int64 + body string + system bool + commentType string + meta map[string]any + }{ + {1, "This is a regular comment", false, "", map[string]any{}}, + {2, "enabled an automatic merge for abcd1234", true, "pull_scheduled_merge", map[string]any{}}, + {3, "changed target branch from `master` to `main`", true, "change_target_branch", map[string]any{"OldRef": "master", "NewRef": "main"}}, + {4, "canceled the automatic merge", true, "pull_cancel_scheduled_merge", map[string]any{}}, + } { + note := gitlab.Note{ + ID: testCase.id, Author: gitlab.NoteAuthor{ ID: 72, Email: "test@example.com", Username: "test", }, - Body: body, + Body: testCase.body, CreatedAt: &now, - System: system, + System: testCase.system, } - } - notes := []gitlab.Note{ - makeTestNote(1, "This is a regular comment", false), - makeTestNote(2, "enabled an automatic merge for abcd1234", true), - makeTestNote(3, "changed target branch from `master` to `main`", true), - makeTestNote(4, "canceled the automatic merge", true), - } - comments := []base.Comment{{ - IssueIndex: 17, - Index: 1, - PosterID: 72, - PosterName: "test", - PosterEmail: "test@example.com", - CommentType: "", - Content: "This is a regular comment", - Created: now, - Meta: map[string]any{}, - }, { - IssueIndex: 17, - Index: 2, - PosterID: 72, - PosterName: "test", - PosterEmail: "test@example.com", - CommentType: "pull_scheduled_merge", - Content: "enabled an automatic merge for abcd1234", - Created: now, - Meta: map[string]any{}, - }, { - IssueIndex: 17, - Index: 3, - PosterID: 72, - PosterName: "test", - PosterEmail: "test@example.com", - CommentType: "change_target_branch", - Content: "changed target branch from `master` to `main`", - Created: now, - Meta: map[string]any{ - "OldRef": "master", - "NewRef": "main", - }, - }, { - IssueIndex: 17, - Index: 4, - PosterID: 72, - PosterName: "test", - PosterEmail: "test@example.com", - CommentType: "pull_cancel_scheduled_merge", - Content: "canceled the automatic merge", - Created: now, - Meta: map[string]any{}, - }} - - for i, note := range notes { - actualComment := *downloader.convertNoteToComment(17, ¬e) - assert.Equal(t, actualComment, comments[i]) + assert.Equal(t, base.Comment{ + IssueIndex: 17, + Index: testCase.id, + PosterID: 72, + PosterName: "test", + PosterEmail: "test@example.com", + CommentType: testCase.commentType, + Content: testCase.body, + Created: now, + Meta: testCase.meta, + }, *downloader.convertNoteToComment(17, ¬e)) } } diff --git a/services/oauth2_provider/additional_scopes_test.go b/services/oauth2_provider/additional_scopes_test.go deleted file mode 100644 index 3b0d0077e9b..00000000000 --- a/services/oauth2_provider/additional_scopes_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package oauth2_provider - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGrantAdditionalScopes(t *testing.T) { - tests := []struct { - grantScopes string - expectedScopes string - }{ - {"", "all"}, // for old tokens without scope, treat it as "all" - {"openid profile email", "all"}, - {"openid profile email groups", "all"}, - {"openid profile email all", "all"}, - {"openid profile email read:user all", "all"}, - {"openid profile email groups read:user", "read:user"}, - {"read:user read:repository", "read:repository,read:user"}, - {"read:user write:issue public-only", "public-only,write:issue,read:user"}, - {"openid profile email read:user", "read:user"}, - {"no-token-scope", "no-token-scope"}, - {"openid no-token-scope", "no-token-scope"}, - {"no-token-scope read:user", "read:user"}, - {"read:invalid_scope", "no-token-scope"}, - {"read:invalid_scope,write:scope_invalid,just-plain-wrong", "no-token-scope"}, - {"openid invalid:scope", "no-token-scope"}, - {"openid ,,", "no-token-scope"}, - {"read:user invalid:scope", "read:user"}, - {"read:user,invalid:scope public-only", "public-only,read:user"}, - } - - for _, test := range tests { - t.Run("scope:"+test.grantScopes, func(t *testing.T) { - result := GrantAdditionalScopes(test.grantScopes) - assert.Equal(t, test.expectedScopes, string(result)) - }) - } -} diff --git a/services/oauth2_provider/jwtsigningkey_test.go b/services/oauth2_provider/jwtsigningkey_test.go index 9062830dc56..a0d6aeb1c01 100644 --- a/services/oauth2_provider/jwtsigningkey_test.go +++ b/services/oauth2_provider/jwtsigningkey_test.go @@ -60,3 +60,36 @@ func TestECDSASigningKeyToJWK(t *testing.T) { }) } } + +func TestGrantAdditionalScopes(t *testing.T) { + tests := []struct { + grantScopes string + expectedScopes string + }{ + {"", "all"}, // for old tokens without scope, treat it as "all" + {"openid profile email", "all"}, + {"openid profile email groups", "all"}, + {"openid profile email all", "all"}, + {"openid profile email read:user all", "all"}, + {"openid profile email groups read:user", "read:user"}, + {"read:user read:repository", "read:repository,read:user"}, + {"read:user write:issue public-only", "public-only,write:issue,read:user"}, + {"openid profile email read:user", "read:user"}, + {"no-token-scope", "no-token-scope"}, + {"openid no-token-scope", "no-token-scope"}, + {"no-token-scope read:user", "read:user"}, + {"read:invalid_scope", "no-token-scope"}, + {"read:invalid_scope,write:scope_invalid,just-plain-wrong", "no-token-scope"}, + {"openid invalid:scope", "no-token-scope"}, + {"openid ,,", "no-token-scope"}, + {"read:user invalid:scope", "read:user"}, + {"read:user,invalid:scope public-only", "public-only,read:user"}, + } + + for _, test := range tests { + t.Run("scope:"+test.grantScopes, func(t *testing.T) { + result := GrantAdditionalScopes(test.grantScopes) + assert.Equal(t, test.expectedScopes, string(result)) + }) + } +} diff --git a/services/org/team_test.go b/services/org/team_test.go index 630ed1db176..4a7409d3bc6 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -104,37 +104,33 @@ func TestNewTeam(t *testing.T) { } func TestUpdateTeam(t *testing.T) { - // successful update - assert.NoError(t, unittest.PrepareTestDatabase()) + for _, name := range []string{ + // successful update + "newName", + // update to already-existing team + "Owners", + } { + assert.NoError(t, unittest.PrepareTestDatabase()) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - team.LowerName = "newname" - team.Name = "newName" - team.Description = strings.Repeat("A long description!", 100) - team.AccessMode = perm.AccessModeAdmin - assert.NoError(t, UpdateTeam(t.Context(), team, true, false)) - - team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}) - assert.True(t, strings.HasPrefix(team.Description, "A long description!")) - - access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3}) - assert.Equal(t, perm.AccessModeAdmin, access.Mode) - - unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) -} - -func TestUpdateTeam2(t *testing.T) { - // update to already-existing team - assert.NoError(t, unittest.PrepareTestDatabase()) - - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - team.LowerName = "owners" - team.Name = "Owners" - team.Description = strings.Repeat("A long description!", 100) - err := UpdateTeam(t.Context(), team, true, false) - assert.True(t, organization.IsErrTeamAlreadyExist(err)) - - unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) + team.LowerName = strings.ToLower(name) + team.Name = name + team.Description = strings.Repeat("A long description!", 100) + if name == "newName" { + team.AccessMode = perm.AccessModeAdmin + } + err := UpdateTeam(t.Context(), team, true, false) + if name == "newName" { + assert.NoError(t, err) + team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}) + assert.True(t, strings.HasPrefix(team.Description, "A long description!")) + access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3}) + assert.Equal(t, perm.AccessModeAdmin, access.Mode) + } else { + assert.True(t, organization.IsErrTeamAlreadyExist(err)) + } + unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) + } } func TestDeleteTeam(t *testing.T) { diff --git a/services/org/user_test.go b/services/org/user_test.go index 5394103a0ce..1bd2a40259d 100644 --- a/services/org/user_test.go +++ b/services/org/user_test.go @@ -47,26 +47,21 @@ func TestUser_RemoveMember(t *testing.T) { func TestRemoveOrgUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - testSuccess := func(org *organization.Organization, user *user_model.User) { - expectedNumMembers := org.NumMembers - if unittest.GetBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID}) != nil { - expectedNumMembers-- - } - assert.NoError(t, RemoveOrgUser(t.Context(), org, user)) - unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID}) - org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID}) - assert.Equal(t, expectedNumMembers, org.NumMembers) - } - org3 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) org7 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 7}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - testSuccess(org3, user4) - - org3 = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - testSuccess(org3, user4) + for range 2 { + expectedNumMembers := org3.NumMembers + if unittest.GetBean(t, &organization.OrgUser{OrgID: org3.ID, UID: user4.ID}) != nil { + expectedNumMembers-- + } + assert.NoError(t, RemoveOrgUser(t.Context(), org3, user4)) + unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: org3.ID, UID: user4.ID}) + org3 = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org3.ID}) + assert.Equal(t, expectedNumMembers, org3.NumMembers) + } err := RemoveOrgUser(t.Context(), org7, user5) assert.Error(t, err) diff --git a/services/projects/issue_test.go b/services/projects/issue_test.go index f8f071524a7..6360445466a 100644 --- a/services/projects/issue_test.go +++ b/services/projects/issue_test.go @@ -30,29 +30,19 @@ func Test_Projects(t *testing.T) { user15 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) t.Run("User projects", func(t *testing.T) { - pi1 := project_model.ProjectIssue{ - ProjectID: 4, - IssueID: 1, - ProjectColumnID: 4, - } - err := db.Insert(t.Context(), &pi1) - assert.NoError(t, err) - defer func() { - _, err = db.DeleteByID[project_model.ProjectIssue](t.Context(), pi1.ID) + for _, issueID := range []int64{1, 4} { + projectIssue := project_model.ProjectIssue{ + ProjectID: 4, + IssueID: issueID, + ProjectColumnID: 4, + } + err := db.Insert(t.Context(), &projectIssue) assert.NoError(t, err) - }() - - pi2 := project_model.ProjectIssue{ - ProjectID: 4, - IssueID: 4, - ProjectColumnID: 4, + defer func() { + _, err := db.DeleteByID[project_model.ProjectIssue](t.Context(), projectIssue.ID) + assert.NoError(t, err) + }() } - err = db.Insert(t.Context(), &pi2) - assert.NoError(t, err) - defer func() { - _, err = db.DeleteByID[project_model.ProjectIssue](t.Context(), pi2.ID) - assert.NoError(t, err) - }() projects, err := db.Find[project_model.Project](t.Context(), project_model.SearchOptions{ OwnerID: user2.ID, @@ -61,34 +51,22 @@ func Test_Projects(t *testing.T) { assert.Len(t, projects, 3) assert.EqualValues(t, 4, projects[0].ID) - t.Run("Authenticated user", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - Owner: user2, - Doer: user2, + for _, testCase := range []struct { + name string + options issues_model.IssuesOptions + issueCount int + }{ + {"Authenticated user", issues_model.IssuesOptions{Owner: user2, Doer: user2}, 2}, // user2 can visit both issues, one from public repository one from private repository + {"Anonymous user", issues_model.IssuesOptions{AllPublic: true}, 1}, // anonymous user can only visit public repo issues + {"Authenticated user with no permission to the private repo", issues_model.IssuesOptions{Owner: user2, Doer: user4}, 1}, // user4 can only visit public repo issues + } { + t.Run(testCase.name, func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &testCase.options) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) // 4 has 2 issues, 6 will not contains here because 0 issues + assert.Len(t, columnIssues[4], testCase.issueCount) }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) // 4 has 2 issues, 6 will not contains here because 0 issues - assert.Len(t, columnIssues[4], 2) // user2 can visit both issues, one from public repository one from private repository - }) - - t.Run("Anonymous user", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - AllPublic: true, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) - assert.Len(t, columnIssues[4], 1) // anonymous user can only visit public repo issues - }) - - t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - Owner: user2, - Doer: user4, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) - assert.Len(t, columnIssues[4], 1) // user4 can only visit public repo issues - }) + } }) t.Run("Org projects", func(t *testing.T) { @@ -126,48 +104,27 @@ func Test_Projects(t *testing.T) { assert.Len(t, projects, 1) assert.Equal(t, project1.ID, projects[0].ID) - t.Run("Authenticated user", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - Owner: org3.AsUser(), - Doer: userAdmin, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) // default column has 2 issues - assert.Len(t, columnIssues[defaultColumn.ID], 2) // admin can visit both issues, one from public repository one from private repository - }) - - t.Run("Anonymous user", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - AllPublic: true, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) - assert.Len(t, columnIssues[defaultColumn.ID], 1) // anonymous user can only visit public repo issues - }) - - t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) { + for _, testCase := range []struct { + name string + options issues_model.IssuesOptions + issueCount int + }{ + {"Authenticated user", issues_model.IssuesOptions{Owner: org3.AsUser(), Doer: userAdmin}, 2}, // admin can visit both issues, one from public repository one from private repository + {"Anonymous user", issues_model.IssuesOptions{AllPublic: true}, 1}, // anonymous user can only visit public repo issues // user2 is on org3's Owners team and has owner access to the private repo3, so it is not a // valid "no permission" subject; user15 has no access to repo3 but can see the public repo32. - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - Owner: org3.AsUser(), - Doer: user15, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) - assert.Len(t, columnIssues[defaultColumn.ID], 1) // user15 can only visit public repo issues - }) - - t.Run("Org owner team member", func(t *testing.T) { + {"Authenticated user with no permission to the private repo", issues_model.IssuesOptions{Owner: org3.AsUser(), Doer: user15}, 1}, // user15 can only visit public repo issues // user2 is on org3's Owners team, so it has access to the private repo3 and must see both the // public and the private issue — the owner-team access that team.authorize grants at runtime. - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - Owner: org3.AsUser(), - Doer: user2, + {"Org owner team member", issues_model.IssuesOptions{Owner: org3.AsUser(), Doer: user2}, 2}, // owner-team member visits both public and private issues + } { + t.Run(testCase.name, func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &testCase.options) + assert.NoError(t, err) + assert.Len(t, columnIssues, 1) // default column has 2 issues + assert.Len(t, columnIssues[defaultColumn.ID], testCase.issueCount) }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 1) - assert.Len(t, columnIssues[defaultColumn.ID], 2) // owner-team member visits both public and private issues - }) + } }) t.Run("Moving an issue in one project keeps its column in other projects", func(t *testing.T) { @@ -219,39 +176,22 @@ func Test_Projects(t *testing.T) { assert.Len(t, projects, 1) assert.EqualValues(t, 1, projects[0].ID) - t.Run("Authenticated user", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - RepoIDs: []int64{repo1.ID}, - Doer: userAdmin, + for _, testCase := range []struct { + name string + options issues_model.IssuesOptions + }{ + {"Authenticated user", issues_model.IssuesOptions{RepoIDs: []int64{repo1.ID}, Doer: userAdmin}}, + {"Anonymous user", issues_model.IssuesOptions{AllPublic: true}}, + {"Authenticated user with no permission to the private repo", issues_model.IssuesOptions{RepoIDs: []int64{repo1.ID}, Doer: user2}}, + } { + t.Run(testCase.name, func(t *testing.T) { + columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &testCase.options) + assert.NoError(t, err) + assert.Len(t, columnIssues, 3) + assert.Len(t, columnIssues[1], 2) + assert.Len(t, columnIssues[2], 1) + assert.Len(t, columnIssues[3], 1) }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 3) - assert.Len(t, columnIssues[1], 2) - assert.Len(t, columnIssues[2], 1) - assert.Len(t, columnIssues[3], 1) - }) - - t.Run("Anonymous user", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - AllPublic: true, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 3) - assert.Len(t, columnIssues[1], 2) - assert.Len(t, columnIssues[2], 1) - assert.Len(t, columnIssues[3], 1) - }) - - t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) { - columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{ - RepoIDs: []int64{repo1.ID}, - Doer: user2, - }) - assert.NoError(t, err) - assert.Len(t, columnIssues, 3) - assert.Len(t, columnIssues[1], 2) - assert.Len(t, columnIssues[2], 1) - assert.Len(t, columnIssues[3], 1) - }) + } }) } diff --git a/services/projects/workflow_test.go b/services/projects/workflow_test.go index 7f45ce83249..35342a406dd 100644 --- a/services/projects/workflow_test.go +++ b/services/projects/workflow_test.go @@ -206,20 +206,16 @@ func TestWorkflowExecution(t *testing.T) { } t.Run("ColumnChangedFromDefaultColumn", func(t *testing.T) { - setup(t, project_model.Workflow{WorkflowEvent: project_model.WorkflowEventItemColumnChanged, WorkflowFilters: project_model.WorkflowFilters{SourceColumnID: 1, TargetColumnID: 2}, WorkflowActions: addLabel}) + addWorkflow(t, project_model.Workflow{WorkflowEvent: project_model.WorkflowEventItemColumnChanged, WorkflowFilters: project_model.WorkflowFilters{SourceColumnID: 1, TargetColumnID: 2}, WorkflowActions: addLabel}) moveIssue(t, t.Context(), 2, 2) assert.True(t, hasLabel(t, 2)) }) - t.Run("NoOpMoveWithinDefaultColumn", func(t *testing.T) { + t.Run("NoOpMoveWithinDefaultColumnAndNoCascadeFromWorkflows", func(t *testing.T) { setup(t, project_model.Workflow{WorkflowEvent: project_model.WorkflowEventItemColumnChanged, WorkflowActions: addLabel}) moveIssue(t, t.Context(), 2, 1) assert.False(t, hasLabel(t, 2)) unittest.AssertNotExistsBean(t, &issues_model.Comment{IssueID: 2, Type: issues_model.CommentTypeProjectColumn}) - }) - - t.Run("NoCascadeFromWorkflows", func(t *testing.T) { - setup(t, project_model.Workflow{WorkflowEvent: project_model.WorkflowEventItemColumnChanged, WorkflowActions: addLabel}) moveIssue(t, issues_model.WithProjectWorkflow(t.Context(), project_model.WorkflowEventItemAddedToProject), 1, 2) assert.False(t, hasLabel(t, 1)) }) diff --git a/services/pubsub/memory_test.go b/services/pubsub/memory_test.go index 18b023674bb..324ae8f6789 100644 --- a/services/pubsub/memory_test.go +++ b/services/pubsub/memory_test.go @@ -27,14 +27,9 @@ func TestMemoryBroker_HasNoTopicSubscribers(t *testing.T) { _, cancel := b.Subscribe("topic") cancel() assert.False(t, b.HasTopicSubscribers("topic")) -} -// Backend-specific: MemoryBroker prunes empty topics from its internal map so -// idle-user entries don't accumulate. -func TestMemoryBroker_CancelDeletesEmptyTopic(t *testing.T) { - b := NewMemoryBroker() - _, cancel := b.Subscribe("topic") - cancel() + // Backend-specific: MemoryBroker prunes empty topics from its internal map so + // idle-user entries don't accumulate. b.mu.RLock() defer b.mu.RUnlock() _, present := b.subs["topic"] diff --git a/services/pull/merge_test.go b/services/pull/merge_test.go index bb489e43c59..1b771bb2256 100644 --- a/services/pull/merge_test.go +++ b/services/pull/merge_test.go @@ -17,58 +17,36 @@ import ( ) func Test_expandDefaultMergeMessage(t *testing.T) { - type args struct { - template string - vars map[string]string - } tests := []struct { name string - args args - want string + template string wantBody string }{ { - name: "single line", - args: args{ - template: "Merge ${PullRequestTitle}", - vars: map[string]string{ - "PullRequestTitle": "PullRequestTitle", - "PullRequestDescription": "Pull\nRequest\nDescription\n", - }, - }, - want: "Merge PullRequestTitle", + name: "single line", + template: "Merge ${PullRequestTitle}", wantBody: "", }, { - name: "multiple lines", - args: args{ - template: "Merge ${PullRequestTitle}\nDescription:\n\n${PullRequestDescription}\n", - vars: map[string]string{ - "PullRequestTitle": "PullRequestTitle", - "PullRequestDescription": "Pull\nRequest\nDescription\n", - }, - }, - want: "Merge PullRequestTitle", + name: "multiple lines", + template: "Merge ${PullRequestTitle}\nDescription:\n\n${PullRequestDescription}\n", wantBody: "Description:\n\nPull\nRequest\nDescription\n", }, { - name: "leading newlines", - args: args{ - template: "\n\n\nMerge ${PullRequestTitle}\n\n\nDescription:\n\n${PullRequestDescription}\n", - vars: map[string]string{ - "PullRequestTitle": "PullRequestTitle", - "PullRequestDescription": "Pull\nRequest\nDescription\n", - }, - }, - want: "Merge PullRequestTitle", + name: "leading newlines", + template: "\n\n\nMerge ${PullRequestTitle}\n\n\nDescription:\n\n${PullRequestDescription}\n", wantBody: "Description:\n\nPull\nRequest\nDescription\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1 := expandDefaultMergeMessage(tt.args.template, tt.args.vars) - assert.Equalf(t, tt.want, got, "expandDefaultMergeMessage(%v, %v)", tt.args.template, tt.args.vars) - assert.Equalf(t, tt.wantBody, got1, "expandDefaultMergeMessage(%v, %v)", tt.args.template, tt.args.vars) + vars := map[string]string{ + "PullRequestTitle": "PullRequestTitle", + "PullRequestDescription": "Pull\nRequest\nDescription\n", + } + got, got1 := expandDefaultMergeMessage(tt.template, vars) + assert.Equalf(t, "Merge PullRequestTitle", got, "expandDefaultMergeMessage(%v, %v)", tt.template, vars) + assert.Equalf(t, tt.wantBody, got1, "expandDefaultMergeMessage(%v, %v)", tt.template, vars) }) } } @@ -99,47 +77,37 @@ func TestAddCommitMessageTailer(t *testing.T) { } func TestResolveMergeMessageTemplate(t *testing.T) { - t.Run("NoDefault", func(t *testing.T) { - repo, err := git.ForceFastImportWithInit(t.Context(), t.TempDir(), []git.FastImportCommit{ - {Ref: "refs/heads/master", Files: []git.FastImportFile{ - {Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"}, - }}, - }) - require.NoError(t, err) - gitRepo, err := git.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() + for _, tt := range []struct { + name string + want string + }{ + {"NoDefault", ""}, + {"WithDefault", "default template"}, + } { + t.Run(tt.name, func(t *testing.T) { + var files []git.FastImportFile + if tt.want != "" { + files = append(files, git.FastImportFile{Path: ".gitea/default_merge_message/DEFAULT_TEMPLATE.md", Content: tt.want}) + } + files = append(files, git.FastImportFile{Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"}) + repo, err := git.ForceFastImportWithInit(t.Context(), t.TempDir(), []git.FastImportCommit{ + {Ref: "refs/heads/master", Files: files}, + }) + require.NoError(t, err) + gitRepo, err := git.OpenRepository(t.Context(), repo) + require.NoError(t, err) + defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(t.Context(), "master") - require.NoError(t, err) - tmpl, err := resolveMergeMessageTemplate(t.Context(), gitRepo, commit, "merge") - assert.NoError(t, err) - assert.Equal(t, "", tmpl) - tmpl, err = resolveMergeMessageTemplate(t.Context(), gitRepo, commit, "rebase") - assert.NoError(t, err) - assert.Equal(t, "rebase template", tmpl) - }) - t.Run("WithDefault", func(t *testing.T) { - repo, err := git.ForceFastImportWithInit(t.Context(), t.TempDir(), []git.FastImportCommit{ - {Ref: "refs/heads/master", Files: []git.FastImportFile{ - {Path: ".gitea/default_merge_message/DEFAULT_TEMPLATE.md", Content: "default template"}, - {Path: ".gitea/default_merge_message/REBASE_TEMPLATE.md", Content: "rebase template"}, - }}, + commit, err := gitRepo.GetBranchCommit(t.Context(), "master") + require.NoError(t, err) + tmpl, err := resolveMergeMessageTemplate(t.Context(), gitRepo, commit, "merge") + assert.NoError(t, err) + assert.Equal(t, tt.want, tmpl) + tmpl, err = resolveMergeMessageTemplate(t.Context(), gitRepo, commit, "rebase") + assert.NoError(t, err) + assert.Equal(t, "rebase template", tmpl) }) - require.NoError(t, err) - gitRepo, err := git.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() - - commit, err := gitRepo.GetBranchCommit(t.Context(), "master") - require.NoError(t, err) - tmpl, err := resolveMergeMessageTemplate(t.Context(), gitRepo, commit, "merge") - assert.NoError(t, err) - assert.Equal(t, "default template", tmpl) - tmpl, err = resolveMergeMessageTemplate(t.Context(), gitRepo, commit, "rebase") - assert.NoError(t, err) - assert.Equal(t, "rebase template", tmpl) - }) + } } func TestMergeIntentWithMissingCommit(t *testing.T) { diff --git a/services/pull/review_test.go b/services/pull/review_test.go index d133dc76dcf..9e9a398f669 100644 --- a/services/pull/review_test.go +++ b/services/pull/review_test.go @@ -34,21 +34,15 @@ func TestDismissReview(t *testing.T) { }) assert.NoError(t, err) - issue.IsClosed = true - pull.HasMerged = false - assert.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "is_closed")) - assert.NoError(t, pull.UpdateCols(t.Context(), "has_merged")) - _, err = pull_service.DismissReview(t.Context(), review.ID, issue.RepoID, "", &user_model.User{}, false, false) - assert.Error(t, err) - assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err)) - - pull.HasMerged = true - pull.Issue.IsClosed = false - assert.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "is_closed")) - assert.NoError(t, pull.UpdateCols(t.Context(), "has_merged")) - _, err = pull_service.DismissReview(t.Context(), review.ID, issue.RepoID, "", &user_model.User{}, false, false) - assert.Error(t, err) - assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err)) + for _, hasMerged := range []bool{false, true} { + issue.IsClosed = !hasMerged + pull.HasMerged = hasMerged + assert.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue, "is_closed")) + assert.NoError(t, pull.UpdateCols(t.Context(), "has_merged")) + _, err = pull_service.DismissReview(t.Context(), review.ID, issue.RepoID, "", &user_model.User{}, false, false) + assert.Error(t, err) + assert.True(t, pull_service.IsErrDismissRequestOnClosedPR(err)) + } } func TestCreateCodeCommentContextOfUnchangedLine(t *testing.T) { diff --git a/services/pull/reviewer_test.go b/services/pull/reviewer_test.go index 81ca7891799..3640df22cff 100644 --- a/services/pull/reviewer_test.go +++ b/services/pull/reviewer_test.go @@ -54,17 +54,13 @@ func TestRepoGetReviewers(t *testing.T) { reviewers, err = pull_service.GetReviewers(ctx, repo3, 2, 2) assert.NoError(t, err) assert.Len(t, reviewers, 1) -} -func TestRepoGetReviewerTeams(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo2 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) teams, err := pull_service.GetReviewerTeams(t.Context(), repo2) assert.NoError(t, err) assert.Empty(t, teams) - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + repo3 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) teams, err = pull_service.GetReviewerTeams(t.Context(), repo3) assert.NoError(t, err) assert.Len(t, teams, 2) diff --git a/services/pull/update_test.go b/services/pull/update_test.go index f3cd27d05b2..ee5b117bee8 100644 --- a/services/pull/update_test.go +++ b/services/pull/update_test.go @@ -157,65 +157,49 @@ func TestIsUserAllowedToUpdate(t *testing.T) { assert.False(t, rebaseAllowed) }) - t.Run("MaintainerEditInheritsPosterPermissions", func(t *testing.T) { - pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) - pr3.AllowMaintainerEdit = true - protectedBranch := &git_model.ProtectedBranch{ - RepoID: pr3.HeadRepoID, - RuleName: pr3.HeadBranch, - CanPush: true, - CanForcePush: true, - } - _, err := db.GetEngine(t.Context()).Insert(protectedBranch) - require.NoError(t, err) - defer db.DeleteByBean(t.Context(), protectedBranch) + for _, testCase := range []struct { + name string + disableRebase bool + }{ + {"MaintainerEditInheritsPosterPermissions", false}, + {"MaintainerEditInheritsPosterPermissionsRebaseDisabled", true}, + } { + t.Run(testCase.name, func(t *testing.T) { + pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) + pr3.AllowMaintainerEdit = true + protectedBranch := &git_model.ProtectedBranch{ + RepoID: pr3.HeadRepoID, + RuleName: pr3.HeadBranch, + CanPush: true, + CanForcePush: true, + } + _, err := db.GetEngine(t.Context()).Insert(protectedBranch) + require.NoError(t, err) + defer db.DeleteByBean(t.Context(), protectedBranch) - collaboration := &repo_model.Collaboration{ - RepoID: pr3.HeadRepoID, - UserID: pr3Poster.ID, - Mode: perm.AccessModeWrite, - } - require.NoError(t, db.Insert(t.Context(), collaboration)) - defer db.DeleteByBean(t.Context(), collaboration) + collaboration := &repo_model.Collaboration{ + RepoID: pr3.HeadRepoID, + UserID: pr3Poster.ID, + Mode: perm.AccessModeWrite, + } + require.NoError(t, db.Insert(t.Context(), collaboration)) + defer db.DeleteByBean(t.Context(), collaboration) - require.NoError(t, pr3.LoadHeadRepo(t.Context())) - assert.NoError(t, access_model.RecalculateUserAccess(t.Context(), pr3.HeadRepo, pr3Poster.ID)) + require.NoError(t, pr3.LoadHeadRepo(t.Context())) + assert.NoError(t, access_model.RecalculateUserAccess(t.Context(), pr3.HeadRepo, pr3Poster.ID)) - pushAllowed, rebaseAllowed, _, err := checkUserAllowedToUpdate(t.Context(), pr3, pr3Poster) - assert.NoError(t, err) - assert.True(t, pushAllowed) - assert.True(t, rebaseAllowed) - }) + if testCase.disableRebase { + setRepoAllowRebaseUpdate(t, pr3.BaseRepoID, false) + } - t.Run("MaintainerEditInheritsPosterPermissionsRebaseDisabled", func(t *testing.T) { - pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) - pr3.AllowMaintainerEdit = true - protectedBranch := &git_model.ProtectedBranch{ - RepoID: pr3.HeadRepoID, - RuleName: pr3.HeadBranch, - CanPush: true, - CanForcePush: true, - } - _, err := db.GetEngine(t.Context()).Insert(protectedBranch) - require.NoError(t, err) - defer db.DeleteByBean(t.Context(), protectedBranch) - - collaboration := &repo_model.Collaboration{ - RepoID: pr3.HeadRepoID, - UserID: pr3Poster.ID, - Mode: perm.AccessModeWrite, - } - require.NoError(t, db.Insert(t.Context(), collaboration)) - defer db.DeleteByBean(t.Context(), collaboration) - - require.NoError(t, pr3.LoadHeadRepo(t.Context())) - assert.NoError(t, access_model.RecalculateUserAccess(t.Context(), pr3.HeadRepo, pr3Poster.ID)) - - setRepoAllowRebaseUpdate(t, pr3.BaseRepoID, false) - - pushAllowed, rebaseAllowed, _, err := checkUserAllowedToUpdate(t.Context(), pr3, pr3Poster) - assert.NoError(t, err) - assert.True(t, pushAllowed) - assert.False(t, rebaseAllowed) - }) + pushAllowed, rebaseAllowed, _, err := checkUserAllowedToUpdate(t.Context(), pr3, pr3Poster) + assert.NoError(t, err) + assert.True(t, pushAllowed) + if testCase.disableRebase { + assert.False(t, rebaseAllowed) + } else { + assert.True(t, rebaseAllowed) + } + }) + } } diff --git a/services/release/release_test.go b/services/release/release_test.go index 9603c125eb0..3747f948a2e 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -39,75 +39,30 @@ func TestRelease_Create(t *testing.T) { assert.NoError(t, err) defer gitRepo.Close() - assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v0.1", - Target: "master", - Title: "v0.1 is released", - Note: "v0.1 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, - }, nil, "")) - - assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v0.1.1", - Target: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - Title: "v0.1.1 is released", - Note: "v0.1.1 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, - }, nil, "")) - - assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v0.1.2", - Target: "65f1bf2", - Title: "v0.1.2 is released", - Note: "v0.1.2 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, - }, nil, "")) - - assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v0.1.3", - Target: "65f1bf2", - Title: "v0.1.3 is released", - Note: "v0.1.3 is released", - IsDraft: true, - IsPrerelease: false, - IsTag: false, - }, nil, "")) - - assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v0.1.4", - Target: "65f1bf2", - Title: "v0.1.4 is released", - Note: "v0.1.4 is released", - IsDraft: false, - IsPrerelease: true, - IsTag: false, - }, nil, "")) + for _, testCase := range []struct { + tagName, target string + isDraft, isPrerelease bool + }{ + {"v0.1", "master", false, false}, + {"v0.1.1", "65f1bf27bc3bf70f64657658635e66094edbcb4d", false, false}, + {"v0.1.2", "65f1bf2", false, false}, + {"v0.1.3", "65f1bf2", true, false}, + {"v0.1.4", "65f1bf2", false, true}, + } { + assert.NoError(t, CreateRelease(t.Context(), gitRepo, &repo_model.Release{ + RepoID: repo.ID, + Repo: repo, + PublisherID: user.ID, + Publisher: user, + TagName: testCase.tagName, + Target: testCase.target, + Title: testCase.tagName + " is released", + Note: testCase.tagName + " is released", + IsDraft: testCase.isDraft, + IsPrerelease: testCase.isPrerelease, + IsTag: false, + }, nil, "")) + } testPlayload := "testtest" @@ -328,76 +283,51 @@ func TestRelease_createTag(t *testing.T) { defer timeutil.MockSet(fakeNow)() advance := func() { fakeNow = fakeNow.Add(time.Second); timeutil.MockSet(fakeNow) } - // Test a changed release - release := &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v2.1.1", - Target: "master", - Title: "v2.1.1 is released", - Note: "v2.1.1 is released", - IsDraft: false, - IsPrerelease: false, - IsTag: false, + for _, testCase := range []struct { + tagName, target, state string + isDraft, isPrerelease bool + }{ + // Test a changed release + {"v2.1.1", "master", "released", false, false}, + // Test a changed draft + {"v2.2.1", "65f1bf2", "draft", true, false}, + // Test a changed pre-release + {"v2.3.1", "65f1bf2", "pre-released", false, true}, + } { + release := &repo_model.Release{ + RepoID: repo.ID, + Repo: repo, + PublisherID: user.ID, + Publisher: user, + TagName: testCase.tagName, + Target: testCase.target, + Title: testCase.tagName + " is " + testCase.state, + Note: testCase.tagName + " is " + testCase.state, + IsDraft: testCase.isDraft, + IsPrerelease: testCase.isPrerelease, + IsTag: false, + } + _, err = createTag(t.Context(), gitRepo, release, "") + assert.NoError(t, err) + if !testCase.isDraft && !testCase.isPrerelease { + assert.NotEmpty(t, release.CreatedUnix) + } + releaseCreatedUnix := release.CreatedUnix + advance() + if testCase.isDraft || testCase.isPrerelease { + release.Title = "Changed title" + } + if !testCase.isDraft { + release.Note = "Changed note" + } + _, err = createTag(t.Context(), gitRepo, release, "") + assert.NoError(t, err) + if testCase.isDraft { + assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix)) + } else { + assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix)) + } } - _, err = createTag(t.Context(), gitRepo, release, "") - assert.NoError(t, err) - assert.NotEmpty(t, release.CreatedUnix) - releaseCreatedUnix := release.CreatedUnix - advance() - release.Note = "Changed note" - _, err = createTag(t.Context(), gitRepo, release, "") - assert.NoError(t, err) - assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix)) - - // Test a changed draft - release = &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v2.2.1", - Target: "65f1bf2", - Title: "v2.2.1 is draft", - Note: "v2.2.1 is draft", - IsDraft: true, - IsPrerelease: false, - IsTag: false, - } - _, err = createTag(t.Context(), gitRepo, release, "") - assert.NoError(t, err) - releaseCreatedUnix = release.CreatedUnix - advance() - release.Title = "Changed title" - _, err = createTag(t.Context(), gitRepo, release, "") - assert.NoError(t, err) - assert.Less(t, int64(releaseCreatedUnix), int64(release.CreatedUnix)) - - // Test a changed pre-release - release = &repo_model.Release{ - RepoID: repo.ID, - Repo: repo, - PublisherID: user.ID, - Publisher: user, - TagName: "v2.3.1", - Target: "65f1bf2", - Title: "v2.3.1 is pre-released", - Note: "v2.3.1 is pre-released", - IsDraft: false, - IsPrerelease: true, - IsTag: false, - } - _, err = createTag(t.Context(), gitRepo, release, "") - assert.NoError(t, err) - releaseCreatedUnix = release.CreatedUnix - advance() - release.Title = "Changed title" - release.Note = "Changed note" - _, err = createTag(t.Context(), gitRepo, release, "") - assert.NoError(t, err) - assert.Equal(t, int64(releaseCreatedUnix), int64(release.CreatedUnix)) } func TestCreateNewTag(t *testing.T) { diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go index d1077ca625e..4c01f063d41 100644 --- a/services/repository/adopt_test.go +++ b/services/repository/adopt_test.go @@ -76,17 +76,13 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) { _ = os.Mkdir(filepath.Join(setting.RepoRootPath, unadopted+".git"), 0o755) } - opts := db.ListOptions{Page: 1, PageSize: 1} - repoNames, count, err := ListUnadoptedRepositories(t.Context(), "", &opts) - assert.NoError(t, err) - assert.EqualValues(t, 2, count) - assert.Equal(t, unadoptedList[0], repoNames[0]) - - opts = db.ListOptions{Page: 2, PageSize: 1} - repoNames, count, err = ListUnadoptedRepositories(t.Context(), "", &opts) - assert.NoError(t, err) - assert.EqualValues(t, 2, count) - assert.Equal(t, unadoptedList[1], repoNames[0]) + for i, unadopted := range unadoptedList { + opts := db.ListOptions{Page: i + 1, PageSize: 1} + repoNames, count, err := ListUnadoptedRepositories(t.Context(), "", &opts) + assert.NoError(t, err) + assert.EqualValues(t, 2, count) + assert.Equal(t, unadopted, repoNames[0]) + } } func TestAdoptRepository(t *testing.T) { diff --git a/services/repository/avatar_test.go b/services/repository/avatar_test.go index 67f41a1b602..4386c709705 100644 --- a/services/repository/avatar_test.go +++ b/services/repository/avatar_test.go @@ -17,48 +17,35 @@ import ( ) func TestUploadAvatar(t *testing.T) { - // Generate image - myImage := image.NewRGBA(image.Rect(0, 0, 1, 1)) - var buff bytes.Buffer - png.Encode(&buff, myImage) + for _, testCase := range []struct { + width int + deleteAvatar bool + }{ + {1, false}, + {5000, false}, + {1, true}, + } { + // Generate image + var buff bytes.Buffer + png.Encode(&buff, image.NewRGBA(image.Rect(0, 0, testCase.width, 1))) - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + assert.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) - err := UploadAvatar(t.Context(), repo, buff.Bytes()) - assert.NoError(t, err) - assert.Equal(t, avatar.HashAvatar(10, buff.Bytes()), repo.Avatar) -} - -func TestUploadBigAvatar(t *testing.T) { - // Generate BIG image - myImage := image.NewRGBA(image.Rect(0, 0, 5000, 1)) - var buff bytes.Buffer - png.Encode(&buff, myImage) - - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) - - err := UploadAvatar(t.Context(), repo, buff.Bytes()) - assert.Error(t, err) -} - -func TestDeleteAvatar(t *testing.T) { - // Generate image - myImage := image.NewRGBA(image.Rect(0, 0, 1, 1)) - var buff bytes.Buffer - png.Encode(&buff, myImage) - - assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) - - err := UploadAvatar(t.Context(), repo, buff.Bytes()) - assert.NoError(t, err) - - err = DeleteAvatar(t.Context(), repo) - assert.NoError(t, err) - - assert.Empty(t, repo.Avatar) + err := UploadAvatar(t.Context(), repo, buff.Bytes()) + if testCase.width == 5000 { + assert.Error(t, err) + continue + } + assert.NoError(t, err) + if testCase.deleteAvatar { + err = DeleteAvatar(t.Context(), repo) + assert.NoError(t, err) + assert.Empty(t, repo.Avatar) + } else { + assert.Equal(t, avatar.HashAvatar(10, buff.Bytes()), repo.Avatar) + } + } } func TestGenerateAvatar(t *testing.T) { diff --git a/services/repository/collaboration_test.go b/services/repository/collaboration_test.go index 2cb734fd06d..740c17324f2 100644 --- a/services/repository/collaboration_test.go +++ b/services/repository/collaboration_test.go @@ -24,14 +24,11 @@ func TestRepository_AddCollaborator(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - testSuccess := func(repo *repo_model.Repository, user *user_model.User) { + for _, repo := range []*repo_model.Repository{repo1, repo1, repo3} { assert.NoError(t, repo.LoadOwner(t.Context())) - assert.NoError(t, AddOrUpdateCollaborator(t.Context(), repo, user, perm.AccessModeWrite)) - unittest.CheckConsistencyFor(t, repo, user) + assert.NoError(t, AddOrUpdateCollaborator(t.Context(), repo, user4, perm.AccessModeWrite)) + unittest.CheckConsistencyFor(t, repo, user4) } - testSuccess(repo1, user4) - testSuccess(repo1, user4) - testSuccess(repo3, user4) assert.Error(t, AddOrUpdateCollaborator(t.Context(), repo1, user4, perm.AccessModeOwner)) assert.NoError(t, AddOrUpdateCollaborator(t.Context(), repo1, user4, perm.AccessModeAdmin)) @@ -44,11 +41,10 @@ func TestRepository_DeleteCollaboration(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) assert.NoError(t, repo.LoadOwner(t.Context())) - assert.NoError(t, DeleteCollaboration(t.Context(), repo, user)) - unittest.AssertNotExistsBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: user.ID}) - - assert.NoError(t, DeleteCollaboration(t.Context(), repo, user)) - unittest.AssertNotExistsBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: user.ID}) + for range 2 { + assert.NoError(t, DeleteCollaboration(t.Context(), repo, user)) + unittest.AssertNotExistsBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: user.ID}) + } unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID}) } diff --git a/services/repository/delete_test.go b/services/repository/delete_test.go index 7d1c1e4b6b0..4dfe3073f7c 100644 --- a/services/repository/delete_test.go +++ b/services/repository/delete_test.go @@ -34,10 +34,6 @@ func TestTeam_HasRepository(t *testing.T) { test(2, 3, true) test(2, 5, false) -} - -func TestTeam_RemoveRepository(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) testSuccess := func(teamID, repoID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go index 373e283e227..1cfc7d71f51 100644 --- a/services/repository/files/diff_test.go +++ b/services/repository/files/diff_test.go @@ -31,24 +31,21 @@ func TestGetDiffPreview(t *testing.T) { content := "# repo1\n\nDescription for repo1\nthis is a new line" t.Run("Errors", func(t *testing.T) { - t.Run("empty repo", func(t *testing.T) { - diff, err := GetDiffPreview(ctx, &repo_model.Repository{}, branch, treePath, oldContent, content) - assert.Nil(t, diff) - assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]") - }) - - t.Run("bad branch", func(t *testing.T) { - badBranch := "bad_branch" - diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, badBranch, treePath, oldContent, content) - assert.Nil(t, diff) - assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]") - }) - - t.Run("empty treePath", func(t *testing.T) { - diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, "", oldContent, content) - assert.Nil(t, diff) - assert.EqualError(t, err, "path is invalid [path: ]") - }) + for _, test := range []struct { + name string + repo *repo_model.Repository + branch, treePath, expectedError string + }{ + {"empty repo", &repo_model.Repository{}, branch, treePath, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]"}, + {"bad branch", ctx.Repo.Repository, "bad_branch", treePath, "branch does not exist [name: bad_branch]"}, + {"empty treePath", ctx.Repo.Repository, branch, "", "path is invalid [path: ]"}, + } { + t.Run(test.name, func(t *testing.T) { + diff, err := GetDiffPreview(ctx, test.repo, test.branch, test.treePath, oldContent, content) + assert.Nil(t, diff) + assert.EqualError(t, err, test.expectedError) + }) + } }) expectedDiff := &gitdiff.Diff{ @@ -133,23 +130,20 @@ func TestGetDiffPreview(t *testing.T) { IsIncomplete: false, } - t.Run("with given branch", func(t *testing.T) { - diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, treePath, oldContent, content) - assert.NoError(t, err) - expectedBs, err := json.Marshal(expectedDiff) - assert.NoError(t, err) - bs, err := json.Marshal(diff) - assert.NoError(t, err) - assert.JSONEq(t, string(expectedBs), string(bs)) - }) - - t.Run("empty branch, same results", func(t *testing.T) { - diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, "", treePath, oldContent, content) - assert.NoError(t, err) - expectedBs, err := json.Marshal(expectedDiff) - assert.NoError(t, err) - bs, err := json.Marshal(diff) - assert.NoError(t, err) - assert.JSONEq(t, string(expectedBs), string(bs)) - }) + for _, test := range []struct { + name, branch string + }{ + {"with given branch", branch}, + {"empty branch, same results", ""}, + } { + t.Run(test.name, func(t *testing.T) { + diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, test.branch, treePath, oldContent, content) + assert.NoError(t, err) + expectedBs, err := json.Marshal(expectedDiff) + assert.NoError(t, err) + bs, err := json.Marshal(diff) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedBs), string(bs)) + }) + } } diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index ea67e1248f5..d6a90fa18a8 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -78,7 +78,7 @@ func TestGetTreeViewNodes(t *testing.T) { } treeNodes, err := GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.GitRepo, ctx.Repo.Commit, "", "") assert.NoError(t, err) - assert.Equal(t, []*TreeViewNode{ + expectedNodes := []*TreeViewNode{ { EntryName: "docs", EntryMode: "tree", @@ -86,36 +86,22 @@ func TestGetTreeViewNodes(t *testing.T) { EntryIcon: mockIconForFolder(), EntryIconOpen: mockOpenIconForFolder(), }, - }, treeNodes) + } + assert.Equal(t, expectedNodes, treeNodes) treeNodes, err = GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.GitRepo, ctx.Repo.Commit, "", "docs/README.md") assert.NoError(t, err) - assert.Equal(t, []*TreeViewNode{ - { - EntryName: "docs", - EntryMode: "tree", - FullPath: "docs", - EntryIcon: mockIconForFolder(), - EntryIconOpen: mockOpenIconForFolder(), - Children: []*TreeViewNode{ - { - EntryName: "README.md", - EntryMode: "blob", - FullPath: "docs/README.md", - EntryIcon: mockIconForFile(`svg-mfi-readme`), - }, - }, - }, - }, treeNodes) - - treeNodes, err = GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.GitRepo, ctx.Repo.Commit, "docs", "README.md") - assert.NoError(t, err) - assert.Equal(t, []*TreeViewNode{ + expectedNodes[0].Children = []*TreeViewNode{ { EntryName: "README.md", EntryMode: "blob", FullPath: "docs/README.md", EntryIcon: mockIconForFile(`svg-mfi-readme`), }, - }, treeNodes) + } + assert.Equal(t, expectedNodes, treeNodes) + + treeNodes, err = GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.GitRepo, ctx.Repo.Commit, "docs", "README.md") + assert.NoError(t, err) + assert.Equal(t, expectedNodes[0].Children, treeNodes) } diff --git a/services/repository/gitgraph/graph_test.go b/services/repository/gitgraph/graph_test.go index 5e16432d486..3e7c01fa1c8 100644 --- a/services/repository/gitgraph/graph_test.go +++ b/services/repository/gitgraph/graph_test.go @@ -114,22 +114,25 @@ func TestReleaseUnusedColors(t *testing.T) { parser.firstAvailable = testcase.firstAvailable parser.firstInUse = testcase.firstInUse parser.releaseUnusedColors() + reportColorError := func(format string, color int) { + t.Errorf(format, + testcase.availableColors, + testcase.oldColors, + testcase.firstAvailable, + testcase.firstInUse, + parser.availableColors, + parser.oldColors, + parser.firstAvailable, + parser.firstInUse, + color) + } if parser.firstAvailable == -1 { // All in use for _, color := range parser.availableColors { found := slices.Contains(parser.oldColors, color) if !found { - t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not", - testcase.availableColors, - testcase.oldColors, - testcase.firstAvailable, - testcase.firstInUse, - parser.availableColors, - parser.oldColors, - parser.firstAvailable, - parser.firstInUse, - color) + reportColorError("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not", color) } } } else if parser.firstInUse != -1 { @@ -138,48 +141,21 @@ func TestReleaseUnusedColors(t *testing.T) { color := parser.availableColors[i] found := slices.Contains(parser.oldColors, color) if !found { - t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not", - testcase.availableColors, - testcase.oldColors, - testcase.firstAvailable, - testcase.firstInUse, - parser.availableColors, - parser.oldColors, - parser.firstAvailable, - parser.firstInUse, - color) + reportColorError("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not", color) } } for i := parser.firstAvailable; i != parser.firstInUse; i = (i + 1) % len(parser.availableColors) { color := parser.availableColors[i] found := slices.Contains(parser.oldColors, color) if found { - t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should not be available but is", - testcase.availableColors, - testcase.oldColors, - testcase.firstAvailable, - testcase.firstInUse, - parser.availableColors, - parser.oldColors, - parser.firstAvailable, - parser.firstInUse, - color) + reportColorError("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should not be available but is", color) } } } else { // None in use for _, color := range parser.oldColors { if color != 0 { - t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should not be available but is", - testcase.availableColors, - testcase.oldColors, - testcase.firstAvailable, - testcase.firstInUse, - parser.availableColors, - parser.oldColors, - parser.firstAvailable, - parser.firstInUse, - color) + reportColorError("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should not be available but is", color) } } } diff --git a/services/repository/license_test.go b/services/repository/license_test.go index d17d2ae8ba4..321b5e05191 100644 --- a/services/repository/license_test.go +++ b/services/repository/license_test.go @@ -107,49 +107,37 @@ func Test_resolveLicenses(t *testing.T) { require.NoError(t, err) defer gitRepo.Close() - // 1. repo with no license at all - require.NoError(t, git.ForceFastImport(t.Context(), gitRepo, []git.FastImportCommit{{ - Ref: "refs/heads/master", - Message: "empty", - }})) - commit, err := gitRepo.GetBranchCommit(t.Context(), "master") - require.NoError(t, err) + resolve := func(message string, files ...git.FastImportFile) ([]repo_model.DetectedLicense, error) { + require.NoError(t, git.ForceFastImport(t.Context(), gitRepo, []git.FastImportCommit{{ + Ref: "refs/heads/master", + Message: message, + Files: files, + }})) + commit, err := gitRepo.GetBranchCommit(t.Context(), "master") + require.NoError(t, err) + return resolveLicenses(t.Context(), gitRepo, commit) + } - licenses, err := resolveLicenses(t.Context(), gitRepo, commit) + // 1. repo with no license at all + licenses, err := resolve("empty") assert.Empty(t, licenses) assert.NoError(t, err) // 2. repo with a plain LICENSE file — classifier should detect MIT - require.NoError(t, git.ForceFastImport(t.Context(), gitRepo, []git.FastImportCommit{{ - Ref: "refs/heads/master", - Message: "add LICENSE", - Files: []git.FastImportFile{ - {Mode: git.EntryModeBlob, Path: "LICENSE", Content: string(mitLicense)}, - }, - }})) - commit, err = gitRepo.GetBranchCommit(t.Context(), "master") - require.NoError(t, err) - - licenses, err = resolveLicenses(t.Context(), gitRepo, commit) + licenses, err = resolve("add LICENSE", + git.FastImportFile{Mode: git.EntryModeBlob, Path: "LICENSE", Content: string(mitLicense)}, + ) assert.NoError(t, err) assert.Len(t, licenses, 1) assert.Equal(t, "MIT", licenses[0].SPDXID) assert.Equal(t, "LICENSE", licenses[0].LicensePath) // 3. repo with REUSE LICENSES/ dir — should take priority over root LICENSE - require.NoError(t, git.ForceFastImport(t.Context(), gitRepo, []git.FastImportCommit{{ - Ref: "refs/heads/master", - Message: "add LICENSES dir", - Files: []git.FastImportFile{ - {Mode: git.EntryModeBlob, Path: "LICENSE", Content: string(mitLicense)}, - {Mode: git.EntryModeBlob, Path: "LICENSES/MIT.txt", Content: "MIT license text"}, - {Mode: git.EntryModeBlob, Path: "LICENSES/Apache-2.0.txt", Content: "Apache license text"}, - }, - }})) - commit, err = gitRepo.GetBranchCommit(t.Context(), "master") - require.NoError(t, err) - - licenses, err = resolveLicenses(t.Context(), gitRepo, commit) + licenses, err = resolve("add LICENSES dir", + git.FastImportFile{Mode: git.EntryModeBlob, Path: "LICENSE", Content: string(mitLicense)}, + git.FastImportFile{Mode: git.EntryModeBlob, Path: "LICENSES/MIT.txt", Content: "MIT license text"}, + git.FastImportFile{Mode: git.EntryModeBlob, Path: "LICENSES/Apache-2.0.txt", Content: "Apache license text"}, + ) assert.NoError(t, err) assert.Len(t, licenses, 2) @@ -161,14 +149,7 @@ func Test_resolveLicenses(t *testing.T) { } // 4. remove all licenses — should return not exist - require.NoError(t, git.ForceFastImport(t.Context(), gitRepo, []git.FastImportCommit{{ - Ref: "refs/heads/master", - Message: "remove licenses", - }})) - commit, err = gitRepo.GetBranchCommit(t.Context(), "master") - require.NoError(t, err) - - licenses, err = resolveLicenses(t.Context(), gitRepo, commit) + licenses, err = resolve("remove licenses") assert.Empty(t, licenses) assert.NoError(t, err) } diff --git a/services/repository/repository_test.go b/services/repository/repository_test.go index d7bceae0329..a2f278984cf 100644 --- a/services/repository/repository_test.go +++ b/services/repository/repository_test.go @@ -71,45 +71,33 @@ func TestRepository_HasWiki(t *testing.T) { } func TestMakeRepoPrivateClearsWatches(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) + for _, name := range []string{"MakeRepoPrivate", "updateRepository"} { + t.Run(name, func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - assert.False(t, repo.IsPrivate) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + assert.False(t, repo.IsPrivate) - watchers, err := repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - require.NoError(t, err) - require.NotEmpty(t, watchers) + watchers, err := repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) + require.NoError(t, err) + require.NotEmpty(t, watchers) - assert.NoError(t, MakeRepoPrivate(t.Context(), repo, true)) + if name == "MakeRepoPrivate" { + assert.NoError(t, MakeRepoPrivate(t.Context(), repo, true)) + } else { + repo.IsPrivate = true + require.NoError(t, updateRepository(t.Context(), repo, true)) + } - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Empty(t, watchers) + watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) + assert.NoError(t, err) + assert.Empty(t, watchers) - updatedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) - assert.True(t, updatedRepo.IsPrivate) - assert.Zero(t, updatedRepo.NumWatches) -} - -// TestUpdateRepositoryClearsWatchesOnVisibilityChange ensures the shared updateRepository -// helper (used by the API EditRepo path) also clears watches when a repo goes private. -func TestUpdateRepositoryClearsWatchesOnVisibilityChange(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - assert.False(t, repo.IsPrivate) - - watchers, err := repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - require.NoError(t, err) - require.NotEmpty(t, watchers) - - repo.IsPrivate = true - require.NoError(t, updateRepository(t.Context(), repo, true)) - - watchers, err = repo_model.GetRepoWatchers(t.Context(), repo.ID, db.ListOptions{Page: 1}) - assert.NoError(t, err) - assert.Empty(t, watchers) - - updatedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) - assert.Zero(t, updatedRepo.NumWatches) + updatedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + if name == "MakeRepoPrivate" { + assert.True(t, updatedRepo.IsPrivate) + } + assert.Zero(t, updatedRepo.NumWatches) + }) + } } diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index 82a15ba76b6..95192b53018 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -78,32 +78,26 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) { assert.NoError(t, err) assert.False(t, hasAccess) - t.Run("RevokeAccessAfterRejection", func(t *testing.T) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - assert.NoError(t, repo.LoadOwner(t.Context())) - assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil)) - hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo) - assert.NoError(t, err) - assert.True(t, hasAccess) - assert.NoError(t, RejectRepositoryTransfer(t.Context(), repo, recipient)) - hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo) - assert.NoError(t, err) - assert.False(t, hasAccess) - }) - t.Run("RevokeAccessAfterCancel", func(t *testing.T) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - assert.NoError(t, repo.LoadOwner(t.Context())) - assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil)) - hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo) - assert.NoError(t, err) - assert.True(t, hasAccess) - transfer, err := repo_model.GetPendingRepositoryTransfer(t.Context(), repo) - assert.NoError(t, err) - assert.NoError(t, CancelRepositoryTransfer(t.Context(), transfer, doer)) - hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo) - assert.NoError(t, err) - assert.False(t, hasAccess) - }) + for _, name := range []string{"RevokeAccessAfterRejection", "RevokeAccessAfterCancel"} { + t.Run(name, func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + assert.NoError(t, repo.LoadOwner(t.Context())) + assert.NoError(t, StartRepositoryTransfer(t.Context(), doer, recipient, repo, nil)) + hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo) + assert.NoError(t, err) + assert.True(t, hasAccess) + if name == "RevokeAccessAfterRejection" { + assert.NoError(t, RejectRepositoryTransfer(t.Context(), repo, recipient)) + } else { + transfer, err := repo_model.GetPendingRepositoryTransfer(t.Context(), repo) + assert.NoError(t, err) + assert.NoError(t, CancelRepositoryTransfer(t.Context(), transfer, doer)) + } + hasAccess, err = access_model.HasAnyUnitAccess(t.Context(), recipient.ID, repo) + assert.NoError(t, err) + assert.False(t, hasAccess) + }) + } t.Run("KeepOriginAccessOnStop", func(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) assert.NoError(t, repo.LoadOwner(t.Context())) diff --git a/services/user/block_test.go b/services/user/block_test.go index f97d19ebf7d..f6a9857cd81 100644 --- a/services/user/block_test.go +++ b/services/user/block_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCanBlockUser(t *testing.T) { +func TestCanBlockAndUnblockUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) @@ -39,15 +39,8 @@ func TestCanBlockUser(t *testing.T) { assert.True(t, CanBlockUser(t.Context(), user1, user2, user4)) assert.True(t, CanBlockUser(t.Context(), user2, user2, user4)) assert.True(t, CanBlockUser(t.Context(), user2, org3, user29)) -} -func TestCanUnblockUser(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user28 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) // Doer can't self unblock diff --git a/services/user/update_test.go b/services/user/update_test.go index 7665c48f144..1b5a898b847 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -58,44 +58,28 @@ func TestUpdateUser(t *testing.T) { } assert.NoError(t, UpdateUser(t.Context(), user, opts)) - assert.Equal(t, opts.KeepEmailPrivate.Value(), user.KeepEmailPrivate) - assert.Equal(t, opts.FullName.Value(), user.FullName) - assert.Equal(t, opts.Website.Value(), user.Website) - assert.Equal(t, opts.Location.Value(), user.Location) - assert.Equal(t, opts.Description.Value(), user.Description) - assert.Equal(t, opts.AllowGitHook.Value(), user.AllowGitHook) - assert.Equal(t, opts.AllowImportLocal.Value(), user.AllowImportLocal) - assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation) - assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) - assert.Equal(t, opts.IsActive.Value(), user.IsActive) - assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) - assert.Equal(t, opts.Visibility.Value(), user.Visibility) - assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) - assert.Equal(t, opts.Language.Value(), user.Language) - assert.Equal(t, opts.Theme.Value(), user.Theme) - assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle) - assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization) - assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference) - - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - assert.Equal(t, opts.KeepEmailPrivate.Value(), user.KeepEmailPrivate) - assert.Equal(t, opts.FullName.Value(), user.FullName) - assert.Equal(t, opts.Website.Value(), user.Website) - assert.Equal(t, opts.Location.Value(), user.Location) - assert.Equal(t, opts.Description.Value(), user.Description) - assert.Equal(t, opts.AllowGitHook.Value(), user.AllowGitHook) - assert.Equal(t, opts.AllowImportLocal.Value(), user.AllowImportLocal) - assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation) - assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) - assert.Equal(t, opts.IsActive.Value(), user.IsActive) - assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) - assert.Equal(t, opts.Visibility.Value(), user.Visibility) - assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) - assert.Equal(t, opts.Language.Value(), user.Language) - assert.Equal(t, opts.Theme.Value(), user.Theme) - assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle) - assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization) - assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference) + assertUpdatedUser := func(user *user_model.User) { + assert.Equal(t, opts.KeepEmailPrivate.Value(), user.KeepEmailPrivate) + assert.Equal(t, opts.FullName.Value(), user.FullName) + assert.Equal(t, opts.Website.Value(), user.Website) + assert.Equal(t, opts.Location.Value(), user.Location) + assert.Equal(t, opts.Description.Value(), user.Description) + assert.Equal(t, opts.AllowGitHook.Value(), user.AllowGitHook) + assert.Equal(t, opts.AllowImportLocal.Value(), user.AllowImportLocal) + assert.Equal(t, opts.MaxRepoCreation.Value(), user.MaxRepoCreation) + assert.Equal(t, opts.IsRestricted.Value(), user.IsRestricted) + assert.Equal(t, opts.IsActive.Value(), user.IsActive) + assert.Equal(t, opts.IsAdmin.Value().FieldValue, user.IsAdmin) + assert.Equal(t, opts.Visibility.Value(), user.Visibility) + assert.Equal(t, opts.KeepActivityPrivate.Value(), user.KeepActivityPrivate) + assert.Equal(t, opts.Language.Value(), user.Language) + assert.Equal(t, opts.Theme.Value(), user.Theme) + assert.Equal(t, opts.DiffViewStyle.Value(), user.DiffViewStyle) + assert.Equal(t, opts.AllowCreateOrganization.Value(), user.AllowCreateOrganization) + assert.Equal(t, opts.EmailNotificationsPreference.Value(), user.EmailNotificationsPreference) + } + assertUpdatedUser(user) + assertUpdatedUser(unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28})) } func TestUpdateAuth(t *testing.T) { diff --git a/services/user/user_test.go b/services/user/user_test.go index 5d275d71f94..bad6a2ab96d 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -76,14 +76,10 @@ func TestDeleteUser(t *testing.T) { unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 2}) // delete users - user24 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 24}) - assert.NoError(t, DeleteUser(t.Context(), user24, true)) - - user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) - assert.NoError(t, DeleteUser(t.Context(), user32, true)) - - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - assert.NoError(t, DeleteUser(t.Context(), user2, true)) + for _, userID := range []int64{24, 32, 2} { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) + assert.NoError(t, DeleteUser(t.Context(), user, true)) + } // assert they do not exist after deletion unittest.AssertNotExistsBean(t, &auth.TwoFactor{UID: 24}) @@ -112,7 +108,7 @@ func TestDeleteUserUnlinkedAttachments(t *testing.T) { } func TestPurgeUser(t *testing.T) { - test := func(userID int64) { + for _, userID := range []int64{2, 4, 8, 11} { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) @@ -122,11 +118,6 @@ func TestPurgeUser(t *testing.T) { unittest.AssertNotExistsBean(t, &user_model.User{ID: userID}) unittest.CheckConsistencyFor(t, &user_model.User{}, &repo_model.Repository{}) } - test(2) - test(4) - test(8) - test(11) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) assert.Error(t, DeleteUser(t.Context(), org, false)) } diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index cf954bbfc44..7daf9cf8c58 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -157,55 +157,38 @@ func TestWebhookDeliverHookTask(t *testing.T) { } assert.NoError(t, webhook_model.CreateWebhook(t.Context(), hook)) - t.Run("Version 1", func(t *testing.T) { - testVersion = 1 - hookTask := &webhook_model.HookTask{ - HookID: hook.ID, - EventType: webhook_module.HookEventPush, - PayloadContent: `{"data": 42}`, - PayloadVersion: 1, - } + for version, name := range []string{"Version 1", "Version 2"} { + t.Run(name, func(t *testing.T) { + payloadContent := `{"data": 42}` + if version == 1 { + p := pushTestPayload() + data, err := p.JSONPayload() + assert.NoError(t, err) + payloadContent = string(data) + } - hookTask, err := webhook_model.CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - assert.NotNil(t, hookTask) + testVersion = version + 1 + hookTask := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: webhook_module.HookEventPush, + PayloadContent: payloadContent, + PayloadVersion: testVersion, + } - assert.NoError(t, Deliver(t.Context(), hookTask)) - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("waited to long for request to happen") - } + hookTask, err := webhook_model.CreateHookTask(t.Context(), hookTask) + assert.NoError(t, err) + assert.NotNil(t, hookTask) - assert.True(t, hookTask.IsSucceed) - }) + assert.NoError(t, Deliver(t.Context(), hookTask)) + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("waited to long for request to happen") + } - t.Run("Version 2", func(t *testing.T) { - p := pushTestPayload() - data, err := p.JSONPayload() - assert.NoError(t, err) - - testVersion = 2 - hookTask := &webhook_model.HookTask{ - HookID: hook.ID, - EventType: webhook_module.HookEventPush, - PayloadContent: string(data), - PayloadVersion: 2, - } - - hookTask, err = webhook_model.CreateHookTask(t.Context(), hookTask) - assert.NoError(t, err) - assert.NotNil(t, hookTask) - - assert.NoError(t, Deliver(t.Context(), hookTask)) - select { - case <-done: - case <-time.After(5 * time.Second): - t.Fatal("waited to long for request to happen") - } - - assert.True(t, hookTask.IsSucceed) - }) + assert.True(t, hookTask.IsSucceed) + }) + } } func TestWebhookDeliverSpecificTypes(t *testing.T) { diff --git a/services/webhook/dingtalk_test.go b/services/webhook/dingtalk_test.go index afe391d8c5a..bf3a43a4e8f 100644 --- a/services/webhook/dingtalk_test.go +++ b/services/webhook/dingtalk_test.go @@ -27,6 +27,12 @@ func TestDingTalkPayload(t *testing.T) { return "" } dc := dingtalkConvertor{} + assertPayload := func(t *testing.T, pl DingtalkPayload, text, title, singleTitle, singleURL string) { + assert.Equal(t, text, pl.ActionCard.Text) + assert.Equal(t, title, pl.ActionCard.Title) + assert.Equal(t, singleTitle, pl.ActionCard.SingleTitle) + assert.Equal(t, singleURL, parseRealSingleURL(pl.ActionCard.SingleURL)) + } t.Run("Create", func(t *testing.T) { p := createTestPayload() @@ -34,10 +40,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Create(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] branch test created", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] branch test created", pl.ActionCard.Title) - assert.Equal(t, "view ref test", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] branch test created", "[test/repo] branch test created", "view ref test", "http://localhost:3000/test/repo/src/test") }) t.Run("Delete", func(t *testing.T) { @@ -46,10 +49,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Delete(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] branch test deleted", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] branch test deleted", pl.ActionCard.Title) - assert.Equal(t, "view ref test", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] branch test deleted", "[test/repo] branch test deleted", "view ref test", "http://localhost:3000/test/repo/src/test") }) t.Run("Fork", func(t *testing.T) { @@ -58,10 +58,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Fork(p) require.NoError(t, err) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.ActionCard.Text) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.ActionCard.Title) - assert.Equal(t, "view forked repo test/repo", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "test/repo2 is forked to test/repo", "test/repo2 is forked to test/repo", "view forked repo test/repo", "http://localhost:3000/test/repo") }) t.Run("Push", func(t *testing.T) { @@ -70,10 +67,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Push(p) require.NoError(t, err) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.ActionCard.Text) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.ActionCard.Title) - assert.Equal(t, "view commits", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", "[test/repo:test] 2 new commits", "view commits", "http://localhost:3000/test/repo/src/test") }) t.Run("Issue", func(t *testing.T) { @@ -83,19 +77,13 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Issue(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", pl.ActionCard.Text) - assert.Equal(t, "#2 crash", pl.ActionCard.Title) - assert.Equal(t, "view issue", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Issue opened: #2 crash by user1\r\n\r\nissue body", "#2 crash", "view issue", "http://localhost:3000/test/repo/issues/2") p.Action = api.HookIssueClosed pl, err = dc.Issue(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Issue closed: #2 crash by user1", pl.ActionCard.Text) - assert.Equal(t, "#2 crash", pl.ActionCard.Title) - assert.Equal(t, "view issue", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Issue closed: #2 crash by user1", "#2 crash", "view issue", "http://localhost:3000/test/repo/issues/2") }) t.Run("IssueComment", func(t *testing.T) { @@ -104,10 +92,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.IssueComment(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", pl.ActionCard.Text) - assert.Equal(t, "#2 crash", pl.ActionCard.Title) - assert.Equal(t, "view issue comment", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] New comment on issue #2 crash by user1\r\n\r\nmore info needed", "#2 crash", "view issue comment", "http://localhost:3000/test/repo/issues/2#issuecomment-4") }) t.Run("PullRequest", func(t *testing.T) { @@ -116,10 +101,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.PullRequest(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", pl.ActionCard.Text) - assert.Equal(t, "#12 Fix bug", pl.ActionCard.Title) - assert.Equal(t, "view pull request", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Pull request opened: #12 Fix bug by user1\r\n\r\nfixes bug #2", "#12 Fix bug", "view pull request", "http://localhost:3000/test/repo/pulls/12") }) t.Run("PullRequestComment", func(t *testing.T) { @@ -128,10 +110,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.IssueComment(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", pl.ActionCard.Text) - assert.Equal(t, "#12 Fix bug", pl.ActionCard.Title) - assert.Equal(t, "view issue comment", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] New comment on pull request #12 Fix bug by user1\r\n\r\nchanges requested", "#12 Fix bug", "view issue comment", "http://localhost:3000/test/repo/pulls/12#issuecomment-4") }) t.Run("Review", func(t *testing.T) { @@ -141,10 +120,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug", pl.ActionCard.Title) - assert.Equal(t, "view pull request", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", "[test/repo] Pull request review approved : #12 Fix bug", "view pull request", "http://localhost:3000/test/repo/pulls/12") }) t.Run("Repository", func(t *testing.T) { @@ -153,10 +129,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Repository(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Repository created", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] Repository created", pl.ActionCard.Title) - assert.Equal(t, "view repository", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Repository created", "[test/repo] Repository created", "view repository", "http://localhost:3000/test/repo") }) t.Run("Package", func(t *testing.T) { @@ -165,10 +138,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Package(p) require.NoError(t, err) - assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.ActionCard.Text) - assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.ActionCard.Title) - assert.Equal(t, "view package", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "Package created: GiteaContainer:latest by user1", "Package created: GiteaContainer:latest by user1", "view package", "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest") }) t.Run("Wiki", func(t *testing.T) { @@ -178,28 +148,19 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Wiki(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", pl.ActionCard.Title) - assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] New wiki page 'index' (Wiki change comment) by user1", "[test/repo] New wiki page 'index' (Wiki change comment) by user1", "view wiki", "http://localhost:3000/test/repo/wiki/index") p.Action = api.HookWikiEdited pl, err = dc.Wiki(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", pl.ActionCard.Title) - assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", "[test/repo] Wiki page 'index' edited (Wiki change comment) by user1", "view wiki", "http://localhost:3000/test/repo/wiki/index") p.Action = api.HookWikiDeleted pl, err = dc.Wiki(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.ActionCard.Title) - assert.Equal(t, "view wiki", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Wiki page 'index' deleted by user1", "[test/repo] Wiki page 'index' deleted by user1", "view wiki", "http://localhost:3000/test/repo/wiki/index") }) t.Run("Release", func(t *testing.T) { @@ -208,10 +169,7 @@ func TestDingTalkPayload(t *testing.T) { pl, err := dc.Release(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.ActionCard.Text) - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.ActionCard.Title) - assert.Equal(t, "view release", pl.ActionCard.SingleTitle) - assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", parseRealSingleURL(pl.ActionCard.SingleURL)) + assertPayload(t, pl, "[test/repo] Release created: v1.0 by user1", "[test/repo] Release created: v1.0 by user1", "view release", "http://localhost:3000/test/repo/releases/tag/v1.0") }) } diff --git a/services/webhook/discord_test.go b/services/webhook/discord_test.go index 162a15f707c..f31dd766e6c 100644 --- a/services/webhook/discord_test.go +++ b/services/webhook/discord_test.go @@ -18,6 +18,21 @@ import ( func TestDiscordPayload(t *testing.T) { dc := discordConvertor{} + assertPayload := func(t *testing.T, pl DiscordPayload, sender *api.User, title, description, link string) { + assert.Len(t, pl.Embeds, 1) + assert.Equal(t, title, pl.Embeds[0].Title) + if description == "" { + assert.Empty(t, pl.Embeds[0].Description) + } else { + assert.Equal(t, description, pl.Embeds[0].Description) + } + if link != "" { + assert.Equal(t, link, pl.Embeds[0].URL) + } + assert.Equal(t, sender.UserName, pl.Embeds[0].Author.Name) + assert.Equal(t, setting.AppURL+sender.UserName, pl.Embeds[0].Author.URL) + assert.Equal(t, sender.AvatarURL, pl.Embeds[0].Author.IconURL) + } t.Run("Create", func(t *testing.T) { p := createTestPayload() @@ -25,13 +40,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Create(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] branch test created", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] branch test created", "", "http://localhost:3000/test/repo/src/test") }) t.Run("Delete", func(t *testing.T) { @@ -40,13 +49,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Delete(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] branch test deleted", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] branch test deleted", "", "http://localhost:3000/test/repo/src/test") }) t.Run("Fork", func(t *testing.T) { @@ -55,13 +58,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Fork(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "test/repo2 is forked to test/repo", "", "http://localhost:3000/test/repo") }) t.Run("Push", func(t *testing.T) { @@ -70,13 +67,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Push(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo:test] 2 new commits", "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", "http://localhost:3000/test/repo/src/test") }) t.Run("PushWithMultilineCommitMessage", func(t *testing.T) { @@ -85,12 +76,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Push(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1", pl.Embeds[0].Description) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo:test] 2 new commits", "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1", "") }) t.Run("PushWithLongCommitSummary", func(t *testing.T) { @@ -99,12 +85,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Push(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1", pl.Embeds[0].Description) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo:test] 2 new commits", "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1", "") }) t.Run("Issue", func(t *testing.T) { @@ -114,25 +95,13 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Issue(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Embeds[0].Title) - assert.Equal(t, "issue body", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Issue opened: #2 crash", "issue body", "http://localhost:3000/test/repo/issues/2") p.Action = api.HookIssueClosed pl, err = dc.Issue(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Issue closed: #2 crash", "", "http://localhost:3000/test/repo/issues/2") }) t.Run("IssueComment", func(t *testing.T) { @@ -141,13 +110,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.IssueComment(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Embeds[0].Title) - assert.Equal(t, "more info needed", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] New comment on issue #2 crash", "more info needed", "http://localhost:3000/test/repo/issues/2#issuecomment-4") }) t.Run("PullRequest", func(t *testing.T) { @@ -156,13 +119,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.PullRequest(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Embeds[0].Title) - assert.Equal(t, "fixes bug #2", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Pull request opened: #12 Fix bug", "fixes bug #2", "http://localhost:3000/test/repo/pulls/12") }) t.Run("PullRequestComment", func(t *testing.T) { @@ -171,13 +128,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.IssueComment(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Embeds[0].Title) - assert.Equal(t, "changes requested", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] New comment on pull request #12 Fix bug", "changes requested", "http://localhost:3000/test/repo/pulls/12#issuecomment-4") }) t.Run("Review", func(t *testing.T) { @@ -187,13 +138,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Embeds[0].Title) - assert.Equal(t, "good job", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Pull request review approved: #12 Fix bug", "good job", "http://localhost:3000/test/repo/pulls/12") }) t.Run("Repository", func(t *testing.T) { @@ -202,13 +147,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Repository(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Repository created", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Repository created", "", "http://localhost:3000/test/repo") }) t.Run("Package", func(t *testing.T) { @@ -217,13 +156,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Package(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "Package created: GiteaContainer:latest", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "Package created: GiteaContainer:latest", "", "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest") }) t.Run("Wiki", func(t *testing.T) { @@ -233,37 +166,19 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Wiki(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Embeds[0].Title) - assert.Equal(t, "Wiki change comment", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] New wiki page 'index' (Wiki change comment)", "Wiki change comment", "http://localhost:3000/test/repo/wiki/index") p.Action = api.HookWikiEdited pl, err = dc.Wiki(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Embeds[0].Title) - assert.Equal(t, "Wiki change comment", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Wiki page 'index' edited (Wiki change comment)", "Wiki change comment", "http://localhost:3000/test/repo/wiki/index") p.Action = api.HookWikiDeleted pl, err = dc.Wiki(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Embeds[0].Title) - assert.Empty(t, pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Wiki page 'index' deleted", "", "http://localhost:3000/test/repo/wiki/index") }) t.Run("Release", func(t *testing.T) { @@ -272,13 +187,7 @@ func TestDiscordPayload(t *testing.T) { pl, err := dc.Release(p) require.NoError(t, err) - assert.Len(t, pl.Embeds, 1) - assert.Equal(t, "[test/repo] Release created: v1.0", pl.Embeds[0].Title) - assert.Equal(t, "Note of first stable release", pl.Embeds[0].Description) - assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.Embeds[0].URL) - assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name) - assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL) - assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL) + assertPayload(t, pl, p.Sender, "[test/repo] Release created: v1.0", "Note of first stable release", "http://localhost:3000/test/repo/releases/tag/v1.0") }) } diff --git a/services/webhook/feishu_test.go b/services/webhook/feishu_test.go index f8a5f7a983c..63d86e6aba4 100644 --- a/services/webhook/feishu_test.go +++ b/services/webhook/feishu_test.go @@ -17,41 +17,68 @@ import ( func TestFeishuPayload(t *testing.T) { fc := feishuConvertor{} - t.Run("Create", func(t *testing.T) { - p := createTestPayload() - - pl, err := fc.Create(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] branch test created`, pl.Content.Text) - }) - - t.Run("Delete", func(t *testing.T) { - p := deleteTestPayload() - - pl, err := fc.Delete(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] branch test deleted`, pl.Content.Text) - }) - - t.Run("Fork", func(t *testing.T) { - p := forkTestPayload() - - pl, err := fc.Fork(p) - require.NoError(t, err) - - assert.Equal(t, `test/repo2 is forked to test/repo`, pl.Content.Text) - }) - - t.Run("Push", func(t *testing.T) { - p := pushTestPayload() - - pl, err := fc.Push(p) - require.NoError(t, err) - - assert.Equal(t, "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Content.Text) - }) + for _, test := range []struct { + name string + convert func() (FeishuPayload, error) + text string + }{ + { + "Create", + func() (FeishuPayload, error) { return fc.Create(createTestPayload()) }, + `[test/repo] branch test created`, + }, + { + "Delete", + func() (FeishuPayload, error) { return fc.Delete(deleteTestPayload()) }, + `[test/repo] branch test deleted`, + }, + { + "Fork", + func() (FeishuPayload, error) { return fc.Fork(forkTestPayload()) }, + `test/repo2 is forked to test/repo`, + }, + { + "Push", + func() (FeishuPayload, error) { return fc.Push(pushTestPayload()) }, + "[test/repo:test] \r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\r\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", + }, + { + "IssueComment", + func() (FeishuPayload, error) { return fc.IssueComment(issueCommentTestPayload()) }, + "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", + }, + { + "PullRequest", + func() (FeishuPayload, error) { return fc.PullRequest(pullRequestTestPayload()) }, + "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", + }, + { + "PullRequestComment", + func() (FeishuPayload, error) { return fc.IssueComment(pullRequestCommentTestPayload()) }, + "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", + }, + { + "Repository", + func() (FeishuPayload, error) { return fc.Repository(repositoryTestPayload()) }, + "[test/repo] Repository created", + }, + { + "Package", + func() (FeishuPayload, error) { return fc.Package(packageTestPayload()) }, + "Package created: GiteaContainer:latest by user1", + }, + { + "Release", + func() (FeishuPayload, error) { return fc.Release(pullReleaseTestPayload()) }, + "[test/repo] Release created: v1.0 by user1", + }, + } { + t.Run(test.name, func(t *testing.T) { + pl, err := test.convert() + require.NoError(t, err) + assert.Equal(t, test.text, pl.Content.Text) + }) + } t.Run("Issue", func(t *testing.T) { p := issueTestPayload() @@ -69,33 +96,6 @@ func TestFeishuPayload(t *testing.T) { assert.Equal(t, "[Issue-test/repo #2]: closed\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\nAssignees: user1\n\nissue body", pl.Content.Text) }) - t.Run("IssueComment", func(t *testing.T) { - p := issueCommentTestPayload() - - pl, err := fc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, "[Comment-test/repo #2]: created\ncrash\nhttp://localhost:3000/test/repo/issues/2\nIssue by user1\nOperator: user1\n\nmore info needed", pl.Content.Text) - }) - - t.Run("PullRequest", func(t *testing.T) { - p := pullRequestTestPayload() - - pl, err := fc.PullRequest(p) - require.NoError(t, err) - - assert.Equal(t, "[PullRequest-test/repo #12]: opened\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\nAssignees: user1\n\nfixes bug #2", pl.Content.Text) - }) - - t.Run("PullRequestComment", func(t *testing.T) { - p := pullRequestCommentTestPayload() - - pl, err := fc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, "[Comment-test/repo #12]: created\nFix bug\nhttp://localhost:3000/test/repo/pulls/12\nPullRequest by user1\nOperator: user1\n\nchanges requested", pl.Content.Text) - }) - t.Run("Review", func(t *testing.T) { p := pullRequestTestPayload() p.Action = api.HookIssueReviewed @@ -106,24 +106,6 @@ func TestFeishuPayload(t *testing.T) { assert.Equal(t, "[test/repo] Pull request review approved : #12 Fix bug\r\n\r\ngood job", pl.Content.Text) }) - t.Run("Repository", func(t *testing.T) { - p := repositoryTestPayload() - - pl, err := fc.Repository(p) - require.NoError(t, err) - - assert.Equal(t, "[test/repo] Repository created", pl.Content.Text) - }) - - t.Run("Package", func(t *testing.T) { - p := packageTestPayload() - - pl, err := fc.Package(p) - require.NoError(t, err) - - assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.Content.Text) - }) - t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() @@ -145,15 +127,6 @@ func TestFeishuPayload(t *testing.T) { assert.Equal(t, "[test/repo] Wiki page 'index' deleted by user1", pl.Content.Text) }) - - t.Run("Release", func(t *testing.T) { - p := pullReleaseTestPayload() - - pl, err := fc.Release(p) - require.NoError(t, err) - - assert.Equal(t, "[test/repo] Release created: v1.0 by user1", pl.Content.Text) - }) } func TestFeishuJSONPayload(t *testing.T) { diff --git a/services/webhook/general_test.go b/services/webhook/general_test.go index 43649f2af5c..dcf14190e5a 100644 --- a/services/webhook/general_test.go +++ b/services/webhook/general_test.go @@ -383,202 +383,113 @@ func packageTestPayload() *api.PackagePayload { } func TestGetIssuesPayloadInfo(t *testing.T) { - p := issueTestPayload() - p.Changes = &api.ChangesPayload{AddedAssignees: p.Issue.Assignees} + issue := issueTestPayload() + issue.Changes = &api.ChangesPayload{AddedAssignees: issue.Issue.Assignees} + pull := pullRequestTestPayload() + pull.Changes = &api.ChangesPayload{AddedAssignees: pull.PullRequest.Assignees} cases := []struct { - action api.HookIssueAction - text string - issueTitle string - attachmentText string - color int + action api.HookIssueAction + issueText string + pullRequestText string + issueMarkdown string + pullRequestMarkdown string + issueColor int + pullRequestColor int }{ { api.HookIssueOpened, "[test/repo] Issue opened: #2 crash by user1", - "#2 crash", - "issue body", - orangeColor, + "[test/repo] Pull request opened: #12 Fix bug by user1", + "issue body", "fixes bug #2", + orangeColor, greenColor, }, { api.HookIssueClosed, "[test/repo] Issue closed: #2 crash by user1", - "#2 crash", - "", - redColor, + "[test/repo] Pull request closed: #12 Fix bug by user1", + "", "", + redColor, redColor, }, { api.HookIssueReOpened, "[test/repo] Issue re-opened: #2 crash by user1", - "#2 crash", - "", - yellowColor, + "[test/repo] Pull request re-opened: #12 Fix bug by user1", + "", "", + yellowColor, yellowColor, }, { api.HookIssueEdited, "[test/repo] Issue edited: #2 crash by user1", - "#2 crash", - "issue body", - yellowColor, + "[test/repo] Pull request edited: #12 Fix bug by user1", + "issue body", "fixes bug #2", + yellowColor, yellowColor, }, { api.HookIssueAssigned, "[test/repo] Issue assigned to user1: #2 crash by user1", - "#2 crash", - "", - greenColor, + "[test/repo] Pull request assigned to user1: #12 Fix bug by user1", + "", "", + greenColor, greenColor, }, { api.HookIssueUnassigned, "[test/repo] Issue unassigned: #2 crash by user1", - "#2 crash", - "", - yellowColor, + "[test/repo] Pull request unassigned: #12 Fix bug by user1", + "", "", + yellowColor, yellowColor, }, { api.HookIssueLabelUpdated, "[test/repo] Issue labels updated: #2 crash by user1", - "#2 crash", - "", - yellowColor, + "[test/repo] Pull request labels updated: #12 Fix bug by user1", + "", "", + yellowColor, yellowColor, }, { api.HookIssueLabelCleared, "[test/repo] Issue labels cleared: #2 crash by user1", - "#2 crash", - "", - yellowColor, + "[test/repo] Pull request labels cleared: #12 Fix bug by user1", + "", "", + yellowColor, yellowColor, }, { api.HookIssueSynchronized, "[test/repo] Issue synchronized: #2 crash by user1", - "#2 crash", - "", - yellowColor, + "[test/repo] Pull request synchronized: #12 Fix bug by user1", + "", "", + yellowColor, yellowColor, }, { api.HookIssueMilestoned, "[test/repo] Issue milestoned to Milestone Title: #2 crash by user1", - "#2 crash", - "", - yellowColor, + "[test/repo] Pull request milestoned to Milestone Title: #12 Fix bug by user1", + "", "", + yellowColor, yellowColor, }, { api.HookIssueDemilestoned, "[test/repo] Issue milestone cleared: #2 crash by user1", - "#2 crash", - "", - yellowColor, - }, - } - - for i, c := range cases { - p.Action = c.action - text, issueTitle, extraMarkdown, color := getIssuesPayloadInfo(p, noneLinkFormatter, true) - assert.Equal(t, c.text, text, "case %d", i) - assert.Equal(t, c.issueTitle, issueTitle, "case %d", i) - assert.Equal(t, c.attachmentText, extraMarkdown, "case %d", i) - assert.Equal(t, c.color, color, "case %d", i) - } -} - -func TestGetPullRequestPayloadInfo(t *testing.T) { - p := pullRequestTestPayload() - p.Changes = &api.ChangesPayload{AddedAssignees: p.PullRequest.Assignees} - - cases := []struct { - action api.HookIssueAction - text string - issueTitle string - attachmentText string - color int - }{ - { - api.HookIssueOpened, - "[test/repo] Pull request opened: #12 Fix bug by user1", - "#12 Fix bug", - "fixes bug #2", - greenColor, - }, - { - api.HookIssueClosed, - "[test/repo] Pull request closed: #12 Fix bug by user1", - "#12 Fix bug", - "", - redColor, - }, - { - api.HookIssueReOpened, - "[test/repo] Pull request re-opened: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, - }, - { - api.HookIssueEdited, - "[test/repo] Pull request edited: #12 Fix bug by user1", - "#12 Fix bug", - "fixes bug #2", - yellowColor, - }, - { - api.HookIssueAssigned, - "[test/repo] Pull request assigned to user1: #12 Fix bug by user1", - "#12 Fix bug", - "", - greenColor, - }, - { - api.HookIssueUnassigned, - "[test/repo] Pull request unassigned: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, - }, - { - api.HookIssueLabelUpdated, - "[test/repo] Pull request labels updated: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, - }, - { - api.HookIssueLabelCleared, - "[test/repo] Pull request labels cleared: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, - }, - { - api.HookIssueSynchronized, - "[test/repo] Pull request synchronized: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, - }, - { - api.HookIssueMilestoned, - "[test/repo] Pull request milestoned to Milestone Title: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, - }, - { - api.HookIssueDemilestoned, "[test/repo] Pull request milestone cleared: #12 Fix bug by user1", - "#12 Fix bug", - "", - yellowColor, + "", "", + yellowColor, yellowColor, }, } for i, c := range cases { - p.Action = c.action - text, issueTitle, extraMarkdown, color := getPullRequestPayloadInfo(p, noneLinkFormatter, true) - assert.Equal(t, c.text, text, "case %d", i) - assert.Equal(t, c.issueTitle, issueTitle, "case %d", i) - assert.Equal(t, c.attachmentText, extraMarkdown, "case %d", i) - assert.Equal(t, c.color, color, "case %d", i) + issue.Action = c.action + text, issueTitle, extraMarkdown, color := getIssuesPayloadInfo(issue, noneLinkFormatter, true) + assert.Equal(t, c.issueText, text, "case %d", i) + assert.Equal(t, "#2 crash", issueTitle, "case %d", i) + assert.Equal(t, c.issueMarkdown, extraMarkdown, "case %d", i) + assert.Equal(t, c.issueColor, color, "case %d", i) + + pull.Action = c.action + text, issueTitle, extraMarkdown, color = getPullRequestPayloadInfo(pull, noneLinkFormatter, true) + assert.Equal(t, c.pullRequestText, text, "case %d", i) + assert.Equal(t, "#12 Fix bug", issueTitle, "case %d", i) + assert.Equal(t, c.pullRequestMarkdown, extraMarkdown, "case %d", i) + assert.Equal(t, c.pullRequestColor, color, "case %d", i) } } diff --git a/services/webhook/matrix_test.go b/services/webhook/matrix_test.go index 29b7affc193..4b880f7615c 100644 --- a/services/webhook/matrix_test.go +++ b/services/webhook/matrix_test.go @@ -229,30 +229,12 @@ func TestMatrixJSONPayload(t *testing.T) { } func Test_getTxnID(t *testing.T) { - type args struct { - payload []byte - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "dummy payload", - args: args{payload: []byte("Hello World")}, - want: "0a4d55a8d778e5022fab701977c5d840bbc486d0", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getMatrixTxnID(tt.args.payload) - if (err != nil) != tt.wantErr { - t.Errorf("getMatrixTxnID() error = %v, wantErr %v", err, tt.wantErr) - return - } - assert.Equal(t, tt.want, got) - }) - } + t.Run("dummy payload", func(t *testing.T) { + got, err := getMatrixTxnID([]byte("Hello World")) + if err != nil { + t.Errorf("getMatrixTxnID() error = %v, wantErr %v", err, false) + return + } + assert.Equal(t, "0a4d55a8d778e5022fab701977c5d840bbc486d0", got) + }) } diff --git a/services/webhook/msteams_test.go b/services/webhook/msteams_test.go index 94112356f7d..9f10c95777f 100644 --- a/services/webhook/msteams_test.go +++ b/services/webhook/msteams_test.go @@ -18,30 +18,42 @@ import ( func TestMSTeamsPayload(t *testing.T) { mc := msteamsConvertor{} + + checkPayload := func(t *testing.T, pl MSTeamsPayload, title, text string, facts map[string]string) { + assert.Equal(t, title, pl.Title) + assert.Equal(t, title, pl.Summary) + assert.Len(t, pl.Sections, 1) + assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) + if text == "" { + assert.Empty(t, pl.Sections[0].Text) + } else { + assert.Equal(t, text, pl.Sections[0].Text) + } + assert.Len(t, pl.Sections[0].Facts, len(facts)) + for _, fact := range pl.Sections[0].Facts { + if expected, ok := facts[fact.Name]; ok { + assert.Equal(t, expected, fact.Value) + } else { + t.Fail() + } + } + } + checkAction := func(t *testing.T, pl MSTeamsPayload, target string) { + assert.Len(t, pl.PotentialAction, 1) + assert.Len(t, pl.PotentialAction[0].Targets, 1) + assert.Equal(t, target, pl.PotentialAction[0].Targets[0].URI) + } t.Run("Create", func(t *testing.T) { p := createTestPayload() pl, err := mc.Create(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] branch test created", pl.Title) - assert.Equal(t, "[test/repo] branch test created", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value) - } else if fact.Name == "branch:" { - assert.Equal(t, "test", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] branch test created", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), + "branch:": "test", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/src/test") }) t.Run("Delete", func(t *testing.T) { @@ -50,24 +62,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Delete(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] branch test deleted", pl.Title) - assert.Equal(t, "[test/repo] branch test deleted", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value) - } else if fact.Name == "branch:" { - assert.Equal(t, "test", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] branch test deleted", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), + "branch:": "test", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/src/test") }) t.Run("Fork", func(t *testing.T) { @@ -76,24 +75,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Fork(p) require.NoError(t, err) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.Title) - assert.Equal(t, "test/repo2 is forked to test/repo", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value) - } else if fact.Name == "Forkee:" { - assert.Equal(t, p.Forkee.FullName, fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "test/repo2 is forked to test/repo", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), + "Forkee:": p.Forkee.FullName, + }) + checkAction(t, pl, "http://localhost:3000/test/repo") }) t.Run("Push", func(t *testing.T) { @@ -102,24 +88,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Push(p) require.NoError(t, err) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.Title) - assert.Equal(t, "[test/repo:test] 2 new commits", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), fact.Value) - } else if fact.Name == "Commit count:" { - assert.Equal(t, "2", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/src/test", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo:test] 2 new commits", "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1\n\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) commit message - user1", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repo.FullName, p.Repo.HTMLURL), + "Commit count:": "2", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/src/test") }) t.Run("Issue", func(t *testing.T) { @@ -129,47 +102,21 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Issue(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Title) - assert.Equal(t, "[test/repo] Issue opened: #2 crash", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "issue body", pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Issue #:" { - assert.Equal(t, "2", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Issue opened: #2 crash", "issue body", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Issue #:": "2", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/issues/2") p.Action = api.HookIssueClosed pl, err = mc.Issue(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Title) - assert.Equal(t, "[test/repo] Issue closed: #2 crash", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Issue #:" { - assert.Equal(t, "2", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Issue closed: #2 crash", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Issue #:": "2", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/issues/2") }) t.Run("IssueComment", func(t *testing.T) { @@ -178,24 +125,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.IssueComment(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Title) - assert.Equal(t, "[test/repo] New comment on issue #2 crash", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "more info needed", pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Issue #:" { - assert.Equal(t, "2", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/issues/2#issuecomment-4", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] New comment on issue #2 crash", "more info needed", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Issue #:": "2", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/issues/2#issuecomment-4") }) t.Run("PullRequest", func(t *testing.T) { @@ -204,24 +138,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.PullRequest(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Title) - assert.Equal(t, "[test/repo] Pull request opened: #12 Fix bug", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "fixes bug #2", pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Pull request:" { - assert.Equal(t, fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Pull request opened: #12 Fix bug", "fixes bug #2", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Pull request:": fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), + }) + checkAction(t, pl, "http://localhost:3000/test/repo/pulls/12") }) t.Run("PullRequestReviewRequest", func(t *testing.T) { @@ -258,24 +179,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.IssueComment(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Title) - assert.Equal(t, "[test/repo] New comment on pull request #12 Fix bug", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "changes requested", pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Issue #:" { - assert.Equal(t, "12", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12#issuecomment-4", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] New comment on pull request #12 Fix bug", "changes requested", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Issue #:": "12", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/pulls/12#issuecomment-4") }) t.Run("Review", func(t *testing.T) { @@ -285,24 +193,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Review(p, webhook_module.HookEventPullRequestReviewApproved) require.NoError(t, err) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Title) - assert.Equal(t, "[test/repo] Pull request review approved: #12 Fix bug", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "good job", pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Pull request:" { - assert.Equal(t, fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/pulls/12", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Pull request review approved: #12 Fix bug", "good job", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Pull request:": fmt.Sprintf("[#%d](%s)", p.PullRequest.Index, p.PullRequest.HTMLURL), + }) + checkAction(t, pl, "http://localhost:3000/test/repo/pulls/12") }) t.Run("Repository", func(t *testing.T) { @@ -311,22 +206,10 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Repository(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Repository created", pl.Title) - assert.Equal(t, "[test/repo] Repository created", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 1) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Repository created", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + }) + checkAction(t, pl, "http://localhost:3000/test/repo") }) t.Run("Package", func(t *testing.T) { @@ -335,22 +218,10 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Package(p) require.NoError(t, err) - assert.Equal(t, "Package created: GiteaContainer:latest", pl.Title) - assert.Equal(t, "Package created: GiteaContainer:latest", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 1) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Package:" { - assert.Equal(t, p.Package.Name, fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "Package created: GiteaContainer:latest", "", map[string]string{ + "Package:": p.Package.Name, + }) + checkAction(t, pl, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest") }) t.Run("Wiki", func(t *testing.T) { @@ -360,61 +231,27 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Wiki(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Title) - assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 1) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else { - t.Fail() - } - } + checkPayload(t, pl, "[test/repo] New wiki page 'index' (Wiki change comment)", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + }) p.Action = api.HookWikiEdited pl, err = mc.Wiki(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Title) - assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 1) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Wiki page 'index' edited (Wiki change comment)", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + }) + checkAction(t, pl, "http://localhost:3000/test/repo/wiki/index") p.Action = api.HookWikiDeleted pl, err = mc.Wiki(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Title) - assert.Equal(t, "[test/repo] Wiki page 'index' deleted", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 1) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/wiki/index", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Wiki page 'index' deleted", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + }) + checkAction(t, pl, "http://localhost:3000/test/repo/wiki/index") }) t.Run("Release", func(t *testing.T) { @@ -423,24 +260,11 @@ func TestMSTeamsPayload(t *testing.T) { pl, err := mc.Release(p) require.NoError(t, err) - assert.Equal(t, "[test/repo] Release created: v1.0", pl.Title) - assert.Equal(t, "[test/repo] Release created: v1.0", pl.Summary) - assert.Len(t, pl.Sections, 1) - assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Empty(t, pl.Sections[0].Text) - assert.Len(t, pl.Sections[0].Facts, 2) - for _, fact := range pl.Sections[0].Facts { - if fact.Name == "Repository:" { - assert.Equal(t, fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), fact.Value) - } else if fact.Name == "Tag:" { - assert.Equal(t, "v1.0", fact.Value) - } else { - t.Fail() - } - } - assert.Len(t, pl.PotentialAction, 1) - assert.Len(t, pl.PotentialAction[0].Targets, 1) - assert.Equal(t, "http://localhost:3000/test/repo/releases/tag/v1.0", pl.PotentialAction[0].Targets[0].URI) + checkPayload(t, pl, "[test/repo] Release created: v1.0", "", map[string]string{ + "Repository:": fmt.Sprintf("[%s](%s)", p.Repository.FullName, p.Repository.HTMLURL), + "Tag:": "v1.0", + }) + checkAction(t, pl, "http://localhost:3000/test/repo/releases/tag/v1.0") }) } diff --git a/services/webhook/packagist_test.go b/services/webhook/packagist_test.go index 28396d798e2..d97800bcd40 100644 --- a/services/webhook/packagist_test.go +++ b/services/webhook/packagist_test.go @@ -19,29 +19,31 @@ func TestPackagistPayload(t *testing.T) { pc := packagistConvertor{ PackageURL: "https://packagist.org/packages/example", } - t.Run("Create", func(t *testing.T) { - p := createTestPayload() - - pl, err := pc.Create(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("Delete", func(t *testing.T) { - p := deleteTestPayload() - - pl, err := pc.Delete(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("Fork", func(t *testing.T) { - p := forkTestPayload() - - pl, err := pc.Fork(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) + for _, testCase := range []struct { + name string + call func() (PackagistPayload, error) + }{ + {"Create", func() (PackagistPayload, error) { return pc.Create(createTestPayload()) }}, + {"Delete", func() (PackagistPayload, error) { return pc.Delete(deleteTestPayload()) }}, + {"Fork", func() (PackagistPayload, error) { return pc.Fork(forkTestPayload()) }}, + {"IssueComment", func() (PackagistPayload, error) { return pc.IssueComment(issueCommentTestPayload()) }}, + {"PullRequest", func() (PackagistPayload, error) { return pc.PullRequest(pullRequestTestPayload()) }}, + {"PullRequestComment", func() (PackagistPayload, error) { return pc.IssueComment(pullRequestCommentTestPayload()) }}, + {"Review", func() (PackagistPayload, error) { + p := pullRequestTestPayload() + p.Action = api.HookIssueReviewed + return pc.Review(p, webhook_module.HookEventPullRequestReviewApproved) + }}, + {"Repository", func() (PackagistPayload, error) { return pc.Repository(repositoryTestPayload()) }}, + {"Package", func() (PackagistPayload, error) { return pc.Package(packageTestPayload()) }}, + {"Release", func() (PackagistPayload, error) { return pc.Release(pullReleaseTestPayload()) }}, + } { + t.Run(testCase.name, func(t *testing.T) { + pl, err := testCase.call() + require.NoError(t, err) + require.Equal(t, PackagistPayload{}, pl) + }) + } t.Run("Push", func(t *testing.T) { p := pushTestPayload() @@ -54,161 +56,69 @@ func TestPackagistPayload(t *testing.T) { t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - - p.Action = api.HookIssueOpened - pl, err := pc.Issue(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - - p.Action = api.HookIssueClosed - pl, err = pc.Issue(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("IssueComment", func(t *testing.T) { - p := issueCommentTestPayload() - - pl, err := pc.IssueComment(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("PullRequest", func(t *testing.T) { - p := pullRequestTestPayload() - - pl, err := pc.PullRequest(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("PullRequestComment", func(t *testing.T) { - p := pullRequestCommentTestPayload() - - pl, err := pc.IssueComment(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("Review", func(t *testing.T) { - p := pullRequestTestPayload() - p.Action = api.HookIssueReviewed - - pl, err := pc.Review(p, webhook_module.HookEventPullRequestReviewApproved) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("Repository", func(t *testing.T) { - p := repositoryTestPayload() - - pl, err := pc.Repository(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("Package", func(t *testing.T) { - p := packageTestPayload() - - pl, err := pc.Package(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) + for _, action := range []api.HookIssueAction{api.HookIssueOpened, api.HookIssueClosed} { + p.Action = action + pl, err := pc.Issue(p) + require.NoError(t, err) + require.Equal(t, PackagistPayload{}, pl) + } }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - - p.Action = api.HookWikiCreated - pl, err := pc.Wiki(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - - p.Action = api.HookWikiEdited - pl, err = pc.Wiki(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - - p.Action = api.HookWikiDeleted - pl, err = pc.Wiki(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) - }) - - t.Run("Release", func(t *testing.T) { - p := pullReleaseTestPayload() - - pl, err := pc.Release(p) - require.NoError(t, err) - require.Equal(t, PackagistPayload{}, pl) + for _, action := range []api.HookWikiAction{api.HookWikiCreated, api.HookWikiEdited, api.HookWikiDeleted} { + p.Action = action + pl, err := pc.Wiki(p) + require.NoError(t, err) + require.Equal(t, PackagistPayload{}, pl) + } }) } func TestPackagistJSONPayload(t *testing.T) { - p := pushTestPayload() - data, err := p.JSONPayload() - require.NoError(t, err) + for _, testCase := range []struct { + event webhook_module.HookEventType + payload func() api.Payloader + }{ + {webhook_module.HookEventPush, func() api.Payloader { return pushTestPayload() }}, + {webhook_module.HookEventCreate, func() api.Payloader { return createTestPayload() }}, + } { + t.Run(string(testCase.event), func(t *testing.T) { + data, err := testCase.payload().JSONPayload() + require.NoError(t, err) - hook := &webhook_model.Webhook{ - RepoID: 3, - IsActive: true, - Type: webhook_module.PACKAGIST, - URL: "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", - Meta: `{"package_url":"https://packagist.org/packages/example"}`, - HTTPMethod: "POST", + hook := &webhook_model.Webhook{ + RepoID: 3, + IsActive: true, + Type: webhook_module.PACKAGIST, + URL: "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", + Meta: `{"package_url":"https://packagist.org/packages/example"}`, + HTTPMethod: "POST", + } + task := &webhook_model.HookTask{ + HookID: hook.ID, + EventType: testCase.event, + PayloadContent: string(data), + PayloadVersion: 2, + } + + req, reqBody, err := newPackagistRequest(t.Context(), hook, task) + require.NotNil(t, req) + require.NotNil(t, reqBody) + require.NoError(t, err) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", req.URL.String()) + assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) + assert.Equal(t, "application/json", req.Header.Get("Content-Type")) + var body PackagistPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(t, err) + if testCase.event == webhook_module.HookEventPush { + assert.Equal(t, "https://packagist.org/packages/example", body.PackagistRepository.URL) + } else { + assert.Empty(t, body.PackagistRepository.URL) + } + }) } - task := &webhook_model.HookTask{ - HookID: hook.ID, - EventType: webhook_module.HookEventPush, - PayloadContent: string(data), - PayloadVersion: 2, - } - - req, reqBody, err := newPackagistRequest(t.Context(), hook, task) - require.NotNil(t, req) - require.NotNil(t, reqBody) - require.NoError(t, err) - - assert.Equal(t, "POST", req.Method) - assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", req.URL.String()) - assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) - assert.Equal(t, "application/json", req.Header.Get("Content-Type")) - var body PackagistPayload - err = json.NewDecoder(req.Body).Decode(&body) - assert.NoError(t, err) - assert.Equal(t, "https://packagist.org/packages/example", body.PackagistRepository.URL) -} - -func TestPackagistEmptyPayload(t *testing.T) { - p := createTestPayload() - data, err := p.JSONPayload() - require.NoError(t, err) - - hook := &webhook_model.Webhook{ - RepoID: 3, - IsActive: true, - Type: webhook_module.PACKAGIST, - URL: "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", - Meta: `{"package_url":"https://packagist.org/packages/example"}`, - HTTPMethod: "POST", - } - task := &webhook_model.HookTask{ - HookID: hook.ID, - EventType: webhook_module.HookEventCreate, - PayloadContent: string(data), - PayloadVersion: 2, - } - - req, reqBody, err := newPackagistRequest(t.Context(), hook, task) - require.NotNil(t, req) - require.NotNil(t, reqBody) - require.NoError(t, err) - - assert.Equal(t, "POST", req.Method) - assert.Equal(t, "https://packagist.org/api/update-package?username=THEUSERNAME&apiToken=TOPSECRETAPITOKEN", req.URL.String()) - assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) - assert.Equal(t, "application/json", req.Header.Get("Content-Type")) - var body PackagistPayload - err = json.NewDecoder(req.Body).Decode(&body) - assert.NoError(t, err) - assert.Empty(t, body.PackagistRepository.URL) } diff --git a/services/webhook/slack_test.go b/services/webhook/slack_test.go index 8d672799d6a..295b057b226 100644 --- a/services/webhook/slack_test.go +++ b/services/webhook/slack_test.go @@ -18,45 +18,69 @@ import ( func TestSlackPayload(t *testing.T) { sc := slackConvertor{} - t.Run("Create", func(t *testing.T) { - p := createTestPayload() - - pl, err := sc.Create(p) - require.NoError(t, err) - - assert.Equal(t, "[:] branch created by user1", pl.Text) - }) - - t.Run("Delete", func(t *testing.T) { - p := deleteTestPayload() - - pl, err := sc.Delete(p) - require.NoError(t, err) - - assert.Equal(t, "[:test] branch deleted by user1", pl.Text) - }) - - t.Run("Fork", func(t *testing.T) { - p := forkTestPayload() - - pl, err := sc.Fork(p) - require.NoError(t, err) - - assert.Equal(t, " is forked to ", pl.Text) - }) - - t.Run("Push", func(t *testing.T) { - p := pushTestPayload() - - pl, err := sc.Push(p) - require.NoError(t, err) - - assert.Equal(t, "[:] 2 new commits pushed by user1", pl.Text) - require.Len(t, pl.Attachments, 1) - assert.Equal(t, "user1", pl.Attachments[0].AuthorName) - assert.Equal(t, "http://localhost:3000/user1/avatar", pl.Attachments[0].AuthorIcon) - assert.Equal(t, "http://localhost:3000/user1", pl.Attachments[0].AuthorLink) - }) + for _, testCase := range []struct { + name string + call func() (SlackPayload, error) + text string + attachments bool + }{ + { + "Create", func() (SlackPayload, error) { return sc.Create(createTestPayload()) }, + "[:] branch created by user1", false, + }, + { + "Delete", func() (SlackPayload, error) { return sc.Delete(deleteTestPayload()) }, + "[:test] branch deleted by user1", false, + }, + { + "Fork", func() (SlackPayload, error) { return sc.Fork(forkTestPayload()) }, + " is forked to ", false, + }, + { + "Push", func() (SlackPayload, error) { return sc.Push(pushTestPayload()) }, + "[:] 2 new commits pushed by user1", true, + }, + { + "IssueComment", func() (SlackPayload, error) { return sc.IssueComment(issueCommentTestPayload()) }, + "[] New comment on issue by ", true, + }, + { + "PullRequest", func() (SlackPayload, error) { return sc.PullRequest(pullRequestTestPayload()) }, + "[] Pull request opened: by ", true, + }, + { + "PullRequestComment", func() (SlackPayload, error) { return sc.IssueComment(pullRequestCommentTestPayload()) }, + "[] New comment on pull request by ", false, + }, + { + "Review", func() (SlackPayload, error) { + p := pullRequestTestPayload() + p.Action = api.HookIssueReviewed + return sc.Review(p, webhook_module.HookEventPullRequestReviewApproved) + }, + "[] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by ", false, + }, + { + "Package", func() (SlackPayload, error) { return sc.Package(packageTestPayload()) }, + "Package created: by ", false, + }, + { + "Release", func() (SlackPayload, error) { return sc.Release(pullReleaseTestPayload()) }, + "[] Release created: by ", false, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + pl, err := testCase.call() + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.Text) + if testCase.attachments { + require.Len(t, pl.Attachments, 1) + assert.Equal(t, "user1", pl.Attachments[0].AuthorName) + assert.Equal(t, "http://localhost:3000/user1/avatar", pl.Attachments[0].AuthorIcon) + assert.Equal(t, "http://localhost:3000/user1", pl.Attachments[0].AuthorLink) + } + }) + } t.Run("Issue", func(t *testing.T) { p := issueTestPayload() @@ -78,51 +102,6 @@ func TestSlackPayload(t *testing.T) { assert.Equal(t, "[] Issue closed: by ", pl.Text) }) - t.Run("IssueComment", func(t *testing.T) { - p := issueCommentTestPayload() - - pl, err := sc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, "[] New comment on issue by ", pl.Text) - require.Len(t, pl.Attachments, 1) - assert.Equal(t, "user1", pl.Attachments[0].AuthorName) - assert.Equal(t, "http://localhost:3000/user1/avatar", pl.Attachments[0].AuthorIcon) - assert.Equal(t, "http://localhost:3000/user1", pl.Attachments[0].AuthorLink) - }) - - t.Run("PullRequest", func(t *testing.T) { - p := pullRequestTestPayload() - - pl, err := sc.PullRequest(p) - require.NoError(t, err) - - assert.Equal(t, "[] Pull request opened: by ", pl.Text) - require.Len(t, pl.Attachments, 1) - assert.Equal(t, "user1", pl.Attachments[0].AuthorName) - assert.Equal(t, "http://localhost:3000/user1/avatar", pl.Attachments[0].AuthorIcon) - assert.Equal(t, "http://localhost:3000/user1", pl.Attachments[0].AuthorLink) - }) - - t.Run("PullRequestComment", func(t *testing.T) { - p := pullRequestCommentTestPayload() - - pl, err := sc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, "[] New comment on pull request by ", pl.Text) - }) - - t.Run("Review", func(t *testing.T) { - p := pullRequestTestPayload() - p.Action = api.HookIssueReviewed - - pl, err := sc.Review(p, webhook_module.HookEventPullRequestReviewApproved) - require.NoError(t, err) - - assert.Equal(t, "[] Pull request review approved: [#12 Fix bug](http://localhost:3000/test/repo/pulls/12) by ", pl.Text) - }) - t.Run("Repository", func(t *testing.T) { p := repositoryTestPayload() @@ -138,44 +117,21 @@ func TestSlackPayload(t *testing.T) { assert.Equal(t, "[] Repository renamed from old-repo by ", pl.Text) }) - t.Run("Package", func(t *testing.T) { - p := packageTestPayload() - - pl, err := sc.Package(p) - require.NoError(t, err) - - assert.Equal(t, "Package created: by ", pl.Text) - }) - t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - - p.Action = api.HookWikiCreated - pl, err := sc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, "[] New wiki page '' (Wiki change comment) by ", pl.Text) - - p.Action = api.HookWikiEdited - pl, err = sc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, "[] Wiki page '' edited (Wiki change comment) by ", pl.Text) - - p.Action = api.HookWikiDeleted - pl, err = sc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, "[] Wiki page '' deleted by ", pl.Text) - }) - - t.Run("Release", func(t *testing.T) { - p := pullReleaseTestPayload() - - pl, err := sc.Release(p) - require.NoError(t, err) - - assert.Equal(t, "[] Release created: by ", pl.Text) + for _, testCase := range []struct { + action api.HookWikiAction + text string + }{ + {api.HookWikiCreated, "[] New wiki page '' (Wiki change comment) by "}, + {api.HookWikiEdited, "[] Wiki page '' edited (Wiki change comment) by "}, + {api.HookWikiDeleted, "[] Wiki page '' deleted by "}, + } { + p.Action = testCase.action + pl, err := sc.Wiki(p) + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.Text) + } }) } diff --git a/services/webhook/telegram_test.go b/services/webhook/telegram_test.go index 64c3f70e2f4..d87ccc1caa8 100644 --- a/services/webhook/telegram_test.go +++ b/services/webhook/telegram_test.go @@ -33,142 +33,98 @@ func TestTelegramPayload(t *testing.T) { assert.Equal(t, "first line
second line
third line", p.RichMessage.HTML) }) - t.Run("Create", func(t *testing.T) { - p := createTestPayload() - - pl, err := tc.Create(p) - require.NoError(t, err) - - assert.Equal(t, `[
test/repo] branch test created`, pl.RichMessage.HTML) - }) - - t.Run("Delete", func(t *testing.T) { - p := deleteTestPayload() - - pl, err := tc.Delete(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] branch test deleted`, pl.RichMessage.HTML) - }) - - t.Run("Fork", func(t *testing.T) { - p := forkTestPayload() - - pl, err := tc.Fork(p) - require.NoError(t, err) - - assert.Equal(t, `test/repo2 is forked to test/repo`, pl.RichMessage.HTML) - }) - - t.Run("Push", func(t *testing.T) { - p := pushTestPayload() - - pl, err := tc.Push(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo:test] 2 new commits
[2020558] commit message - user1
[2020558] commit message - user1`, pl.RichMessage.HTML) - }) + for _, testCase := range []struct { + name string + call func() (TelegramPayload, error) + text string + }{ + { + "Create", func() (TelegramPayload, error) { return tc.Create(createTestPayload()) }, + `[test/repo] branch test created`, + }, + { + "Delete", func() (TelegramPayload, error) { return tc.Delete(deleteTestPayload()) }, + `[test/repo] branch test deleted`, + }, + { + "Fork", func() (TelegramPayload, error) { return tc.Fork(forkTestPayload()) }, + `test/repo2 is forked to test/repo`, + }, + { + "Push", func() (TelegramPayload, error) { return tc.Push(pushTestPayload()) }, + `[test/repo:test] 2 new commits
[2020558] commit message - user1
[2020558] commit message - user1`, + }, + { + "IssueComment", func() (TelegramPayload, error) { return tc.IssueComment(issueCommentTestPayload()) }, + `[test/repo] New comment on issue #2 crash by user1
more info needed`, + }, + { + "PullRequest", func() (TelegramPayload, error) { return tc.PullRequest(pullRequestTestPayload()) }, + `[test/repo] Pull request opened: #12 Fix bug by user1
fixes bug #2`, + }, + { + "PullRequestComment", func() (TelegramPayload, error) { return tc.IssueComment(pullRequestCommentTestPayload()) }, + `[test/repo] New comment on pull request #12 Fix bug by user1
changes requested`, + }, + { + "Review", func() (TelegramPayload, error) { + p := pullRequestTestPayload() + p.Action = api.HookIssueReviewed + return tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) + }, + `[test/repo] Pull request review approved: #12 Fix bug
good job`, + }, + { + "Repository", func() (TelegramPayload, error) { return tc.Repository(repositoryTestPayload()) }, + `[test/repo] Repository created`, + }, + { + "Package", func() (TelegramPayload, error) { return tc.Package(packageTestPayload()) }, + `Package created: GiteaContainer:latest by user1`, + }, + { + "Release", func() (TelegramPayload, error) { return tc.Release(pullReleaseTestPayload()) }, + `[test/repo] Release created: v1.0 by user1`, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + pl, err := testCase.call() + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.RichMessage.HTML) + }) + } t.Run("Issue", func(t *testing.T) { p := issueTestPayload() - - p.Action = api.HookIssueOpened - pl, err := tc.Issue(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Issue opened: #2 crash by user1

issue body`, pl.RichMessage.HTML) - - p.Action = api.HookIssueClosed - pl, err = tc.Issue(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Issue closed: #2 crash by user1`, pl.RichMessage.HTML) - }) - - t.Run("IssueComment", func(t *testing.T) { - p := issueCommentTestPayload() - - pl, err := tc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] New comment on issue #2 crash by user1
more info needed`, pl.RichMessage.HTML) - }) - - t.Run("PullRequest", func(t *testing.T) { - p := pullRequestTestPayload() - - pl, err := tc.PullRequest(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Pull request opened: #12 Fix bug by user1
fixes bug #2`, pl.RichMessage.HTML) - }) - - t.Run("PullRequestComment", func(t *testing.T) { - p := pullRequestCommentTestPayload() - - pl, err := tc.IssueComment(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] New comment on pull request #12 Fix bug by user1
changes requested`, pl.RichMessage.HTML) - }) - - t.Run("Review", func(t *testing.T) { - p := pullRequestTestPayload() - p.Action = api.HookIssueReviewed - - pl, err := tc.Review(p, webhook_module.HookEventPullRequestReviewApproved) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Pull request review approved: #12 Fix bug
good job`, pl.RichMessage.HTML) - }) - - t.Run("Repository", func(t *testing.T) { - p := repositoryTestPayload() - - pl, err := tc.Repository(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Repository created`, pl.RichMessage.HTML) - }) - - t.Run("Package", func(t *testing.T) { - p := packageTestPayload() - - pl, err := tc.Package(p) - require.NoError(t, err) - - assert.Equal(t, `Package created: GiteaContainer:latest by user1`, pl.RichMessage.HTML) + for _, testCase := range []struct { + action api.HookIssueAction + text string + }{ + {api.HookIssueOpened, `[test/repo] Issue opened: #2 crash by user1

issue body`}, + {api.HookIssueClosed, `[test/repo] Issue closed: #2 crash by user1`}, + } { + p.Action = testCase.action + pl, err := tc.Issue(p) + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.RichMessage.HTML) + } }) t.Run("Wiki", func(t *testing.T) { p := wikiTestPayload() - - p.Action = api.HookWikiCreated - pl, err := tc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] New wiki page 'index' (Wiki change comment) by user1`, pl.RichMessage.HTML) - - p.Action = api.HookWikiEdited - pl, err = tc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Wiki page 'index' edited (Wiki change comment) by user1`, pl.RichMessage.HTML) - - p.Action = api.HookWikiDeleted - pl, err = tc.Wiki(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Wiki page 'index' deleted by user1`, pl.RichMessage.HTML) - }) - - t.Run("Release", func(t *testing.T) { - p := pullReleaseTestPayload() - - pl, err := tc.Release(p) - require.NoError(t, err) - - assert.Equal(t, `[test/repo] Release created: v1.0 by user1`, pl.RichMessage.HTML) + for _, testCase := range []struct { + action api.HookWikiAction + text string + }{ + {api.HookWikiCreated, `[test/repo] New wiki page 'index' (Wiki change comment) by user1`}, + {api.HookWikiEdited, `[test/repo] Wiki page 'index' edited (Wiki change comment) by user1`}, + {api.HookWikiDeleted, `[test/repo] Wiki page 'index' deleted by user1`}, + } { + p.Action = testCase.action + pl, err := tc.Wiki(p) + require.NoError(t, err) + assert.Equal(t, testCase.text, pl.RichMessage.HTML) + } }) } diff --git a/tests/integration/actions_concurrency_test.go b/tests/integration/actions_concurrency_test.go index 95ae1ee38ca..658cb820aca 100644 --- a/tests/integration/actions_concurrency_test.go +++ b/tests/integration/actions_concurrency_test.go @@ -30,333 +30,108 @@ import ( ) func TestWorkflowConcurrency(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // add a variable for test - req := NewRequestWithJSON(t, "POST", - fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ - Value: "abc123", - }). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" - wf1FileContent := `name: concurrent-workflow-1 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-1.yml' -concurrency: - group: workflow-main-abc123-user2 -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow1' -` - wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" - wf2FileContent := `name: concurrent-workflow-2 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-2.yml' -concurrency: - group: workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }} -jobs: - wf2-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow2' -` - wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" - wf3FileContent := `name: concurrent-workflow-3 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-3.yml' -concurrency: - group: workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }} -jobs: - wf3-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow3' -` - // push workflow1 - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // fetch and exec workflow1 - task := runner.fetchTask(t) - _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // push workflow2 - opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) - // fetch workflow2 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) - - // push workflow3 - opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) - runner.fetchNoTask(t) - - // exec workflow2 - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // fetch and exec workflow3 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - }) -} - -func TestWorkflowConcurrencyShort(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // add a variable for test - req := NewRequestWithJSON(t, "POST", - fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ - Value: "abc123", - }). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" - wf1FileContent := `name: concurrent-workflow-1 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-1.yml' -concurrency: workflow-main-abc123-user2 -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow1' -` - wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" - wf2FileContent := `name: concurrent-workflow-2 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-2.yml' -concurrency: workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }} -jobs: - wf2-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow2' -` - wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" - wf3FileContent := `name: concurrent-workflow-3 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-3.yml' -concurrency: workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }} -jobs: - wf3-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow3' -` - // push workflow1 - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // fetch and exec workflow1 - task := runner.fetchTask(t) - _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // push workflow2 - opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) - // fetch workflow2 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) - - // push workflow3 - opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) - runner.fetchNoTask(t) - - // exec workflow2 - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // fetch and exec workflow3 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - }) -} - -func TestWorkflowConcurrencyShortJson(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // add a variable for test - req := NewRequestWithJSON(t, "POST", - fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ - Value: "abc123", - }). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" - wf1FileContent := `name: concurrent-workflow-1 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-1.yml' -concurrency: |- + for _, workflowSyntax := range []struct { + name, concurrency string + }{ + {"Long", `concurrency: + group: %s`}, + {"Short", `concurrency: %s`}, + {"ShortJson", `concurrency: |- ${{ fromjson('{ - "group": "workflow-main-abc123-user2", + "group": "%s", "cancel-in-progress": false - }') }} -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow1' -` - wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" - wf2FileContent := `name: concurrent-workflow-2 + }') }}`}, + } { + t.Run(workflowSyntax.name, func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + defer doAPIDeleteRepository(httpContext)(t) + + runner := newMockRunner() + runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + // add a variable for test + req := NewRequestWithJSON(t, "POST", + fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/myvar", user2.Name, repo.Name), &api.CreateVariableOption{ + Value: "abc123", + }). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + workflow := func(index int, group string) string { + if workflowSyntax.name == "ShortJson" { + group = "workflow-main-abc123-user2" + } + return fmt.Sprintf(`name: concurrent-workflow-%[1]d on: push: paths: - - '.gitea/workflows/concurrent-workflow-2.yml' -concurrency: |- - ${{ fromjson('{ - "group": "workflow-main-abc123-user2", - "cancel-in-progress": false - }') }} + - '.gitea/workflows/concurrent-workflow-%[1]d.yml' +%[2]s jobs: - wf2-job: + wf%[1]d-job: runs-on: ubuntu-latest steps: - - run: echo 'job from workflow2' -` - wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" - wf3FileContent := `name: concurrent-workflow-3 -on: - push: - paths: - - '.gitea/workflows/concurrent-workflow-3.yml' -concurrency: |- - ${{ fromjson('{ - "group": "workflow-main-abc123-user2", - "cancel-in-progress": false - }') }} -jobs: - wf3-job: - runs-on: ubuntu-latest - steps: - - run: echo 'job from workflow3' -` - // push workflow1 - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // fetch and exec workflow1 - task := runner.fetchTask(t) - _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, + - run: echo 'job from workflow%[1]d' +`, index, fmt.Sprintf(workflowSyntax.concurrency, group)) + } + + wf1TreePath := ".gitea/workflows/concurrent-workflow-1.yml" + wf1FileContent := workflow(1, "workflow-main-abc123-user2") + wf2TreePath := ".gitea/workflows/concurrent-workflow-2.yml" + wf2FileContent := workflow(2, "workflow-${{ gitea.ref_name }}-${{ vars.myvar }}-${{ gitea.event.pusher.username }}") + wf3TreePath := ".gitea/workflows/concurrent-workflow-3.yml" + wf3FileContent := workflow(3, "workflow-main-abc${{ 123 }}-${{ gitea.event.pusher.username }}") + // push workflow1 + opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf1TreePath, wf1FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) + // fetch and exec workflow1 + task := runner.fetchTask(t) + _, _, run := getTaskAndJobAndRunByTaskID(t, task.Id) + assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) + assert.Equal(t, "concurrent-workflow-1.yml", run.WorkflowID) + runner.fetchNoTask(t) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + + // push workflow2 + opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) + // fetch workflow2 + task = runner.fetchTask(t) + _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) + assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) + assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) + + // push workflow3 + opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) + runner.fetchNoTask(t) + + // exec workflow2 + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + + // fetch and exec workflow3 + task = runner.fetchTask(t) + _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) + assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) + assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) + runner.fetchNoTask(t) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + }) }) - - // push workflow2 - opts2 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf2TreePath, wf2FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf2TreePath, opts2) - // fetch workflow2 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-2.yml", run.WorkflowID) - - // push workflow3 - opts3 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wf3TreePath, wf3FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf3TreePath, opts3) - runner.fetchNoTask(t) - - // exec workflow2 - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // fetch and exec workflow3 - task = runner.fetchTask(t) - _, _, run = getTaskAndJobAndRunByTaskID(t, task.Id) - assert.Equal(t, "workflow-main-abc123-user2", getRunConcurrencyGroup(t, run)) - assert.Equal(t, "concurrent-workflow-3.yml", run.WorkflowID) - runner.fetchNoTask(t) - runner.execTask(t, task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - }) + } } func TestPullRequestWorkflowConcurrency(t *testing.T) { @@ -850,22 +625,24 @@ jobs: }) } -func TestWorkflowDispatchRerunAllJobsConcurrency(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) +func TestWorkflowDispatchRerunConcurrency(t *testing.T) { + for _, jobScope := range []string{"all-jobs", "single-job"} { + t.Run(jobScope, func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) + apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + defer doAPIDeleteRepository(httpContext)(t) - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + runner := newMockRunner() + runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml" - wf1FileContent := `name: workflow-dispatch-concurrency + wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml" + wf1FileContent := `name: workflow-dispatch-concurrency on: workflow_dispatch: inputs: @@ -893,236 +670,110 @@ jobs: - run: echo 'workflow dispatch job' ` - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) + opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) + createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - // run the workflow with appVersion=v1.21 and cancel=false - urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml") - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.21", + // run the workflow with appVersion=v1.21 and cancel=false + urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml") + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.21", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + task1 := runner.fetchTask(t) + _, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id) + assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1)) + + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.22", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + task2 := runner.fetchTask(t) + _, job2, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id) + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2)) + + // run the workflow with appVersion=v1.22 and cancel=false again + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.22", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + runner.fetchNoTask(t) // cannot fetch task because task2 is not completed + run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID}, unittest.OrderBy("id DESC")) + assert.Equal(t, actions_model.StatusBlocked, run3.Status) + job3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RepoID: repo.ID, RunID: run3.ID}) + assert.Equal(t, actions_model.StatusBlocked, job3.Status) + + // run the workflow with appVersion=v1.22 and cancel=true + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "ref": "refs/heads/main", + "appVersion": "v1.22", + "cancel": "on", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + task4 := runner.fetchTask(t) + _, job4, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id) + assert.Equal(t, actions_model.StatusRunning, run4.Status) + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4)) + _, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id) + assert.Equal(t, actions_model.StatusCancelled, run2.Status) + + runner.execTask(t, task4, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + + rerun := func(runID int64, job **actions_model.ActionRunJob) { + if jobScope == "single-job" { + *job = getLatestAttemptJobByTemplateJobID(t, runID, (*job).ID) + req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, runID, (*job).ID)) + } else { + req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, runID)) + } + _ = session.MakeRequest(t, req, http.StatusOK) + } + + // rerun cancel true scenario + + rerun(run2.ID, &job2) + + rerun(run4.ID, &job4) + + task5 := runner.fetchTask(t) + _, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id) + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1)) + assert.Equal(t, run4.ID, run4_1.ID) + _, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id) + assert.Equal(t, actions_model.StatusCancelled, run2_1.Status) + + runner.execTask(t, task5, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_CANCELLED, + }) + + // rerun cancel false scenario + + rerun(run2.ID, &job2) + + run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) + assert.Equal(t, actions_model.StatusWaiting, run2_2.Status) + + rerun(run3.ID, &job3) + + assert.Equal(t, actions_model.StatusBlocked, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID}).Status) + runner.execTask(t, runner.fetchTask(t), &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS}) + task6 := runner.fetchTask(t) + _, _, run3_2 := getTaskAndJobAndRunByTaskID(t, task6.Id) + if jobScope == "all-jobs" { + assert.Equal(t, run3.ID, run3_2.ID) + assert.Equal(t, actions_model.StatusRunning, run3_2.Status) + } else { + run3 = run3_2 + } + assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3)) + }) }) - session.MakeRequest(t, req, http.StatusSeeOther) - task1 := runner.fetchTask(t) - _, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id) - assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1)) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task2 := runner.fetchTask(t) - _, _, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2)) - - // run the workflow with appVersion=v1.22 and cancel=false again - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - runner.fetchNoTask(t) // cannot fetch task because task2 is not completed - run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID}, unittest.OrderBy("id DESC")) - assert.Equal(t, actions_model.StatusBlocked, run3.Status) - job3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RepoID: repo.ID, RunID: run3.ID}) - assert.Equal(t, actions_model.StatusBlocked, job3.Status) - - // run the workflow with appVersion=v1.22 and cancel=true - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - "cancel": "on", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task4 := runner.fetchTask(t) - _, _, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id) - assert.Equal(t, actions_model.StatusRunning, run4.Status) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4)) - _, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2.Status) - - runner.execTask(t, task4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // rerun cancel true scenario - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run4.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - task5 := runner.fetchTask(t) - _, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1)) - assert.Equal(t, run4.ID, run4_1.ID) - _, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2_1.Status) - - runner.execTask(t, task5, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_CANCELLED, - }) - - // rerun cancel false scenario - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) - assert.Equal(t, actions_model.StatusWaiting, run2_2.Status) - - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, apiRepo.Name, run3.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, actions_model.StatusBlocked, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID}).Status) - runner.execTask(t, runner.fetchTask(t), &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS}) - task6 := runner.fetchTask(t) - _, _, run3_2 := getTaskAndJobAndRunByTaskID(t, task6.Id) - assert.Equal(t, run3.ID, run3_2.ID) - assert.Equal(t, actions_model.StatusRunning, run3_2.Status) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3)) - }) -} - -func TestWorkflowDispatchRerunSingleJobConcurrency(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiRepo := createActionsTestRepo(t, token, "actions-concurrency", false) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) - defer doAPIDeleteRepository(httpContext)(t) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - wf1TreePath := ".gitea/workflows/workflow-dispatch-concurrency.yml" - wf1FileContent := `name: workflow-dispatch-concurrency -on: - workflow_dispatch: - inputs: - appVersion: - description: 'APP version' - required: true - default: 'v1.23' - type: choice - options: - - v1.21 - - v1.22 - - v1.23 - cancel: - description: 'Cancel running workflows' - required: false - type: boolean - default: false -concurrency: - group: workflow-dispatch-${{ inputs.appVersion }} - cancel-in-progress: ${{ inputs.cancel }} -jobs: - job: - runs-on: ubuntu-latest - steps: - - run: echo 'workflow dispatch job' -` - - opts1 := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, wf1TreePath, opts1) - - // run the workflow with appVersion=v1.21 and cancel=false - urlStr := fmt.Sprintf("/%s/%s/actions/run?workflow=%s", user2.Name, repo.Name, "workflow-dispatch-concurrency.yml") - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.21", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task1 := runner.fetchTask(t) - _, _, run1 := getTaskAndJobAndRunByTaskID(t, task1.Id) - assert.Equal(t, "workflow-dispatch-v1.21", getRunConcurrencyGroup(t, run1)) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task2 := runner.fetchTask(t) - _, job2, run2 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run2)) - - // run the workflow with appVersion=v1.22 and cancel=false again - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - runner.fetchNoTask(t) // cannot fetch task because task2 is not completed - run3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: repo.ID}, unittest.OrderBy("id DESC")) - assert.Equal(t, actions_model.StatusBlocked, run3.Status) - job3 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{RepoID: repo.ID, RunID: run3.ID}) - assert.Equal(t, actions_model.StatusBlocked, job3.Status) - - // run the workflow with appVersion=v1.22 and cancel=true - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "ref": "refs/heads/main", - "appVersion": "v1.22", - "cancel": "on", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - task4 := runner.fetchTask(t) - _, job4, run4 := getTaskAndJobAndRunByTaskID(t, task4.Id) - assert.Equal(t, actions_model.StatusRunning, run4.Status) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4)) - _, _, run2 = getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2.Status) - - runner.execTask(t, task4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - - // rerun cancel true scenario - job2 = getLatestAttemptJobByTemplateJobID(t, run2.ID, job2.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run2.ID, job2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - job4 = getLatestAttemptJobByTemplateJobID(t, run4.ID, job4.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run4.ID, job4.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - task5 := runner.fetchTask(t) - _, _, run4_1 := getTaskAndJobAndRunByTaskID(t, task5.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run4_1)) - assert.Equal(t, run4.ID, run4_1.ID) - _, _, run2_1 := getTaskAndJobAndRunByTaskID(t, task2.Id) - assert.Equal(t, actions_model.StatusCancelled, run2_1.Status) - - runner.execTask(t, task5, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_CANCELLED, - }) - - // rerun cancel false scenario - - job2 = getLatestAttemptJobByTemplateJobID(t, run2.ID, job2.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run2.ID, job2.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - run2_2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) - assert.Equal(t, actions_model.StatusWaiting, run2_2.Status) - - job3 = getLatestAttemptJobByTemplateJobID(t, run3.ID, job3.ID) - req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, apiRepo.Name, run3.ID, job3.ID)) - _ = session.MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, actions_model.StatusBlocked, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run3.ID}).Status) - runner.execTask(t, runner.fetchTask(t), &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS}) - task6 := runner.fetchTask(t) - _, _, run3 = getTaskAndJobAndRunByTaskID(t, task6.Id) - assert.Equal(t, "workflow-dispatch-v1.22", getRunConcurrencyGroup(t, run3)) - }) + } } func TestScheduleConcurrency(t *testing.T) { diff --git a/tests/integration/actions_delete_run_test.go b/tests/integration/actions_delete_run_test.go index 39f0bd712b7..b577457eb6e 100644 --- a/tests/integration/actions_delete_run_test.go +++ b/tests/integration/actions_delete_run_test.go @@ -25,6 +25,26 @@ import ( func TestActionsDeleteRun(t *testing.T) { now := time.Now() + outcomes := make(map[string]*mockTaskOutcome) + for _, jobName := range []string{"job1", "job2", "job3"} { + outcomes[jobName] = &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + { + Time: timestamppb.New(now.Add(4 * time.Second)), + Content: " \U0001F433 docker create image", + }, + { + Time: timestamppb.New(now.Add(5 * time.Second)), + Content: jobName, + }, + { + Time: timestamppb.New(now.Add(6 * time.Second)), + Content: "\U0001F3C1 Job succeeded", + }, + }, + } + } testCase := struct { treePath string fileContent string @@ -51,59 +71,7 @@ jobs: steps: - run: echo job3 `, - outcomes: map[string]*mockTaskOutcome{ - "job1": { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job1", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - "job2": { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job2", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - "job3": { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job3", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - }, + outcomes: outcomes, expectedStatuses: map[string]string{ "job1": actions_model.StatusSuccess.String(), "job2": actions_model.StatusSuccess.String(), diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index b2c46d024aa..7a1da909abd 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -466,21 +466,29 @@ func getRepoRunnerID(t *testing.T, authToken, ownerName, repoName string) int64 } func TestActionsGiteaContext(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user2Session := loginUser(t, user2.Name) - user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + for _, ephemeral := range []bool{false, true} { + t.Run(strconv.FormatBool(ephemeral), func(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Session := loginUser(t, user2.Name) + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) - baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) - runner := newMockRunner() - runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + runner := newMockRunner() + runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, ephemeral) - // init the workflow - wfTreePath := ".gitea/workflows/pull.yml" - wfFileContent := `name: Pull Request + if ephemeral { + // verify CleanupEphemeralRunners does not remove this runner + err := actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + } + + // init the workflow + wfTreePath := ".gitea/workflows/pull.yml" + wfFileContent := `name: Pull Request on: pull_request jobs: wf1-job: @@ -488,213 +496,132 @@ jobs: steps: - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) - // user2 creates a pull request - doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - NewBranchName: "user2/patch-1", - Message: "create user2-patch.txt", - Author: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Committer: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Dates: api.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), - })(t) - apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) - assert.NoError(t, err) - task := runner.fetchTask(t) - gtCtx := task.Context.GetFields() - actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) - actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) - actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) - assert.NoError(t, actionRun.LoadAttributes(t.Context())) - - assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) - assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) - assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) - runEvent := map[string]any{} - assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) - assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) - assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) - assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) - assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) - assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) - assert.Equal(t, git.RefName(actionRun.Ref).ShortName(), gtCtx["ref_name"].GetStringValue()) - assert.False(t, gtCtx["ref_protected"].GetBoolValue()) - assert.Equal(t, "branch", gtCtx["ref_type"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) - assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) - assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) - assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) - assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) - assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) - assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) - token := gtCtx["token"].GetStringValue() - assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) - }) -} - -// Ephemeral -func TestActionsGiteaContextEphemeral(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user2Session := loginUser(t, user2.Name) - user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) - baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) - - runner := newMockRunner() - runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, true) - - // verify CleanupEphemeralRunners does not remove this runner - err := actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - // init the workflow - wfTreePath := ".gitea/workflows/pull.yml" - wfFileContent := `name: Pull Request -on: pull_request -jobs: - wf1-job: - runs-on: ubuntu-latest - steps: - - run: echo 'test the pull' - wf2-job: + if ephemeral { + wfFileContent += ` wf2-job: runs-on: ubuntu-latest steps: - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) - // user2 creates a pull request - doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - NewBranchName: "user2/patch-1", - Message: "create user2-patch.txt", - Author: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Committer: api.Identity{ - Name: user2.Name, - Email: user2.Email, - }, - Dates: api.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), - })(t) - apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) - assert.NoError(t, err) - task := runner.fetchTask(t) - gtCtx := task.Context.GetFields() - actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) - actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) - actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) - assert.NoError(t, actionRun.LoadAttributes(t.Context())) + } + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) + // user2 creates a pull request + doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "user2/patch-1", + Message: "create user2-patch.txt", + Author: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Committer: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), + })(t) + apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) + assert.NoError(t, err) + task := runner.fetchTask(t) + gtCtx := task.Context.GetFields() + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) + actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) + actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) + assert.NoError(t, actionRun.LoadAttributes(t.Context())) - assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) - assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) - assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) - runEvent := map[string]any{} - assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) - assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) - assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) - assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) - assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) - assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) - assert.Equal(t, git.RefName(actionRun.Ref).ShortName(), gtCtx["ref_name"].GetStringValue()) - assert.False(t, gtCtx["ref_protected"].GetBoolValue()) - assert.Equal(t, "branch", gtCtx["ref_type"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) - assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) - assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) - assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) - assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) - assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) - assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) - assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) - token := gtCtx["token"].GetStringValue() - assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) + assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) + assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) + assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) + runEvent := map[string]any{} + assert.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) + assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) + assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) + assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) + assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) + assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) + assert.Equal(t, git.RefName(actionRun.Ref).ShortName(), gtCtx["ref_name"].GetStringValue()) + assert.False(t, gtCtx["ref_protected"].GetBoolValue()) + assert.Equal(t, "branch", gtCtx["ref_type"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) + assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) + assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) + assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) + assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) + assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) + token := gtCtx["token"].GetStringValue() + assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) - // verify CleanupEphemeralRunners does not remove this runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) + if ephemeral { + // verify CleanupEphemeralRunners does not remove this runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) - resp, err := runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ - TasksVersion: 0, + resp, err := runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ + TasksVersion: 0, + }) + assert.NoError(t, err) + assert.Nil(t, resp.Task) + + // verify CleanupEphemeralRunners does not remove this runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + _, err = runner.client.runnerServiceClient.UpdateTask(t.Context(), &runnerv1.UpdateTaskRequest{ + State: &runnerv1.TaskState{ + Id: actionTask.ID, + Result: runnerv1.Result_RESULT_SUCCESS, + }, + }) + assert.NoError(t, err) + + resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ + TasksVersion: 0, + }) + assert.Error(t, err) + assert.Nil(t, resp) + + resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ + TasksVersion: 0, + }) + assert.Error(t, err) + assert.Nil(t, resp) + + // create a runner that picks a job and get force cancelled + runnerToBeRemoved := newMockRunner() + runnerToBeRemoved.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner-to-be-removed", []string{"ubuntu-latest"}, true) + + taskToStopAPIObj := runnerToBeRemoved.fetchTask(t) + + taskToStop := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskToStopAPIObj.Id}) + + // verify CleanupEphemeralRunners does not remove the custom crafted runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + runnerToRemove := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: taskToStop.RunnerID}) + + err = actions_model.StopTask(t.Context(), taskToStop.ID, actions_model.StatusFailure) + assert.NoError(t, err) + + // verify CleanupEphemeralRunners does remove the custom crafted runner + err = actions_service.CleanupEphemeralRunners(t.Context()) + assert.NoError(t, err) + + unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: runnerToRemove.ID}) + } + }) }) - assert.NoError(t, err) - assert.Nil(t, resp.Task) - - // verify CleanupEphemeralRunners does not remove this runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - _, err = runner.client.runnerServiceClient.UpdateTask(t.Context(), &runnerv1.UpdateTaskRequest{ - State: &runnerv1.TaskState{ - Id: actionTask.ID, - Result: runnerv1.Result_RESULT_SUCCESS, - }, - }) - assert.NoError(t, err) - - resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ - TasksVersion: 0, - }) - assert.Error(t, err) - assert.Nil(t, resp) - - resp, err = runner.client.runnerServiceClient.FetchTask(t.Context(), &runnerv1.FetchTaskRequest{ - TasksVersion: 0, - }) - assert.Error(t, err) - assert.Nil(t, resp) - - // create a runner that picks a job and get force cancelled - runnerToBeRemoved := newMockRunner() - runnerToBeRemoved.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner-to-be-removed", []string{"ubuntu-latest"}, true) - - taskToStopAPIObj := runnerToBeRemoved.fetchTask(t) - - taskToStop := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: taskToStopAPIObj.Id}) - - // verify CleanupEphemeralRunners does not remove the custom crafted runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - runnerToRemove := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: taskToStop.RunnerID}) - - err = actions_model.StopTask(t.Context(), taskToStop.ID, actions_model.StatusFailure) - assert.NoError(t, err) - - // verify CleanupEphemeralRunners does remove the custom crafted runner - err = actions_service.CleanupEphemeralRunners(t.Context()) - assert.NoError(t, err) - - unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: runnerToRemove.ID}) - }) + } } func createActionsTestRepo(t *testing.T, authToken, repoName string, isPrivate bool) *api.Repository { diff --git a/tests/integration/actions_job_token_test.go b/tests/integration/actions_job_token_test.go index e85201e8299..12cc48a95b5 100644 --- a/tests/integration/actions_job_token_test.go +++ b/tests/integration/actions_job_token_test.go @@ -183,103 +183,210 @@ func TestActionsJobTokenPermissiveAccess(t *testing.T) { }) }) } - }) -} -func TestActionsCrossRepoAccess(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) + t.Run("CrossRepoAccess", func(t *testing.T) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) - // 1. Create Organization - orgName := "org-cross-test" - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &structs.CreateOrgOption{ - UserName: orgName, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - owner, err := org_model.GetOrgByName(t.Context(), orgName) - require.NoError(t, err) - - // 2. Create Two Repositories in owner - createRepoInOrg := func(name string) int64 { - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", orgName), &structs.CreateRepoOption{ - Name: name, - AutoInit: true, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - repo := DecodeJSON(t, resp, &structs.Repository{}) - return repo.ID - } - - repoAID := createRepoInOrg("repo-A") - repoBID := createRepoInOrg("repo-B") - - // 3. Enable Actions in Repo A (Source) and Repo B (Target) - enableActions := func(repoID int64) { - err := db.Insert(t.Context(), &repo_model.RepoUnit{ - RepoID: repoID, - Type: unit_model.TypeActions, - Config: &repo_model.ActionsConfig{ - TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, - }, - }) - require.NoError(t, err) - } - - enableActions(repoAID) - enableActions(repoBID) - - // 4. Create Task in Repo A, and use A's token to access B - taskA := createActionTask(t, repoAID, false) - testCtxA := APITestContext{ - Session: emptyTestSession(t), - Token: taskA.Token, - Username: orgName, - Reponame: "repo-B", - } - - testCtxA.ExpectedCode = http.StatusOK - t.Run("PublicCrossRepoAccess", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { - assert.Equal(t, "repo-B", r.Name) - })) - - // make repo-B be private - req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/org-cross-test/repo-B", &structs.EditRepoOption{Private: new(true)}).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - testCtxA.ExpectedCode = http.StatusNotFound - t.Run("NoPrivateCrossRepoAccess", doAPIGetRepository(testCtxA, nil)) - - ownerActionsCfg := actions_model.OwnerActionsConfig{AllowedCrossRepoIDs: []int64{repoBID}} - require.NoError(t, actions_model.SetOwnerActionsConfig(t.Context(), owner.ID, ownerActionsCfg)) - - testCtxA.ExpectedCode = http.StatusOK - t.Run("AccessToSelectedPrivateRepo", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { - assert.Equal(t, "repo-B", r.Name) - })) - - t.Run("RepoTransfer", func(t *testing.T) { - ownerActionsCfg, err := actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) - require.NoError(t, err) - assert.Contains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) - - // Transfer Repository to user4 - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer", orgName), &structs.TransferRepoOption{ - NewOwner: "user4", + // 1. Create Organization + orgName := "org-cross-test" + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &structs.CreateOrgOption{ + UserName: orgName, }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - // Accept transfer as user4 - session4 := loginUser(t, "user4") - token4 := getTokenForLoggedInUser(t, session4, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer/accept", orgName)).AddTokenAuth(token4) - MakeRequest(t, req, http.StatusAccepted) - - // Verify it is removed from the org's config - ownerActionsCfg, err = actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) + owner, err := org_model.GetOrgByName(t.Context(), orgName) require.NoError(t, err) - assert.NotContains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) + + // 2. Create Two Repositories in owner + createRepoInOrg := func(name string) int64 { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", orgName), &structs.CreateRepoOption{ + Name: name, + AutoInit: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + repo := DecodeJSON(t, resp, &structs.Repository{}) + return repo.ID + } + + repoAID := createRepoInOrg("repo-A") + repoBID := createRepoInOrg("repo-B") + + // 3. Enable Actions in Repo A (Source) and Repo B (Target) + enableActions := func(repoID int64) { + err := db.Insert(t.Context(), &repo_model.RepoUnit{ + RepoID: repoID, + Type: unit_model.TypeActions, + Config: &repo_model.ActionsConfig{ + TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, + }, + }) + require.NoError(t, err) + } + + enableActions(repoAID) + enableActions(repoBID) + + // 4. Create Task in Repo A, and use A's token to access B + taskA := createActionTask(t, repoAID, false) + testCtxA := APITestContext{ + Session: emptyTestSession(t), + Token: taskA.Token, + Username: orgName, + Reponame: "repo-B", + } + + testCtxA.ExpectedCode = http.StatusOK + t.Run("PublicCrossRepoAccess", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { + assert.Equal(t, "repo-B", r.Name) + })) + + // make repo-B be private + req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/org-cross-test/repo-B", &structs.EditRepoOption{Private: new(true)}).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + testCtxA.ExpectedCode = http.StatusNotFound + t.Run("NoPrivateCrossRepoAccess", doAPIGetRepository(testCtxA, nil)) + + ownerActionsCfg := actions_model.OwnerActionsConfig{AllowedCrossRepoIDs: []int64{repoBID}} + require.NoError(t, actions_model.SetOwnerActionsConfig(t.Context(), owner.ID, ownerActionsCfg)) + + testCtxA.ExpectedCode = http.StatusOK + t.Run("AccessToSelectedPrivateRepo", doAPIGetRepository(testCtxA, func(t *testing.T, r structs.Repository) { + assert.Equal(t, "repo-B", r.Name) + })) + + t.Run("RepoTransfer", func(t *testing.T) { + ownerActionsCfg, err := actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) + require.NoError(t, err) + assert.Contains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) + + // Transfer Repository to user4 + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer", orgName), &structs.TransferRepoOption{ + NewOwner: "user4", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + // Accept transfer as user4 + session4 := loginUser(t, "user4") + token4 := getTokenForLoggedInUser(t, session4, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/repo-B/transfer/accept", orgName)).AddTokenAuth(token4) + MakeRequest(t, req, http.StatusAccepted) + + // Verify it is removed from the org's config + ownerActionsCfg, err = actions_model.GetOwnerActionsConfig(t.Context(), owner.ID) + require.NoError(t, err) + assert.NotContains(t, ownerActionsCfg.AllowedCrossRepoIDs, repoBID) + }) + }) + + t.Run("TokenPermissionsPersistenceWithWorkflow", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + // create repos + repo1 := createActionsTestRepo(t, token, "actions-permission-repo1", false) + repo2 := createActionsTestRepo(t, token, "actions-permission-repo2", true) + + // add repo2 to owner-level cross-repo access list + req := NewRequestWithValues(t, "POST", "/user/settings/actions/general", map[string]string{ + "cross_repo_add_target": "true", + "cross_repo_add_target_name": repo2.Name, + }) + session.MakeRequest(t, req, http.StatusOK) + + // create the runner for repo1 + runner1 := newMockRunner() + runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + // set repo1 actions token permission mode to "permissive" + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ + "token_permission_mode": "permissive", + "override_owner_config": "true", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // set repo2 actions token permission mode to "restricted", and set max permissions + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // create a workflow file with "permission" keyword for repo1 + wfTreePath := ".gitea/workflows/test_permissions.yml" + wfFileContent := `name: Test Permissions +on: + push: + paths: + - '.gitea/workflows/test_permissions.yml' + +jobs: + job-override: + runs-on: ubuntu-latest + permissions: + code: write + steps: + - run: echo "test perms" +` + opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, token, user2.Name, repo1.Name, wfTreePath, opts) + + task1 := runner1.fetchTask(t) + task1Token := task1.Secrets["GITEA_TOKEN"] + require.NotEmpty(t, task1Token) + + // should fail: target repo does not allow code access + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) + MakeRequest(t, req, http.StatusNotFound) + + // set repo2 max permission to "read" so that the actions token can access code + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "read", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // should succeed: target repo now allows code read access for this token + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) + MakeRequest(t, req, http.StatusOK) + // but it should not have write access + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). + SetHeader("Accept", lfs.MediaType). + AddBasicAuth("gitea-actions", task1Token) + MakeRequest(t, req, http.StatusUnauthorized) + + // set repo1&repo2 max permission to "write" so that the actions token can access code + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ + "token_permission_mode": "restricted", + "override_owner_config": "true", + "enable_max_permissions": "true", + "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // now task1 has write access to repo1, but still only read access to repo2 (different repo) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo1.Name), lfs.BatchRequest{Operation: "upload"}). + SetHeader("Accept", lfs.MediaType). + AddBasicAuth("gitea-actions", task1Token) + MakeRequest(t, req, http.StatusOK) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). + SetHeader("Accept", lfs.MediaType). + AddBasicAuth("gitea-actions", task1Token) + MakeRequest(t, req, http.StatusUnauthorized) }) }) } @@ -362,114 +469,3 @@ func createActionTask(t *testing.T, repoID int64, isFork bool) *actions_model.Ac require.NoError(t, db.Insert(t.Context(), task)) return task } - -func TestActionsTokenPermissionsPersistenceWithWorkflow(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - // create repos - repo1 := createActionsTestRepo(t, token, "actions-permission-repo1", false) - repo2 := createActionsTestRepo(t, token, "actions-permission-repo2", true) - - // add repo2 to owner-level cross-repo access list - req := NewRequestWithValues(t, "POST", "/user/settings/actions/general", map[string]string{ - "cross_repo_add_target": "true", - "cross_repo_add_target_name": repo2.Name, - }) - session.MakeRequest(t, req, http.StatusOK) - - // create the runner for repo1 - runner1 := newMockRunner() - runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false) - - // set repo1 actions token permission mode to "permissive" - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ - "token_permission_mode": "permissive", - "override_owner_config": "true", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // set repo2 actions token permission mode to "restricted", and set max permissions - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // create a workflow file with "permission" keyword for repo1 - wfTreePath := ".gitea/workflows/test_permissions.yml" - wfFileContent := `name: Test Permissions -on: - push: - paths: - - '.gitea/workflows/test_permissions.yml' - -jobs: - job-override: - runs-on: ubuntu-latest - permissions: - code: write - steps: - - run: echo "test perms" -` - opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) - createWorkflowFile(t, token, user2.Name, repo1.Name, wfTreePath, opts) - - task1 := runner1.fetchTask(t) - task1Token := task1.Secrets["GITEA_TOKEN"] - require.NotEmpty(t, task1Token) - - // should fail: target repo does not allow code access - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) - MakeRequest(t, req, http.StatusNotFound) - - // set repo2 max permission to "read" so that the actions token can access code - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "read", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeReleases)): "read", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // should succeed: target repo now allows code read access for this token - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo2.Name)).AddTokenAuth(task1Token) - MakeRequest(t, req, http.StatusOK) - // but it should not have write access - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). - SetHeader("Accept", lfs.MediaType). - AddBasicAuth("gitea-actions", task1Token) - MakeRequest(t, req, http.StatusUnauthorized) - - // set repo1&repo2 max permission to "write" so that the actions token can access code - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo1.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", user2.Name, repo2.Name), map[string]string{ - "token_permission_mode": "restricted", - "override_owner_config": "true", - "enable_max_permissions": "true", - "max_unit_access_mode_" + strconv.Itoa(int(unit_model.TypeCode)): "write", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // now task1 has write access to repo1, but still only read access to repo2 (different repo) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo1.Name), lfs.BatchRequest{Operation: "upload"}). - SetHeader("Accept", lfs.MediaType). - AddBasicAuth("gitea-actions", task1Token) - MakeRequest(t, req, http.StatusOK) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/%s.git/info/lfs/objects/batch", user2.Name, repo2.Name), lfs.BatchRequest{Operation: "upload"}). - SetHeader("Accept", lfs.MediaType). - AddBasicAuth("gitea-actions", task1Token) - MakeRequest(t, req, http.StatusUnauthorized) - }) -} diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go index 51f705e96f0..e3110acb988 100644 --- a/tests/integration/actions_log_test.go +++ b/tests/integration/actions_log_test.go @@ -28,121 +28,13 @@ import ( func TestDownloadTaskLogs(t *testing.T) { now := time.Now() testCases := []struct { - treePath string - fileContent string - outcome []*mockTaskOutcome + name string + logState string + logStart time.Duration zstdEnabled bool }{ - { - treePath: ".gitea/workflows/download-task-logs-zstd.yml", - fileContent: `name: download-task-logs-zstd -on: - push: - paths: - - '.gitea/workflows/download-task-logs-zstd.yml' -jobs: - job1: - runs-on: ubuntu-latest - steps: - - run: echo job1 with zstd enabled - job2: - runs-on: ubuntu-latest - steps: - - run: echo job2 with zstd enabled -`, - outcome: []*mockTaskOutcome{ - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(1 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(2 * time.Second)), - Content: "job1 zstd enabled", - }, - { - Time: timestamppb.New(now.Add(3 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(1 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(2 * time.Second)), - Content: "job2 zstd enabled", - }, - { - Time: timestamppb.New(now.Add(3 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - }, - zstdEnabled: true, - }, - { - treePath: ".gitea/workflows/download-task-logs-no-zstd.yml", - fileContent: `name: download-task-logs-no-zstd -on: - push: - paths: - - '.gitea/workflows/download-task-logs-no-zstd.yml' -jobs: - job1: - runs-on: ubuntu-latest - steps: - - run: echo job1 with zstd disabled - job2: - runs-on: ubuntu-latest - steps: - - run: echo job2 with zstd disabled -`, - outcome: []*mockTaskOutcome{ - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job1 zstd disabled", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - { - result: runnerv1.Result_RESULT_SUCCESS, - logRows: []*runnerv1.LogRow{ - { - Time: timestamppb.New(now.Add(4 * time.Second)), - Content: " \U0001F433 docker create image", - }, - { - Time: timestamppb.New(now.Add(5 * time.Second)), - Content: "job2 zstd disabled", - }, - { - Time: timestamppb.New(now.Add(6 * time.Second)), - Content: "\U0001F3C1 Job succeeded", - }, - }, - }, - }, - zstdEnabled: false, - }, + {"download-task-logs-zstd", "enabled", time.Second, true}, + {"download-task-logs-no-zstd", "disabled", 4 * time.Second, false}, } onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -155,7 +47,8 @@ jobs: runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run("test "+tc.treePath, func(t *testing.T) { + treePath := ".gitea/workflows/" + tc.name + ".yml" + t.Run("test "+treePath, func(t *testing.T) { var resetFunc func() if tc.zstdEnabled { resetFunc = test.MockVariableValue(&setting.Actions.LogCompression, "zstd") @@ -166,11 +59,33 @@ jobs: } // create the workflow file - opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+tc.treePath, tc.fileContent) - createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts) + opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+treePath, fmt.Sprintf(`name: %s +on: + push: + paths: + - '%s' +jobs: + job1: + runs-on: ubuntu-latest + steps: + - run: echo job1 with zstd %s + job2: + runs-on: ubuntu-latest + steps: + - run: echo job2 with zstd %s +`, tc.name, treePath, tc.logState, tc.logState)) + createWorkflowFile(t, token, user2.Name, repo.Name, treePath, opts) // fetch and execute tasks - for _, outcome := range tc.outcome { + for jobIndex := range 2 { + outcome := &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + logRows: []*runnerv1.LogRow{ + {Time: timestamppb.New(now.Add(tc.logStart)), Content: " \U0001F433 docker create image"}, + {Time: timestamppb.New(now.Add(tc.logStart + time.Second)), Content: fmt.Sprintf("job%d zstd %s", jobIndex+1, tc.logState)}, + {Time: timestamppb.New(now.Add(tc.logStart + 2*time.Second)), Content: "\U0001F3C1 Job succeeded"}, + }, + } task := runner.fetchTask(t) runner.execTask(t, task, outcome) @@ -185,31 +100,22 @@ jobs: _, job, run := getTaskAndJobAndRunByTaskID(t, task.Id) // download task logs and check content - req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, repo.Name, run.ID, job.ID)). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") - assert.Len(t, logTextLines, len(outcome.logRows)) - for idx, lr := range outcome.logRows { - assert.Equal( - t, - fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), - logTextLines[idx], - ) - } - - // download task logs from API and check content - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, job.ID)). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n") - assert.Len(t, logTextLines, len(outcome.logRows)) - for idx, lr := range outcome.logRows { - assert.Equal( - t, - fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), - logTextLines[idx], - ) + for _, logURL := range []string{ + fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/logs", user2.Name, repo.Name, run.ID, job.ID), + // download task logs from API and check content + fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, job.ID), + } { + req := NewRequest(t, "GET", logURL).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n") + assert.Len(t, logTextLines, len(outcome.logRows)) + for idx, lr := range outcome.logRows { + assert.Equal( + t, + fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content), + logTextLines[idx], + ) + } } } resetFunc() diff --git a/tests/integration/actions_rerun_test.go b/tests/integration/actions_rerun_test.go index 660a4f2f29c..467e7d46efd 100644 --- a/tests/integration/actions_rerun_test.go +++ b/tests/integration/actions_rerun_test.go @@ -111,18 +111,13 @@ jobs: req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo.Name, run.ID, job1.ID)) session.MakeRequest(t, req, http.StatusOK) // job2 needs job1, so rerunning job1 will also rerun job2 - // fetch and exec job1 - job1TaskR2 := runner.fetchTask(t) - assert.Equal(t, "3", job1TaskR2.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job1TaskR2, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - // fetch and exec job2 - job2TaskR2 := runner.fetchTask(t) - assert.Equal(t, "3", job2TaskR2.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job2TaskR2, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + for range 2 { + task := runner.fetchTask(t) + assert.Equal(t, "3", task.Context.GetFields()["run_attempt"].GetStringValue()) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + } assert.EqualValues(t, 3, getRunLatestAttemptNum(t, run.ID)) // RERUN-3: rerun job2 @@ -201,17 +196,13 @@ jobs: req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", user2.Name, repo.Name, run.ID)) session.MakeRequest(t, req, http.StatusOK) - // fetch and exec job1 - job1TaskR4 := runner.fetchTask(t) - assert.Equal(t, "5", job1TaskR4.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job1TaskR4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) - job2TaskR4 := runner.fetchTask(t) - assert.Equal(t, "5", job2TaskR4.Context.GetFields()["run_attempt"].GetStringValue()) - runner.execTask(t, job2TaskR4, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + for range 2 { + task := runner.fetchTask(t) + assert.Equal(t, "5", task.Context.GetFields()["run_attempt"].GetStringValue()) + runner.execTask(t, task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + } assert.EqualValues(t, 5, getRunLatestAttemptNum(t, run.ID)) }) }) @@ -282,68 +273,46 @@ jobs: legacyRun.Created = started - 5 legacyRun.Updated = stopped - legacyJob1 := &actions_model.ActionRunJob{ - RunID: legacyRun.ID, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - Name: payloads["job1"].name, - Attempt: 1, - WorkflowPayload: payloads["job1"].payload, - JobID: "job1", - Needs: payloads["job1"].needs, - RunsOn: payloads["job1"].runsOn, - Status: actions_model.StatusSuccess, - RunAttemptID: 0, - AttemptJobID: 0, - Started: started, - Stopped: stopped, - IsForkPullRequest: false, - } - legacyJob2 := &actions_model.ActionRunJob{ - RunID: legacyRun.ID, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - Name: payloads["job2"].name, - Attempt: 1, - WorkflowPayload: payloads["job2"].payload, - JobID: "job2", - Needs: payloads["job2"].needs, - RunsOn: payloads["job2"].runsOn, - Status: actions_model.StatusSuccess, - RunAttemptID: 0, - AttemptJobID: 0, - Started: started, - Stopped: stopped, - IsForkPullRequest: false, + legacyJobs := [2]*actions_model.ActionRunJob{} + for index, jobID := range []string{"job1", "job2"} { + legacyJobs[index] = &actions_model.ActionRunJob{ + RunID: legacyRun.ID, + RepoID: repo.ID, + OwnerID: repo.OwnerID, + CommitSHA: legacyRun.CommitSHA, + Name: payloads[jobID].name, + Attempt: 1, + WorkflowPayload: payloads[jobID].payload, + JobID: jobID, + Needs: payloads[jobID].needs, + RunsOn: payloads[jobID].runsOn, + Status: actions_model.StatusSuccess, + RunAttemptID: 0, + AttemptJobID: 0, + Started: started, + Stopped: stopped, + IsForkPullRequest: false, + } } + legacyJob1, legacyJob2 := legacyJobs[0], legacyJobs[1] require.NoError(t, db.Insert(t.Context(), legacyJob1, legacyJob2)) - legacyTask1 := &actions_model.ActionTask{ - JobID: legacyJob1.ID, - Attempt: 1, - Status: actions_model.StatusSuccess, - Started: started, - Stopped: stopped, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - IsForkPullRequest: false, + legacyTasks := [2]*actions_model.ActionTask{} + for index, legacyJob := range legacyJobs { + legacyTasks[index] = &actions_model.ActionTask{ + JobID: legacyJob.ID, + Attempt: 1, + Status: actions_model.StatusSuccess, + Started: started, + Stopped: stopped, + RepoID: repo.ID, + OwnerID: repo.OwnerID, + CommitSHA: legacyRun.CommitSHA, + IsForkPullRequest: false, + } + legacyTasks[index].GenerateAndFillToken() } - legacyTask1.GenerateAndFillToken() - legacyTask2 := &actions_model.ActionTask{ - JobID: legacyJob2.ID, - Attempt: 1, - Status: actions_model.StatusSuccess, - Started: started, - Stopped: stopped, - RepoID: repo.ID, - OwnerID: repo.OwnerID, - CommitSHA: legacyRun.CommitSHA, - IsForkPullRequest: false, - } - legacyTask2.GenerateAndFillToken() + legacyTask1, legacyTask2 := legacyTasks[0], legacyTasks[1] require.NoError(t, db.Insert(t.Context(), legacyTask1, legacyTask2)) legacyJob1.TaskID = legacyTask1.ID diff --git a/tests/integration/actions_reusable_workflow_test.go b/tests/integration/actions_reusable_workflow_test.go index db13d3b3c3f..0f4b8382e6d 100644 --- a/tests/integration/actions_reusable_workflow_test.go +++ b/tests/integration/actions_reusable_workflow_test.go @@ -168,6 +168,22 @@ jobs: r2Job1AttemptJobID int64 ) + completeCallerJob3 := func(t *testing.T, expectedOutput string) { + callerJob3Task := defaultRunner.fetchTask(t) // for caller_job3 + _, callerJob3, _ := getTaskAndJobAndRunByTaskID(t, callerJob3Task.Id) + assert.Equal(t, "caller_job3", callerJob3.JobID) + if assert.Len(t, callerJob3Task.Needs, 1) { + assert.Contains(t, callerJob3Task.Needs, "caller_job2") + assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, callerJob3Task.Needs["caller_job2"].Result) + if assert.Len(t, callerJob3Task.Needs["caller_job2"].Outputs, 1) { + assert.Equal(t, expectedOutput, callerJob3Task.Needs["caller_job2"].Outputs["r1_out"]) + } + } + defaultRunner.execTask(t, callerJob3Task, &mockTaskOutcome{ + result: runnerv1.Result_RESULT_SUCCESS, + }) + } + t.Run("Check initialized jobs", func(t *testing.T) { // run assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID})) @@ -279,19 +295,7 @@ jobs: callerJob2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: callerJob2ID}) assert.Equal(t, actions_model.StatusSuccess, callerJob2.Status) - callerJob3Task := defaultRunner.fetchTask(t) // for caller_job3 - _, callerJob3, _ := getTaskAndJobAndRunByTaskID(t, callerJob3Task.Id) - assert.Equal(t, "caller_job3", callerJob3.JobID) - if assert.Len(t, callerJob3Task.Needs, 1) { - assert.Contains(t, callerJob3Task.Needs, "caller_job2") - assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, callerJob3Task.Needs["caller_job2"].Result) - if assert.Len(t, callerJob3Task.Needs["caller_job2"].Outputs, 1) { - assert.Equal(t, "r1j2_out_data", callerJob3Task.Needs["caller_job2"].Outputs["r1_out"]) - } - } - defaultRunner.execTask(t, callerJob3Task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + completeCallerJob3(t, "r1j2_out_data") callerRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: runID}) assert.Equal(t, actions_model.StatusSuccess, callerRun.Status) }) @@ -349,19 +353,7 @@ jobs: callerJob2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: callerJob2.ID}) assert.Equal(t, actions_model.StatusSuccess, callerJob2.Status) - callerJob3Task := defaultRunner.fetchTask(t) - _, callerJob3, _ = getTaskAndJobAndRunByTaskID(t, callerJob3Task.Id) - assert.Equal(t, "caller_job3", callerJob3.JobID) - if assert.Len(t, callerJob3Task.Needs, 1) { - assert.Contains(t, callerJob3Task.Needs, "caller_job2") - assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, callerJob3Task.Needs["caller_job2"].Result) - if assert.Len(t, callerJob3Task.Needs["caller_job2"].Outputs, 1) { - assert.Equal(t, "r1j2_out_data_updated", callerJob3Task.Needs["caller_job2"].Outputs["r1_out"]) - } - } - defaultRunner.execTask(t, callerJob3Task, &mockTaskOutcome{ - result: runnerv1.Result_RESULT_SUCCESS, - }) + completeCallerJob3(t, "r1j2_out_data_updated") attempt2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunAttempt{RunID: runID, Attempt: 2}) assert.Equal(t, actions_model.StatusSuccess, attempt2.Status) run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: runID}) diff --git a/tests/integration/actions_runner_modify_test.go b/tests/integration/actions_runner_modify_test.go index e3392ee57c4..8e4ae3004b4 100644 --- a/tests/integration/actions_runner_modify_test.go +++ b/tests/integration/actions_runner_modify_test.go @@ -57,26 +57,16 @@ func TestActionsRunnerModify(t *testing.T) { sess.MakeRequest(t, req, expectedStatus) } - doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { - req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id)) - sess.MakeRequest(t, req, expectedStatus) - } - - doDisable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { - req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=true", baseURL, id)) - sess.MakeRequest(t, req, expectedStatus) - } - - doEnable := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) { - req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/update-runner?disabled=false", baseURL, id)) + doAction := func(t *testing.T, sess *TestSession, baseURL string, id int64, action string, expectedStatus int) { + req := NewRequest(t, "POST", fmt.Sprintf("%s/%d/%s", baseURL, id, action)) sess.MakeRequest(t, req, expectedStatus) } assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) { doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound) - doDisable(t, sess, baseURL, id, http.StatusNotFound) - doEnable(t, sess, baseURL, id, http.StatusNotFound) - doDelete(t, sess, baseURL, id, http.StatusNotFound) + doAction(t, sess, baseURL, id, "update-runner?disabled=true", http.StatusNotFound) + doAction(t, sess, baseURL, id, "update-runner?disabled=false", http.StatusNotFound) + doAction(t, sess, baseURL, id, "delete", http.StatusNotFound) v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.Empty(t, v.Description) assert.False(t, v.IsDisabled) @@ -86,13 +76,13 @@ func TestActionsRunnerModify(t *testing.T) { doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther) v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.Equal(t, "ChangedDescription", v.Description) - doDisable(t, sess, baseURL, id, http.StatusOK) + doAction(t, sess, baseURL, id, "update-runner?disabled=true", http.StatusOK) v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.True(t, v.IsDisabled) - doEnable(t, sess, baseURL, id, http.StatusOK) + doAction(t, sess, baseURL, id, "update-runner?disabled=false", http.StatusOK) v = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id}) assert.False(t, v.IsDisabled) - doDelete(t, sess, baseURL, id, http.StatusOK) + doAction(t, sess, baseURL, id, "delete", http.StatusOK) unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id}) } diff --git a/tests/integration/actions_scoped_workflow_test.go b/tests/integration/actions_scoped_workflow_test.go index 51b273a2f95..aa34e728e44 100644 --- a/tests/integration/actions_scoped_workflow_test.go +++ b/tests/integration/actions_scoped_workflow_test.go @@ -239,6 +239,48 @@ jobs: "both the web form and the API created a scoped dispatch run") }) + // protectAndOpenPR protects consumer's default branch and opens a PR on `branch`, returning a merge-request builder. + // When statusCheckEnabled it also configures "ci/manual" as the only CONFIGURED required context and satisfies it, + // so the scoped check is the only thing that can gate the merge; otherwise the rule's own status check stays off. + protectAndOpenPR := func(t *testing.T, consumer *repo_model.Repository, branch, filename string, statusCheckEnabled bool) (api.PullRequest, func() *RequestWrapper) { + pbValues := map[string]string{ + "rule_name": consumer.DefaultBranch, + "enable_push": "true", + "block_admin_merge_override": "true", // otherwise the repo owner bypasses the status check + } + if statusCheckEnabled { + pbValues["enable_status_check"] = "true" + pbValues["status_check_contexts"] = "ci/manual" + } + user2Session.MakeRequest(t, NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", consumer.OwnerName, consumer.Name), pbValues), http.StatusSeeOther) + + prFile := &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: consumer.DefaultBranch, NewBranchName: branch, Message: "pr change", + Author: api.Identity{Name: user2.Name, Email: user2.Email}, + Committer: api.Identity{Name: user2.Name, Email: user2.Email}, + Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()}, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("pr change")), + } + createWorkflowFile(t, user2Token, consumer.OwnerName, consumer.Name, filename, prFile) + apiCtx := NewAPITestContext(t, user2.Name, consumer.Name, auth_model.AccessTokenScopeWriteRepository) + pr, err := doAPICreatePullRequest(apiCtx, consumer.OwnerName, consumer.Name, consumer.DefaultBranch, branch)(t) + require.NoError(t, err) + + if statusCheckEnabled { + // satisfy the configured "ci/manual" check so only the scoped check can gate the merge + manualStatus := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", consumer.OwnerName, consumer.Name, pr.Head.Sha), + api.CreateStatusOption{State: commitstatus.CommitStatusSuccess, Context: "ci/manual", TargetURL: "http://test.ci/"}).AddTokenAuth(user2Token) + user2Session.MakeRequest(t, manualStatus, http.StatusCreated) + } + + return pr, func() *RequestWrapper { + return NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", consumer.OwnerName, consumer.Name, pr.Index), + &forms.MergePullRequestForm{Do: string(repo_model.MergeStyleMerge), MergeMessageField: "merge"}).AddTokenAuth(user2Token) + } + } + t.Run("Required scoped check gates the PR merge", func(t *testing.T) { // A required scoped workflow's check gates PR merges on a protected branch and cannot be bypassed, // whether or not the branch enables its own status check. The scoped check is added to the required set dynamically. @@ -246,54 +288,12 @@ jobs: createRepoWorkflowFile(t, user2, user2Token, source, ".gitea/scoped_workflows/pr.yaml", scopedPRWorkflow) registerUserScopedSource(t, source, "pr.yaml") // required - // protectAndOpenPR protects consumer's default branch and opens a PR on `branch`, returning a merge-request builder. - // When statusCheckEnabled it also configures "ci/manual" as the only CONFIGURED required context and satisfies it, - // so the scoped check is the only thing that can gate the merge; otherwise the rule's own status check stays off. - protectAndOpenPR := func(t *testing.T, consumer *repo_model.Repository, branch string, statusCheckEnabled bool) func() *RequestWrapper { - pbValues := map[string]string{ - "rule_name": consumer.DefaultBranch, - "enable_push": "true", - "block_admin_merge_override": "true", // otherwise the repo owner bypasses the status check - } - if statusCheckEnabled { - pbValues["enable_status_check"] = "true" - pbValues["status_check_contexts"] = "ci/manual" - } - user2Session.MakeRequest(t, NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", consumer.OwnerName, consumer.Name), pbValues), http.StatusSeeOther) - - prFile := &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - BranchName: consumer.DefaultBranch, NewBranchName: branch, Message: "pr change", - Author: api.Identity{Name: user2.Name, Email: user2.Email}, - Committer: api.Identity{Name: user2.Name, Email: user2.Email}, - Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()}, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("pr change")), - } - createWorkflowFile(t, user2Token, consumer.OwnerName, consumer.Name, "pr-change.txt", prFile) - apiCtx := NewAPITestContext(t, user2.Name, consumer.Name, auth_model.AccessTokenScopeWriteRepository) - pr, err := doAPICreatePullRequest(apiCtx, consumer.OwnerName, consumer.Name, consumer.DefaultBranch, branch)(t) - require.NoError(t, err) - - if statusCheckEnabled { - // satisfy the configured "ci/manual" check so only the scoped check can gate the merge - manualStatus := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", consumer.OwnerName, consumer.Name, pr.Head.Sha), - api.CreateStatusOption{State: commitstatus.CommitStatusSuccess, Context: "ci/manual", TargetURL: "http://test.ci/"}).AddTokenAuth(user2Token) - user2Session.MakeRequest(t, manualStatus, http.StatusCreated) - } - - return func() *RequestWrapper { - return NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", consumer.OwnerName, consumer.Name, pr.Index), - &forms.MergePullRequestForm{Do: string(repo_model.MergeStyleMerge), MergeMessageField: "merge"}).AddTokenAuth(user2Token) - } - } - t.Run("pending blocks, success allows", func(t *testing.T) { consumer := createTestRepo(t, "sw-gate-consumer", false) runner := newMockRunner() runner.registerAsRepoRunner(t, consumer.OwnerName, consumer.Name, "sw-gate-runner", []string{"ubuntu-latest"}, false) - mergeReq := protectAndOpenPR(t, consumer, "gate-pr", true) + _, mergeReq := protectAndOpenPR(t, consumer, "gate-pr", "pr-change.txt", true) run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}) assert.Equal(t, source.ID, run.WorkflowRepoID) @@ -314,7 +314,7 @@ jobs: consumer := createTestRepo(t, "sw-noact-consumer", false) require.NoError(t, repo_service.UpdateRepositoryUnits(t.Context(), consumer, nil, []unit_model.Type{unit_model.TypeActions})) - mergeReq := protectAndOpenPR(t, consumer, "noact-pr", true) + _, mergeReq := protectAndOpenPR(t, consumer, "noact-pr", "pr-change.txt", true) assert.Equal(t, 0, unittest.GetCount(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}), "Actions disabled, so no scoped run is created") @@ -329,7 +329,7 @@ jobs: runner := newMockRunner() runner.registerAsRepoRunner(t, consumer.OwnerName, consumer.Name, "sw-nocheck-runner", []string{"ubuntu-latest"}, false) - mergeReq := protectAndOpenPR(t, consumer, "nocheck-pr", false) + _, mergeReq := protectAndOpenPR(t, consumer, "nocheck-pr", "pr-change.txt", false) unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}) // pending scoped check blocks the merge despite the branch's own status check being off @@ -365,26 +365,8 @@ jobs: consumer := createTestRepo(t, "sw-filtered-consumer", false) // Protect the default branch (its own status check stays off, so only the required scoped check gates the merge). - user2Session.MakeRequest(t, NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/branches/edit", consumer.OwnerName, consumer.Name), map[string]string{ - "rule_name": consumer.DefaultBranch, - "enable_push": "true", - "block_admin_merge_override": "true", // otherwise the repo owner bypasses the status check - }), http.StatusSeeOther) - // Open a PR that changes a file NOT matching the workflow's `paths: [src/**]`, so it is filtered out. - prFile := &api.CreateFileOptions{ - FileOptions: api.FileOptions{ - BranchName: consumer.DefaultBranch, NewBranchName: "filtered-pr", Message: "pr change", - Author: api.Identity{Name: user2.Name, Email: user2.Email}, - Committer: api.Identity{Name: user2.Name, Email: user2.Email}, - Dates: api.CommitDateOptions{Author: time.Now(), Committer: time.Now()}, - }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte("pr change")), - } - createWorkflowFile(t, user2Token, consumer.OwnerName, consumer.Name, "docs.txt", prFile) - apiCtx := NewAPITestContext(t, user2.Name, consumer.Name, auth_model.AccessTokenScopeWriteRepository) - pr, err := doAPICreatePullRequest(apiCtx, consumer.OwnerName, consumer.Name, consumer.DefaultBranch, "filtered-pr")(t) - require.NoError(t, err) + pr, mergeReq := protectAndOpenPR(t, consumer, "filtered-pr", "docs.txt", false) // Filtered: no scoped run is created, but a skipped commit status is posted on the PR head. assert.Equal(t, 0, unittest.GetCount(t, &actions_model.ActionRun{RepoID: consumer.ID, IsScopedRun: true}), "filtered scoped workflow creates no run") @@ -392,9 +374,7 @@ jobs: // The skipped (success) status satisfies the required scoped check (prefixed with the source repo), so the merge is allowed. assert.NoError(t, queue.GetManager().FlushAll(t.Context(), 5*time.Second)) - mergeReq := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", consumer.OwnerName, consumer.Name, pr.Index), - &forms.MergePullRequestForm{Do: string(repo_model.MergeStyleMerge), MergeMessageField: "merge"}).AddTokenAuth(user2Token) - user2Session.MakeRequest(t, mergeReq, http.StatusOK) + user2Session.MakeRequest(t, mergeReq(), http.StatusOK) }) t.Run("Settings page required patterns", func(t *testing.T) { diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 178163cd508..e39bead7c57 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -423,31 +423,32 @@ jobs: }) } -func TestCreateDeleteRefEvent(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) +func TestActionsTriggerEvents(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _u *url.URL) { + t.Run("CreateDeleteRefEvent", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "create-delete-ref-event", - Description: "test create delete ref ci event", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "create-delete-ref-event", + Description: "test create delete ref ci event", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/createdelete.yml", - ContentReader: strings.NewReader(`name: test + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/createdelete.yml", + ContentReader: strings.NewReader(`name: test on: [create,delete] jobs: @@ -456,87 +457,572 @@ jobs: steps: - run: echo helloworld `), + }, }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) - // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-create-branch") - assert.NoError(t, err) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "create", - Ref: "refs/heads/test-create-branch", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) + // create a branch + err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-create-branch") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-create-branch", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) - // create a tag - err = release_service.CreateNewTag(t.Context(), user2, repo, branch.CommitID, "test-create-tag", "test create tag event") - assert.NoError(t, err) - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "create", - Ref: "refs/tags/test-create-tag", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) + // create a tag + err = release_service.CreateNewTag(t.Context(), user2, repo, branch.CommitID, "test-create-tag", "test create tag event") + assert.NoError(t, err) + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/tags/test-create-tag", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) - // delete the branch - err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch") - assert.NoError(t, err) - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "delete", - Ref: "refs/heads/main", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) + // delete the branch + err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch") + assert.NoError(t, err) + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "delete", + Ref: "refs/heads/main", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) - // delete the tag - tag, err := repo_model.GetRelease(t.Context(), repo.ID, "test-create-tag") - assert.NoError(t, err) - err = release_service.DeleteReleaseByID(t.Context(), repo, tag, user2, true) - assert.NoError(t, err) - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "delete", - Ref: "refs/heads/main", - WorkflowID: "createdelete.yml", - CommitSHA: branch.CommitID, + // delete the tag + tag, err := repo_model.GetRelease(t.Context(), repo.ID, "test-create-tag") + assert.NoError(t, err) + err = release_service.DeleteReleaseByID(t.Context(), repo, tag, user2, true) + assert.NoError(t, err) + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "delete", + Ref: "refs/heads/main", + WorkflowID: "createdelete.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) + + t.Run("WorkflowDispatchPublicApi", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-event", + Description: "test workflow-dispatch ci event", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + values := url.Values{} + values.Set("ref", "main") + req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + + // Now trigger with rundetails + values.Set("return_run_details", "true") + + req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runDetails := &api.RunDetails{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(runDetails)) + assert.NotEqual(t, 0, runDetails.WorkflowRunID) + + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + ID: runDetails.WorkflowRunID, + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + + _, _, err = gitcmd.NewCommand("update-ref").AddDynamicArguments("refs/tags/refs/heads/spoof", branch.CommitID).WithRepo(repo).RunStdString(t.Context()) + require.NoError(t, err) + values.Set("ref", "spoof") + req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("WorkflowDispatchPublicApiRequiresWorkflowDispatchTrigger", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-dispatch-requires-trigger", + Description: "test workflow dispatch requires workflow_dispatch", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + require.NoError(t, err) + require.NotNil(t, repo) + + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/push-only.yml", + ContentReader: strings.NewReader(` +on: + push: +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + require.NoError(t, err) + require.NotNil(t, addWorkflowToBaseResp) + + values := url.Values{} + values.Set("ref", "main") + req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/push-only.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + apiError := DecodeJSON(t, resp, &api.APIError{}) + assert.Contains(t, apiError.Message, "has no workflow_dispatch event trigger") + + unittest.AssertNotExistsBean(t, &actions_model.ActionRun{ + RepoID: repo.ID, + Event: "workflow_dispatch", + WorkflowID: "push-only.yml", + }) + }) + + t.Run("WorkflowApi", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "workflow-api", + Description: "test workflow apis", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + workflows := &api.ActionWorkflowResponse{} + json.NewDecoder(resp.Body).Decode(workflows) + assert.Empty(t, workflows.Workflows) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/dispatch.yml", + ContentReader: strings.NewReader(` +on: + workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + json.NewDecoder(resp.Body).Decode(workflows) + assert.Len(t, workflows.Workflows, 1) + assert.Equal(t, "dispatch.yml", workflows.Workflows[0].Name) + assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) + assert.Equal(t, "active", workflows.Workflows[0].State) + + checkWorkflow := func(workflowURL, state string) { + req := NewRequest(t, "GET", workflowURL). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + workflow := &api.ActionWorkflow{} + json.NewDecoder(resp.Body).Decode(workflow) + assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) + assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) + assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) + assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) + assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) + assert.Equal(t, state, workflow.State) + } + + // Use a hardcoded api path + checkWorkflow(fmt.Sprintf("/api/v1/repos/%s/actions/workflows/%s", repo.FullName(), workflows.Workflows[0].ID), workflows.Workflows[0].State) + + // Use the provided url instead of the hardcoded one + checkWorkflow(workflows.Workflows[0].URL, workflows.Workflows[0].State) + + // Disable the workflow + req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/disable"). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + // Use the provided url instead of the hardcoded one + checkWorkflow(workflows.Workflows[0].URL, "disabled_manually") + + inputs := &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + // Since the workflow is disabled, so the response code is 403 forbidden + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusForbidden) + + // Enable the workflow again + req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/enable"). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + // Use the provided url instead of the hardcoded one + checkWorkflow(workflows.Workflows[0].URL, workflows.Workflows[0].State) + + checkWorkflow(workflows.Workflows[0].URL, workflows.Workflows[0].State) + + // Get the commit ID of the default branch + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + inputs = &api.CreateActionWorkflowDispatch{ + Ref: "main", + Inputs: map[string]string{ + "myinput": "val0", + "myinput3": "true", + }, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusNoContent) + + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + dispatchPayload := &api.WorkflowDispatchPayload{} + err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) + assert.NoError(t, err) + assert.Contains(t, dispatchPayload.Inputs, "myinput") + assert.Contains(t, dispatchPayload.Inputs, "myinput2") + assert.Contains(t, dispatchPayload.Inputs, "myinput3") + assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) + assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) + assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) + }) + + t.Run("ActionRunNameWithContextVariables", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "action-run-name-with-variables", + Description: "test action run name", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/runname.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +run-name: ${{ gitea.actor }} is running this workflow +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow with run-name", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + // Get the commit ID of the default branch + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + + // create a branch + err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: user2.LoginName + " is running this workflow", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-action-run-name-with-variables", + WorkflowID: "runname.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) + }) + + t.Run("ActionRunName", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + // create the repo + repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "action-run-name", + Description: "test action run-name", + AutoInit: true, + Gitignores: "Go", + License: "MIT", + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + assert.NoError(t, err) + assert.NotEmpty(t, repo) + + // add workflow file to the repo + addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: ".gitea/workflows/runname.yml", + ContentReader: strings.NewReader(`name: test +on: + [create,delete] +run-name: run name without variables +jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo helloworld +`), + }, + }, + Message: "add workflow with run name", + OldBranch: "main", + NewBranch: "main", + Author: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Committer: &files_service.IdentityOptions{ + GitUserName: user2.Name, + GitUserEmail: user2.Email, + }, + Dates: &files_service.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }) + assert.NoError(t, err) + assert.NotEmpty(t, addWorkflowToBaseResp) + + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + // Get the commit ID of the default branch + branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) + assert.NoError(t, err) + + // create a branch + err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name") + assert.NoError(t, err) + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + Title: "run name without variables", + RepoID: repo.ID, + Event: "create", + Ref: "refs/heads/test-action-run-name", + WorkflowID: "runname.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) }) - assert.NotNil(t, run) }) } @@ -895,184 +1381,6 @@ jobs: }) } -func TestWorkflowDispatchPublicApi(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-dispatch-event", - Description: "test workflow-dispatch ci event", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(` -on: - workflow_dispatch -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - values := url.Values{} - values.Set("ref", "main") - req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - - // Now trigger with rundetails - values.Set("return_run_details", "true") - - req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - runDetails := &api.RunDetails{} - require.NoError(t, json.NewDecoder(resp.Body).Decode(runDetails)) - assert.NotEqual(t, 0, runDetails.WorkflowRunID) - - run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - ID: runDetails.WorkflowRunID, - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - - _, _, err = gitcmd.NewCommand("update-ref").AddDynamicArguments("refs/tags/refs/heads/spoof", branch.CommitID).WithRepo(repo).RunStdString(t.Context()) - require.NoError(t, err) - values.Set("ref", "spoof") - req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - }) -} - -func TestWorkflowDispatchPublicApiRequiresWorkflowDispatchTrigger(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-dispatch-requires-trigger", - Description: "test workflow dispatch requires workflow_dispatch", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - require.NoError(t, err) - require.NotNil(t, repo) - - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/push-only.yml", - ContentReader: strings.NewReader(` -on: - push: -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - require.NoError(t, err) - require.NotNil(t, addWorkflowToBaseResp) - - values := url.Values{} - values.Set("ref", "main") - req := NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/push-only.yml/dispatches", repo.FullName()), values). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusUnprocessableEntity) - apiError := DecodeJSON(t, resp, &api.APIError{}) - assert.Contains(t, apiError.Message, "has no workflow_dispatch event trigger") - - unittest.AssertNotExistsBean(t, &actions_model.ActionRun{ - RepoID: repo.ID, - Event: "workflow_dispatch", - WorkflowID: "push-only.yml", - }) - }) -} - func TestWorkflowDispatchPublicApiWithInputs(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -1164,92 +1472,6 @@ jobs: }) } -func TestWorkflowDispatchPublicApiJSON(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-dispatch-event", - Description: "test workflow-dispatch ci event", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(` -on: - workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - inputs := &api.CreateActionWorkflowDispatch{ - Ref: "main", - Inputs: map[string]string{ - "myinput": "val0", - "myinput3": "true", - }, - } - - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - }) -} - func TestWorkflowDispatchPublicApiWithInputsJSON(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -1475,203 +1697,6 @@ jobs: }) } -func TestWorkflowApi(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "workflow-api", - Description: "test workflow apis", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - workflows := &api.ActionWorkflowResponse{} - json.NewDecoder(resp.Body).Decode(workflows) - assert.Empty(t, workflows.Workflows) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/dispatch.yml", - ContentReader: strings.NewReader(` -on: - workflow_dispatch: { inputs: { myinput: { default: def }, myinput2: { default: def2 }, myinput3: { type: boolean, default: false } } } -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows", repo.FullName())). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - json.NewDecoder(resp.Body).Decode(workflows) - assert.Len(t, workflows.Workflows, 1) - assert.Equal(t, "dispatch.yml", workflows.Workflows[0].Name) - assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) - assert.Equal(t, ".gitea/workflows/dispatch.yml", workflows.Workflows[0].Path) - assert.Equal(t, "active", workflows.Workflows[0].State) - - // Use a hardcoded api path - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/%s", repo.FullName(), workflows.Workflows[0].ID)). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow := &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - // Use the provided url instead of the hardcoded one - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - // Disable the workflow - req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/disable"). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - // Use the provided url instead of the hardcoded one - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, "disabled_manually", workflow.State) - - inputs := &api.CreateActionWorkflowDispatch{ - Ref: "main", - Inputs: map[string]string{ - "myinput": "val0", - "myinput3": "true", - }, - } - // Since the workflow is disabled, so the response code is 403 forbidden - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusForbidden) - - // Enable the workflow again - req = NewRequest(t, "PUT", workflows.Workflows[0].URL+"/enable"). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - // Use the provided url instead of the hardcoded one - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - req = NewRequest(t, "GET", workflows.Workflows[0].URL). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - workflow = &api.ActionWorkflow{} - json.NewDecoder(resp.Body).Decode(workflow) - assert.Equal(t, workflows.Workflows[0].ID, workflow.ID) - assert.Equal(t, workflows.Workflows[0].Path, workflow.Path) - assert.Equal(t, workflows.Workflows[0].URL, workflow.URL) - assert.Equal(t, workflows.Workflows[0].HTMLURL, workflow.HTMLURL) - assert.Equal(t, workflows.Workflows[0].Name, workflow.Name) - assert.Equal(t, workflows.Workflows[0].State, workflow.State) - - // Get the commit ID of the default branch - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - inputs = &api.CreateActionWorkflowDispatch{ - Ref: "main", - Inputs: map[string]string{ - "myinput": "val0", - "myinput3": "true", - }, - } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), inputs). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusNoContent) - - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "add workflow", - RepoID: repo.ID, - Event: "workflow_dispatch", - Ref: "refs/heads/main", - WorkflowID: "dispatch.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - dispatchPayload := &api.WorkflowDispatchPayload{} - err = json.Unmarshal([]byte(run.EventPayload), dispatchPayload) - assert.NoError(t, err) - assert.Contains(t, dispatchPayload.Inputs, "myinput") - assert.Contains(t, dispatchPayload.Inputs, "myinput2") - assert.Contains(t, dispatchPayload.Inputs, "myinput3") - assert.Equal(t, "val0", dispatchPayload.Inputs["myinput"]) - assert.Equal(t, "def2", dispatchPayload.Inputs["myinput2"]) - assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) - }) -} - func TestClosePullRequestWithPath(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { // user2 is the owner of the base repo @@ -1749,162 +1774,6 @@ jobs: }) } -func TestActionRunNameWithContextVariables(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "action-run-name-with-variables", - Description: "test action run name", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/runname.yml", - ContentReader: strings.NewReader(`name: test -on: - [create,delete] -run-name: ${{ gitea.actor }} is running this workflow -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow with run-name", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - - // Get the commit ID of the default branch - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - - // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name-with-variables") - assert.NoError(t, err) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: user2.LoginName + " is running this workflow", - RepoID: repo.ID, - Event: "create", - Ref: "refs/heads/test-action-run-name-with-variables", - WorkflowID: "runname.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - }) -} - -func TestActionRunName(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // create the repo - repo, err := repo_service.CreateRepository(t.Context(), user2, user2, repo_service.CreateRepoOptions{ - Name: "action-run-name", - Description: "test action run-name", - AutoInit: true, - Gitignores: "Go", - License: "MIT", - Readme: "Default", - DefaultBranch: "main", - IsPrivate: false, - }) - assert.NoError(t, err) - assert.NotEmpty(t, repo) - - // add workflow file to the repo - addWorkflowToBaseResp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - Files: []*files_service.ChangeRepoFile{ - { - Operation: "create", - TreePath: ".gitea/workflows/runname.yml", - ContentReader: strings.NewReader(`name: test -on: - [create,delete] -run-name: run name without variables -jobs: - test: - runs-on: ubuntu-latest - steps: - - run: echo helloworld -`), - }, - }, - Message: "add workflow with run name", - OldBranch: "main", - NewBranch: "main", - Author: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Committer: &files_service.IdentityOptions{ - GitUserName: user2.Name, - GitUserEmail: user2.Email, - }, - Dates: &files_service.CommitDateOptions{ - Author: time.Now(), - Committer: time.Now(), - }, - }) - assert.NoError(t, err) - assert.NotEmpty(t, addWorkflowToBaseResp) - - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - - // Get the commit ID of the default branch - branch, err := git_model.GetBranchExisting(t.Context(), repo.ID, repo.DefaultBranch) - assert.NoError(t, err) - - // create a branch - err = repo_service.CreateNewBranchFromCommit(t.Context(), user2, repo, gitRepo, branch.CommitID, "test-action-run-name") - assert.NoError(t, err) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ - Title: "run name without variables", - RepoID: repo.ID, - Event: "create", - Ref: "refs/heads/test-action-run-name", - WorkflowID: "runname.yml", - CommitSHA: branch.CommitID, - }) - assert.NotNil(t, run) - }) -} - func TestPullRequestWithPathsRebase(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/actions_variables_test.go b/tests/integration/actions_variables_test.go index 25dd7321c1e..0d25512c459 100644 --- a/tests/integration/actions_variables_test.go +++ b/tests/integration/actions_variables_test.go @@ -76,57 +76,30 @@ func TestActionsVariables(t *testing.T) { unittest.AssertNotExistsBean(t, &actions_model.ActionVariable{ID: id}) } - t.Run("UpdateUserVar", func(t *testing.T) { - theVar := user2Var - t.Run("FromOrg", func(t *testing.T) { - assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) + originURLs := map[string]string{ + "User": userWebURL, + "Org": orgWebURL, + "Repo": repoWebURL, + "Admin": adminWebURL, + } + for _, variable := range []struct { + name string + id int64 + origins []string + }{ + {"User", user2Var.ID, []string{"Org", "Repo", "Admin"}}, + {"Org", org3Var.ID, []string{"Repo", "User", "Admin"}}, + {"Repo", repo1Var.ID, []string{"Org", "User", "Admin"}}, + {"Global", globalVar.ID, []string{"Org", "User", "Repo"}}, + } { + t.Run("Update"+variable.name+"Var", func(t *testing.T) { + for _, origin := range variable.origins { + t.Run("From"+origin, func(t *testing.T) { + assertDenied(t, sessionAdmin, originURLs[origin], variable.id) + }) + } }) - t.Run("FromRepo", func(t *testing.T) { - assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) - }) - t.Run("FromAdmin", func(t *testing.T) { - assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) - }) - }) - - t.Run("UpdateOrgVar", func(t *testing.T) { - theVar := org3Var - t.Run("FromRepo", func(t *testing.T) { - assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) - }) - t.Run("FromUser", func(t *testing.T) { - assertDenied(t, sessionAdmin, userWebURL, theVar.ID) - }) - t.Run("FromAdmin", func(t *testing.T) { - assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) - }) - }) - - t.Run("UpdateRepoVar", func(t *testing.T) { - theVar := repo1Var - t.Run("FromOrg", func(t *testing.T) { - assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) - }) - t.Run("FromUser", func(t *testing.T) { - assertDenied(t, sessionAdmin, userWebURL, theVar.ID) - }) - t.Run("FromAdmin", func(t *testing.T) { - assertDenied(t, sessionAdmin, adminWebURL, theVar.ID) - }) - }) - - t.Run("UpdateGlobalVar", func(t *testing.T) { - theVar := globalVar - t.Run("FromOrg", func(t *testing.T) { - assertDenied(t, sessionAdmin, orgWebURL, theVar.ID) - }) - t.Run("FromUser", func(t *testing.T) { - assertDenied(t, sessionAdmin, userWebURL, theVar.ID) - }) - t.Run("FromRepo", func(t *testing.T) { - assertDenied(t, sessionAdmin, repoWebURL, theVar.ID) - }) - }) + } t.Run("UpdateSuccess", func(t *testing.T) { t.Run("User", func(t *testing.T) { diff --git a/tests/integration/admin_audit_test.go b/tests/integration/admin_audit_test.go index b091456090b..a84f52b2c3e 100644 --- a/tests/integration/admin_audit_test.go +++ b/tests/integration/admin_audit_test.go @@ -23,71 +23,70 @@ import ( "github.com/stretchr/testify/require" ) -// Events an admin causes while impersonating must stay traceable to the admin, -// otherwise anything done in an impersonated session is pinned on the victim. -func TestAdminAuditLogImpersonation(t *testing.T) { +func TestAdminAuditLogAttribution(t *testing.T) { defer tests.PrepareTestEnv(t)() defer test.MockVariableValue(&setting.Audit.RecordOutput, setting.AuditRecordOutputDatabase)() - session := loginUser(t, "user1") - session.MakeRequest(t, NewRequest(t, "POST", "/-/admin/users/2/impersonate"), http.StatusOK) + // A leaked token is only traceable if events name the token, not just its owner. + t.Run("TokenCredential", func(t *testing.T) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{ - "name": "impersonated-token", - "scope-dummy": "read:user", - }), http.StatusOK) + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ + Name: "audit-credential-repo", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) - session.MakeRequest(t, NewRequest(t, "GET", "/user/logout"), http.StatusSeeOther) - - events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ActorID: 1}) - require.NoError(t, err) - - byAction := make(map[audit_model.Action]*audit_model.Event, len(events)) - for _, e := range events { - byAction[e.Action] = e - } - - start := byAction[audit_model.UserImpersonation] - require.NotNil(t, start) - assert.Equal(t, int64(1), start.ActorID) - assert.Equal(t, int64(2), start.ScopeID) - - token := byAction[audit_model.UserAccessTokenAdd] - require.NotNil(t, token) - assert.Equal(t, int64(2), token.ActorID) // the token really belongs to user2 - assert.Equal(t, int64(1), token.ImpersonatorID) - assert.Equal(t, "user1", token.ImpersonatorName) - assert.Equal(t, audit_model.OriginUI, token.Origin) - assert.NotEmpty(t, token.IPAddress) - - exit := byAction[audit_model.UserImpersonationExit] - require.NotNil(t, exit) - assert.Equal(t, int64(1), exit.ActorID) - assert.Zero(t, exit.ImpersonatorID) -} - -// A leaked token is only traceable if events name the token, not just its owner. -func TestAdminAuditLogTokenCredential(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.Audit.RecordOutput, setting.AuditRecordOutputDatabase)() - - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ - Name: "audit-credential-repo", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ - Action: audit_model.RepositoryCreate, - ActorID: 2, + events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ + Action: audit_model.RepositoryCreate, + ActorID: 2, + }) + require.NoError(t, err) + require.Len(t, events, 1) + assert.True(t, strings.HasPrefix(events[0].ActorCredential, "access-token:"), "unexpected credential %q", events[0].ActorCredential) + assert.Equal(t, audit_model.OriginAPI, events[0].Origin) + assert.NotEmpty(t, events[0].IPAddress) + }) + + // Events an admin causes while impersonating must stay traceable to the admin, + // otherwise anything done in an impersonated session is pinned on the victim. + t.Run("Impersonation", func(t *testing.T) { + session := loginUser(t, "user1") + session.MakeRequest(t, NewRequest(t, "POST", "/-/admin/users/2/impersonate"), http.StatusOK) + + session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/settings/applications", map[string]string{ + "name": "impersonated-token", + "scope-dummy": "read:user", + }), http.StatusOK) + + session.MakeRequest(t, NewRequest(t, "GET", "/user/logout"), http.StatusSeeOther) + + events, _, err := audit_model.FindEvents(t.Context(), &audit_model.EventSearchOptions{ActorID: 1}) + require.NoError(t, err) + + byAction := make(map[audit_model.Action]*audit_model.Event, len(events)) + for _, e := range events { + byAction[e.Action] = e + } + + start := byAction[audit_model.UserImpersonation] + require.NotNil(t, start) + assert.Equal(t, int64(1), start.ActorID) + assert.Equal(t, int64(2), start.ScopeID) + + token := byAction[audit_model.UserAccessTokenAdd] + require.NotNil(t, token) + assert.Equal(t, int64(2), token.ActorID) // the token really belongs to user2 + assert.Equal(t, int64(1), token.ImpersonatorID) + assert.Equal(t, "user1", token.ImpersonatorName) + assert.Equal(t, audit_model.OriginUI, token.Origin) + assert.NotEmpty(t, token.IPAddress) + + exit := byAction[audit_model.UserImpersonationExit] + require.NotNil(t, exit) + assert.Equal(t, int64(1), exit.ActorID) + assert.Zero(t, exit.ImpersonatorID) }) - require.NoError(t, err) - require.Len(t, events, 1) - assert.True(t, strings.HasPrefix(events[0].ActorCredential, "access-token:"), "unexpected credential %q", events[0].ActorCredential) - assert.Equal(t, audit_model.OriginAPI, events[0].Origin) - assert.NotEmpty(t, events[0].IPAddress) } func TestAdminAuditLogExport(t *testing.T) { diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go index c70f0094009..efad9fe160e 100644 --- a/tests/integration/admin_user_test.go +++ b/tests/integration/admin_user_test.go @@ -27,68 +27,66 @@ import ( func TestAdminViewUsers(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user1") - req := NewRequest(t, "GET", "/-/admin/users") - session.MakeRequest(t, req, http.StatusOK) + t.Run("List", func(t *testing.T) { + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/-/admin/users") + session.MakeRequest(t, req, http.StatusOK) - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/-/admin/users") - session.MakeRequest(t, req, http.StatusForbidden) -} + session = loginUser(t, "user2") + req = NewRequest(t, "GET", "/-/admin/users") + session.MakeRequest(t, req, http.StatusForbidden) + }) -func TestAdminViewUsersFilterAuthSource(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ViewUser", func(t *testing.T) { + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/-/admin/users/1") + session.MakeRequest(t, req, http.StatusOK) - source := &auth_model.Source{Type: auth_model.LDAP, Name: "test-user-list-filter", IsActive: false, Cfg: &ldap.Source{}} // users stay attached to a deactivated source - require.NoError(t, auth_model.CreateSource(t.Context(), source)) + session = loginUser(t, "user2") + req = NewRequest(t, "GET", "/-/admin/users/1") + session.MakeRequest(t, req, http.StatusForbidden) + }) - user2 := &user_model.User{ID: 2, LoginType: auth_model.LDAP, LoginSource: source.ID} - require.NoError(t, user_model.UpdateUserCols(t.Context(), user2, "login_type", "login_source")) + t.Run("FilterAuthSource", func(t *testing.T) { + source := &auth_model.Source{Type: auth_model.LDAP, Name: "test-user-list-filter", IsActive: false, Cfg: &ldap.Source{}} // users stay attached to a deactivated source + require.NoError(t, auth_model.CreateSource(t.Context(), source)) - session := loginUser(t, "user1") - listUsers := func(query string) (*HTMLDoc, []string) { - req := NewRequest(t, "GET", "/-/admin/users?"+query) - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - return doc, doc.Find("table tbody tr td:nth-child(2) a").Map(func(_ int, s *goquery.Selection) string { - return s.Text() - }) - } + user2 := &user_model.User{ID: 2, LoginType: auth_model.LDAP, LoginSource: source.ID} + require.NoError(t, user_model.UpdateUserCols(t.Context(), user2, "login_type", "login_source")) - doc, users := listUsers("source_id=") // the "All" option submits an empty value - AssertHTMLElement(t, doc, `input[name="source_id"][value=""][checked]`, true) - assert.Subset(t, users, []string{"user1", "user2"}) + session := loginUser(t, "user1") + listUsers := func(query string) (*HTMLDoc, []string) { + req := NewRequest(t, "GET", "/-/admin/users?"+query) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + return doc, doc.Find("table tbody tr td:nth-child(2) a").Map(func(_ int, s *goquery.Selection) string { + return s.Text() + }) + } - doc, users = listUsers(fmt.Sprintf("source_id=%d", source.ID)) // the "test-user-list-filter" LDAP source - AssertHTMLElement(t, doc, fmt.Sprintf(`input[name="source_id"][value="%d"][checked]`, source.ID), true) - assert.Equal(t, []string{"user2"}, users) - assert.Equal(t, source.Name, doc.Find("table tbody tr td:nth-child(4)").Text()) + doc, users := listUsers("source_id=") // the "All" option submits an empty value + AssertHTMLElement(t, doc, `input[name="source_id"][value=""][checked]`, true) + assert.Subset(t, users, []string{"user1", "user2"}) - _, users = listUsers("source_id=0") // 0 means the "Local" source - assert.Contains(t, users, "user1") - assert.NotContains(t, users, "user2") + doc, users = listUsers(fmt.Sprintf("source_id=%d", source.ID)) // the "test-user-list-filter" LDAP source + AssertHTMLElement(t, doc, fmt.Sprintf(`input[name="source_id"][value="%d"][checked]`, source.ID), true) + assert.Equal(t, []string{"user2"}, users) + assert.Equal(t, source.Name, doc.Find("table tbody tr td:nth-child(4)").Text()) - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", "/api/v1/admin/users?source_id=0").AddTokenAuth(token) // the API also treats 0 as local users - apiUsers := DecodeJSON(t, MakeRequest(t, req, http.StatusOK), []api.User{}) - apiUserNames := make([]string, 0, len(apiUsers)) - for _, u := range apiUsers { - apiUserNames = append(apiUserNames, u.UserName) - } - assert.Contains(t, apiUserNames, "user1") - assert.NotContains(t, apiUserNames, "user2") -} + _, users = listUsers("source_id=0") // 0 means the "Local" source + assert.Contains(t, users, "user1") + assert.NotContains(t, users, "user2") -func TestAdminViewUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - req := NewRequest(t, "GET", "/-/admin/users/1") - session.MakeRequest(t, req, http.StatusOK) - - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/-/admin/users/1") - session.MakeRequest(t, req, http.StatusForbidden) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) + req := NewRequest(t, "GET", "/api/v1/admin/users?source_id=0").AddTokenAuth(token) // the API also treats 0 as local users + apiUsers := DecodeJSON(t, MakeRequest(t, req, http.StatusOK), []api.User{}) + apiUserNames := make([]string, 0, len(apiUsers)) + for _, u := range apiUsers { + apiUserNames = append(apiUserNames, u.UserName) + } + assert.Contains(t, apiUserNames, "user1") + assert.NotContains(t, apiUserNames, "user2") + }) } func TestAdminEditUser(t *testing.T) { diff --git a/tests/integration/api_actions_artifact_test.go b/tests/integration/api_actions_artifact_test.go index 86ec2c18c15..618a8583831 100644 --- a/tests/integration/api_actions_artifact_test.go +++ b/tests/integration/api_actions_artifact_test.go @@ -189,39 +189,85 @@ func TestActionsJobSummaryUpload(t *testing.T) { }) } -func TestActionsArtifactUploadSingleFile(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() +func TestActionsArtifactUpload(t *testing.T) { + type uploadingFile struct { + Path string + Content string + MD5 string + } - // acquire artifact upload url - req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ - Type: "actions_storage", - Name: "artifact", - }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - resp := MakeRequest(t, req, http.StatusOK) - uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) - assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") + for _, test := range []struct { + name string + artifactName string + confirmName string + retentionDays int64 + files []uploadingFile + }{ + { + name: "single-file", + artifactName: "artifact", + confirmName: "artifact-single", + files: []uploadingFile{{"abc-2.txt", strings.Repeat("C", 1024), "XVlf820rMInUi64wmMi6EA=="}}, + }, + { + name: "multiple-files", + artifactName: "multi-files", + confirmName: "multi-files", + files: []uploadingFile{ + {"abc-3.txt", strings.Repeat("D", 1024), "9nqj7E8HZmfQtPifCJ5Zww=="}, + {"xyz/def-2.txt", strings.Repeat("E", 1024), "/s1kKvxeHlUX85vaTaVxuA=="}, + }, + }, + { + name: "retention-days", + artifactName: "artifact-retention-days", + confirmName: "artifact-retention-days", + retentionDays: 9, + files: []uploadingFile{{"abc.txt", strings.Repeat("A", 1024), "1HsSe8LeLWh93ILaw1TEFQ=="}}, + }, + } { + t.Run(test.name, func(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() - // get upload url - idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=artifact/abc-2.txt" + // acquire artifact upload url + req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ + Type: "actions_storage", + Name: test.artifactName, + RetentionDays: test.retentionDays, + }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + resp := MakeRequest(t, req, http.StatusOK) + uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) + assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") - // upload artifact chunk - body := strings.Repeat("C", 1024) - req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). - SetHeader("Content-Range", "bytes 0-1023/1024"). - SetHeader("Content-Encoding", "video/mp4,text/html"). - SetHeader("x-tfs-filelength", "1024"). - SetHeader("x-actions-results-md5", "XVlf820rMInUi64wmMi6EA==") // base64(md5(body)) - MakeRequest(t, req, http.StatusOK) - assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{RunID: 791, ArtifactName: "artifact", ArtifactPath: "abc-2.txt"}).ContentEncodingOrType) + querySeparator := "?" + if test.retentionDays != 0 { + assert.Contains(t, uploadResp.FileContainerResourceURL, "?retentionDays=9") + querySeparator = "&" + } + for _, file := range test.files { + // get upload url + idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") + url := uploadResp.FileContainerResourceURL[idx:] + querySeparator + "itemPath=" + test.artifactName + "/" + file.Path - t.Logf("Create artifact confirm") + // upload artifact chunk + req = NewRequestWithBody(t, "PUT", url, strings.NewReader(file.Content)). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). + SetHeader("Content-Range", "bytes 0-1023/1024"). + SetHeader("Content-Encoding", "video/mp4,text/html"). + SetHeader("x-tfs-filelength", "1024"). + SetHeader("x-actions-results-md5", file.MD5) // base64(md5(body)) + MakeRequest(t, req, http.StatusOK) + assert.Empty(t, unittest.AssertExistsAndLoadBean(t, &actions_model.ActionArtifact{RunID: 791, ArtifactName: test.artifactName, ArtifactPath: file.Path}).ContentEncodingOrType) + } - // confirm artifact upload - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-single"). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - MakeRequest(t, req, http.StatusOK) + t.Logf("Create artifact confirm") + + // confirm artifact upload + req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName="+test.confirmName). + AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") + MakeRequest(t, req, http.StatusOK) + }) + } } func TestActionsArtifactUploadInvalidHash(t *testing.T) { @@ -316,61 +362,6 @@ func TestActionsArtifactDownload(t *testing.T) { assert.Equal(t, body, resp.Body.String()) } -func TestActionsArtifactUploadMultipleFile(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - const testArtifactName = "multi-files" - - // acquire artifact upload url - req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ - Type: "actions_storage", - Name: testArtifactName, - }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - resp := MakeRequest(t, req, http.StatusOK) - uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) - assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") - - type uploadingFile struct { - Path string - Content string - MD5 string - } - - files := []uploadingFile{ - { - Path: "abc-3.txt", - Content: strings.Repeat("D", 1024), - MD5: "9nqj7E8HZmfQtPifCJ5Zww==", - }, - { - Path: "xyz/def-2.txt", - Content: strings.Repeat("E", 1024), - MD5: "/s1kKvxeHlUX85vaTaVxuA==", - }, - } - - for _, f := range files { - // get upload url - idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadResp.FileContainerResourceURL[idx:] + "?itemPath=" + testArtifactName + "/" + f.Path - - // upload artifact chunk - req = NewRequestWithBody(t, "PUT", url, strings.NewReader(f.Content)). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). - SetHeader("Content-Range", "bytes 0-1023/1024"). - SetHeader("x-tfs-filelength", "1024"). - SetHeader("x-actions-results-md5", f.MD5) // base64(md5(body)) - MakeRequest(t, req, http.StatusOK) - } - - t.Logf("Create artifact confirm") - - // confirm artifact upload - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName="+testArtifactName). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - MakeRequest(t, req, http.StatusOK) -} - func TestActionsArtifactDownloadMultiFiles(t *testing.T) { defer prepareTestEnvActionsArtifacts(t)() @@ -424,41 +415,6 @@ func TestActionsArtifactDownloadMultiFiles(t *testing.T) { } } -func TestActionsArtifactUploadWithRetentionDays(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - // acquire artifact upload url - req := NewRequestWithJSON(t, "POST", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts", getUploadArtifactRequest{ - Type: "actions_storage", - Name: "artifact-retention-days", - RetentionDays: 9, - }).AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - resp := MakeRequest(t, req, http.StatusOK) - uploadResp := DecodeJSON(t, resp, &uploadArtifactResponse{}) - assert.Contains(t, uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts") - assert.Contains(t, uploadResp.FileContainerResourceURL, "?retentionDays=9") - - // get upload url - idx := strings.Index(uploadResp.FileContainerResourceURL, "/api/actions_pipeline/_apis/pipelines/") - url := uploadResp.FileContainerResourceURL[idx:] + "&itemPath=artifact-retention-days/abc.txt" - - // upload artifact chunk - body := strings.Repeat("A", 1024) - req = NewRequestWithBody(t, "PUT", url, strings.NewReader(body)). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a"). - SetHeader("Content-Range", "bytes 0-1023/1024"). - SetHeader("x-tfs-filelength", "1024"). - SetHeader("x-actions-results-md5", "1HsSe8LeLWh93ILaw1TEFQ==") // base64(md5(body)) - MakeRequest(t, req, http.StatusOK) - - t.Logf("Create artifact confirm") - - // confirm artifact upload - req = NewRequest(t, "PATCH", "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts?artifactName=artifact-retention-days"). - AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a") - MakeRequest(t, req, http.StatusOK) -} - func TestActionsArtifactOverwrite(t *testing.T) { defer prepareTestEnvActionsArtifacts(t)() diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go index ddb4515ee9a..72662a128bc 100644 --- a/tests/integration/api_actions_artifact_v4_test.go +++ b/tests/integration/api_actions_artifact_v4_test.go @@ -756,46 +756,30 @@ func TestActionsArtifactV4ListAndGetPublicApi(t *testing.T) { } } -func TestActionsArtifactV4GetArtifactMismatchedRepoNotFound(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() +func TestActionsArtifactV4ArtifactAccess(t *testing.T) { + for _, testCase := range []struct { + name string + repoID int64 + suffix string + statusCode int + }{ + {"GetArtifactMismatchedRepoNotFound", 1, "", http.StatusNotFound}, + {"DownloadArtifactMismatchedRepoNotFound", 1, "/zip", http.StatusNotFound}, + {"DownloadArtifactCorrectRepoFound", 4, "/zip", http.StatusFound}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: testCase.repoID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - // confirm artifacts of wrong repo is not visible - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestActionsArtifactV4DownloadArtifactMismatchedRepoNotFound(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // confirm artifacts of wrong repo is not visible - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestActionsArtifactV4DownloadArtifactCorrectRepoFound(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - // confirm artifacts of correct repo is visible - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d/zip", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusFound) + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d%s", repo.FullName(), 22, testCase.suffix), nil). + AddTokenAuth(token) + MakeRequest(t, req, testCase.statusCode) + }) + } } func TestActionsArtifactV4Delete(t *testing.T) { @@ -837,51 +821,37 @@ func TestActionsArtifactV4Delete(t *testing.T) { } func TestActionsArtifactV4DeletePublicApi(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() + for _, testCase := range []struct { + scope auth_model.AccessTokenScope + deleteStatus int + getStatus int + }{ + {auth_model.AccessTokenScopeWriteRepository, http.StatusNoContent, http.StatusNotFound}, + {auth_model.AccessTokenScopeReadRepository, http.StatusForbidden, http.StatusOK}, + } { + t.Run(string(testCase.scope), func(t *testing.T) { + defer prepareTestEnvActionsArtifacts(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, testCase.scope) - // confirm artifacts exists - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + // confirm artifacts exists + req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - // delete artifact by id - req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + // delete artifact by id + req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, testCase.deleteStatus) - // confirm artifacts has been deleted - req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestActionsArtifactV4DeletePublicApiNotAllowedReadScope(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // confirm artifacts exists - req := NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - // try delete artifact by id - req = NewRequestWithBody(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - // confirm artifacts has not been deleted - req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + req = NewRequestWithBody(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/artifacts/%d", repo.FullName(), 22), nil). + AddTokenAuth(token) + MakeRequest(t, req, testCase.getStatus) + }) + } } func testActionRunAttemptArtifactV4(t *testing.T, repo *repo_model.Repository, session *TestSession, runner *mockRunner) { diff --git a/tests/integration/api_actions_run_test.go b/tests/integration/api_actions_run_test.go index d28d8e2c643..1bb31b5da09 100644 --- a/tests/integration/api_actions_run_test.go +++ b/tests/integration/api_actions_run_test.go @@ -198,13 +198,11 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { session := loginUser(t, user.Name) writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/793/jobs/194/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusBadRequest) + for _, endpoint := range []string{"runs/793/rerun", "runs/793/jobs/194/rerun"} { + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/%s", repo.FullName(), endpoint)). + AddTokenAuth(writeToken) + MakeRequest(t, req, http.StatusBadRequest) + } }) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) @@ -312,41 +310,30 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { require.NoError(t, err) require.True(t, hasLatestAttempt) - job198 := getLatestAttemptJobByTemplateJobID(t, 795, 198) - assert.Equal(t, actions_model.StatusWaiting, job198.Status) - assert.Equal(t, latestAttempt.Attempt, job198.Attempt) - assert.Equal(t, int64(0), job198.TaskID) - - job199 := getLatestAttemptJobByTemplateJobID(t, 795, 199) - assert.Equal(t, actions_model.StatusWaiting, job199.Status) - assert.Equal(t, latestAttempt.Attempt, job199.Attempt) - assert.Equal(t, int64(0), job199.TaskID) + for _, jobID := range []int64{198, 199} { + job := getLatestAttemptJobByTemplateJobID(t, 795, jobID) + assert.Equal(t, actions_model.StatusWaiting, job.Status) + assert.Equal(t, latestAttempt.Attempt, job.Attempt) + assert.Equal(t, int64(0), job.TaskID) + } }) - t.Run("ForbiddenWithoutWriteScope", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/rerun", repo.FullName())). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("NotFound", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/999999/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("NotFoundJob", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/999999/rerun", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("NoLogsAfterRerun", func(t *testing.T) { + for _, testCase := range []struct { + name, method, endpoint, token string + status int + }{ + {"ForbiddenWithoutWriteScope", "POST", "runs/795/rerun", readToken, http.StatusForbidden}, + {"NotFound", "POST", "runs/999999/rerun", writeToken, http.StatusNotFound}, + {"NotFoundJob", "POST", "runs/795/jobs/999999/rerun", writeToken, http.StatusNotFound}, // the full rerun above cleared both TaskID and SourceTaskID on every latest-attempt job - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/logs", repo.FullName())). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusNotFound) - }) + {"NoLogsAfterRerun", "GET", "runs/795/logs", writeToken, http.StatusNotFound}, + } { + t.Run(testCase.name, func(t *testing.T) { + req := NewRequest(t, testCase.method, fmt.Sprintf("/api/v1/repos/%s/actions/%s", repo.FullName(), testCase.endpoint)). + AddTokenAuth(testCase.token) + MakeRequest(t, req, testCase.status) + }) + } } func testAPIActionsCancelWorkflowRun(t *testing.T) { @@ -744,39 +731,29 @@ func testAPIActionsGetWorkflowJobLogsNotFound(t *testing.T) { func TestAPIOrgActionsRunsAccessControl(t *testing.T) { defer tests.PrepareTestEnv(t)() + checkAccess := func(token, runMessage, jobMessage string) { + req := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runs").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runs := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) + for _, run := range runs.Entries { + assert.NotEqual(t, int64(802), run.ID, runMessage) + } + + req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/jobs").AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) + for _, job := range jobs.Entries { + assert.NotEqual(t, int64(802), job.RunID, jobMessage) + } + } + // org3 has action run 802 (and its jobs) in the private repo5; user28 is an org3 member // (teams 12/13) with no access to repo5. token := getUserToken(t, "user28", auth_model.AccessTokenScopeReadOrganization) - - req := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runs").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - runs := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) - for _, r := range runs.Entries { - assert.NotEqual(t, int64(802), r.ID, "must not leak a run from an inaccessible repo") - } - - req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/jobs").AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) - for _, j := range jobs.Entries { - assert.NotEqual(t, int64(802), j.RunID, "must not leak a job from an inaccessible repo run") - } + checkAccess(token, "must not leak a run from an inaccessible repo", "must not leak a job from an inaccessible repo run") // user1 is a site admin: it normally bypasses the per-repo access filter, but a public-only token // must stay confined to public repos, so the run/job in the private repo5 must not be listed. adminPublicOnly := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) - - req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runs").AddTokenAuth(adminPublicOnly) - resp = MakeRequest(t, req, http.StatusOK) - adminRuns := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) - for _, r := range adminRuns.Entries { - assert.NotEqual(t, int64(802), r.ID, "a public-only admin token must not list a private repo's run") - } - - req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/jobs").AddTokenAuth(adminPublicOnly) - resp = MakeRequest(t, req, http.StatusOK) - adminJobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) - for _, j := range adminJobs.Entries { - assert.NotEqual(t, int64(802), j.RunID, "a public-only admin token must not list a private repo's job") - } + checkAccess(adminPublicOnly, "a public-only admin token must not list a private repo's run", "a public-only admin token must not list a private repo's job") } diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index 8fefe51ba9a..6e6b114ea2d 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -22,64 +22,48 @@ func TestAPIAdminOrgCreate(t *testing.T) { session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - t.Run("CreateOrg", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "private", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + for _, testCase := range []struct { + name string + visibility api.VisibilityString + status int + }{ + {"CreateOrg", "private", http.StatusCreated}, + {"CreateBadVisibility", "notvalid", http.StatusUnprocessableEntity}, + {"CreateNotAdmin", "public", http.StatusForbidden}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testToken := token + if testCase.status == http.StatusForbidden { + session := loginUser(t, "user2") + testToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) + } + org := api.CreateOrgOption{ + UserName: "user2_org", + FullName: "User2's organization", + Description: "This organization created by admin for user2", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: testCase.visibility, + } + req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). + AddTokenAuth(testToken) + resp := MakeRequest(t, req, testCase.status) + if testCase.status == http.StatusCreated { + apiOrg := DecodeJSON(t, resp, &api.Organization{}) + assert.Equal(t, org.UserName, apiOrg.Name) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) - apiOrg := DecodeJSON(t, resp, &api.Organization{}) - - assert.Equal(t, org.UserName, apiOrg.Name) - assert.Equal(t, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) - - unittest.AssertExistsAndLoadBean(t, &user_model.User{ - Name: org.UserName, - LowerName: strings.ToLower(org.UserName), - FullName: org.FullName, + unittest.AssertExistsAndLoadBean(t, &user_model.User{ + Name: org.UserName, + LowerName: strings.ToLower(org.UserName), + FullName: org.FullName, + }) + } }) - }) - t.Run("CreateBadVisibility", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "notvalid", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - t.Run("CreateNotAdmin", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - nonAdminUsername := "user2" - session := loginUser(t, nonAdminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) - org := api.CreateOrgOption{ - UserName: "user2_org", - FullName: "User2's organization", - Description: "This organization created by admin for user2", - Website: "https://try.gitea.io", - Location: "Shanghai", - Visibility: "public", - } - req := NewRequestWithJSON(t, "POST", "/api/v1/admin/users/user2/orgs", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - }) + } } diff --git a/tests/integration/api_admin_packages_test.go b/tests/integration/api_admin_packages_test.go deleted file mode 100644 index 11ef8910502..00000000000 --- a/tests/integration/api_admin_packages_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "bytes" - "fmt" - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIAdminListPackages(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - packageName := "admin-test-package" - packageVersion := "1.0.0" - - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user4.Name, packageName, packageVersion) - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})).AddBasicAuth(user4.Name) - MakeRequest(t, req, http.StatusCreated) - - adminToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) - req = NewRequest(t, "GET", "/api/v1/admin/packages").AddTokenAuth(adminToken) - resp := MakeRequest(t, req, http.StatusOK) - - apiPackages := DecodeJSON(t, resp, []*api.Package{}) - actual := map[string]any{} - for _, p := range apiPackages { - actual[p.Name] = map[string]any{"ownerName": p.Owner.UserName, "type": p.Type, "version": p.Version} - } - assert.Equal(t, map[string]any{ - packageName: map[string]any{"ownerName": user4.Name, "type": "generic", "version": packageVersion}, - }, actual) -} diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index dfcacdf0687..dccf666e2e2 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -4,6 +4,7 @@ package integration import ( + "bytes" "fmt" "net/http" "testing" @@ -23,140 +24,205 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { +func TestAPIAdminListPackages(t *testing.T) { defer tests.PrepareTestEnv(t)() - // user1 is an admin user - session := loginUser(t, "user1") - keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name) - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - "title": "test-key", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + packageName := "admin-test-package" + packageVersion := "1.0.0" - newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) - unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{ - ID: newPublicKey.ID, - Name: newPublicKey.Title, - Fingerprint: newPublicKey.Fingerprint, - OwnerID: keyOwner.ID, + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user4.Name, packageName, packageVersion) + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{})).AddBasicAuth(user4.Name) + MakeRequest(t, req, http.StatusCreated) + + adminToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadAdmin) + req = NewRequest(t, "GET", "/api/v1/admin/packages").AddTokenAuth(adminToken) + resp := MakeRequest(t, req, http.StatusOK) + + apiPackages := DecodeJSON(t, resp, []*api.Package{}) + actual := map[string]any{} + for _, p := range apiPackages { + actual[p.Name] = map[string]any{"ownerName": p.Owner.UserName, "type": p.Type, "version": p.Version} + } + assert.Equal(t, map[string]any{ + packageName: map[string]any{"ownerName": user4.Name, "type": "generic", "version": packageVersion}, + }, actual) +} + +func TestAPIAdminAccessAndSSHKeys(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("ListUsers", func(t *testing.T) { + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin) + + req := NewRequest(t, "GET", "/api/v1/admin/users"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + users := DecodeJSON(t, resp, []api.User{}) + + found := false + for _, user := range users { + if user.UserName == adminUsername { + found = true + } + } + assert.True(t, found) + numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") + assert.Len(t, users, numberOfUsers) }) - req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", keyOwner.Name, newPublicKey.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID}) -} + t.Run("ListUsersNotLoggedIn", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/admin/users") + MakeRequest(t, req, http.StatusUnauthorized) + }) -func TestAPIAdminDeleteMissingSSHKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ListUsersNonAdmin", func(t *testing.T) { + nonAdminUsername := "user2" + token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) + req := NewRequest(t, "GET", "/api/v1/admin/users"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) - // user1 is an admin user - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin) - req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", unittest.NonexistentID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} + t.Run("SudoUser", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) -func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + req := NewRequest(t, "GET", "/api/v1/user?sudo="+normalUsername). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + user := DecodeJSON(t, resp, &api.User{}) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername) - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - "title": "test-key", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) + assert.Equal(t, normalUsername, user.UserName) + }) - token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) - req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} + t.Run("SudoUserForbidden", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" -func TestAPISudoUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) + req := NewRequest(t, "GET", "/api/v1/user?sudo="+adminUsername). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) - req := NewRequest(t, "GET", "/api/v1/user?sudo="+normalUsername). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - user := DecodeJSON(t, resp, &api.User{}) + t.Run("CreateUserInvalidEmail", func(t *testing.T) { + adminUsername := "user1" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{ + "email": "invalid_email@domain.com\r\n", + "full_name": "invalid user", + "login_name": "invalidUser", + "must_change_password": "true", + "password": "password", + "send_notify": "true", + "source_id": "0", + "username": "invalidUser", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) - assert.Equal(t, normalUsername, user.UserName) -} + t.Run("DeleteMissingSSHKey", func(t *testing.T) { + // user1 is an admin user + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin) + req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", unittest.NonexistentID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) -func TestAPISudoUserForbidden(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" + t.Run("Cron", func(t *testing.T) { + // user1 is an admin user + session := loginUser(t, "user1") - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", "/api/v1/user?sudo="+adminUsername). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} + t.Run("List", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() -func TestAPIListUsers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", "/api/v1/admin/users"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - users := DecodeJSON(t, resp, []api.User{}) + req := NewRequest(t, "GET", "/api/v1/admin/cron"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - found := false - for _, user := range users { - if user.UserName == adminUsername { - found = true - } - } - assert.True(t, found) - numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") - assert.Len(t, users, numberOfUsers) -} + assert.Equal(t, "30", resp.Header().Get("X-Total-Count")) -func TestAPIListUsersNotLoggedIn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/admin/users") - MakeRequest(t, req, http.StatusUnauthorized) -} + crons := DecodeJSON(t, resp, []api.Cron{}) + assert.Len(t, crons, 30) + }) -func TestAPIListUsersNonAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - nonAdminUsername := "user2" - token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) - req := NewRequest(t, "GET", "/api/v1/admin/users"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} + t.Run("Execute", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() -func TestAPICreateUserInvalidEmail(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{ - "email": "invalid_email@domain.com\r\n", - "full_name": "invalid user", - "login_name": "invalidUser", - "must_change_password": "true", - "password": "password", - "send_notify": "true", - "source_id": "0", - "username": "invalidUser", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + now := time.Now() + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + // Archive cleanup is harmless, because in the test environment there are none + // and is thus an NOOP operation and therefore doesn't interfere with any other + // tests. + req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + // Check for the latest run time for this cron, to ensure it has been run. + req = NewRequest(t, "GET", "/api/v1/admin/cron"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + crons := DecodeJSON(t, resp, []api.Cron{}) + + for _, cron := range crons { + if cron.Name == "archive_cleanup" { + assert.True(t, now.Before(cron.Prev)) + } + } + }) + }) + + t.Run("CreateAndDeleteSSHKey", func(t *testing.T) { + // user1 is an admin user + session := loginUser(t, "user1") + keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + "title": "test-key", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{ + ID: newPublicKey.ID, + Name: newPublicKey.Title, + Fingerprint: newPublicKey.Fingerprint, + OwnerID: keyOwner.ID, + }) + + req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", keyOwner.Name, newPublicKey.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID}) + }) + + t.Run("DeleteUnauthorizedKey", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) + + urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername) + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + "title": "test-key", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + newPublicKey := DecodeJSON(t, resp, &api.PublicKey{}) + + token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) + req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) } func TestAPICreateAndDeleteUser(t *testing.T) { @@ -239,87 +305,24 @@ func TestAPIRenameUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename", "user2") - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "User2", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2") - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "User2-2-2", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "user1", - }).AddTokenAuth(token) - // the old user name still be used by with a redirect - MakeRequest(t, req, http.StatusTemporaryRedirect) - - urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2-2-2") - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "user1", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ - // required - "new_name": "user2", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} - -func TestAPICron(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user1 is an admin user - session := loginUser(t, "user1") - - t.Run("List", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) - - req := NewRequest(t, "GET", "/api/v1/admin/cron"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, "30", resp.Header().Get("X-Total-Count")) - - crons := DecodeJSON(t, resp, []api.Cron{}) - assert.Len(t, crons, 30) - }) - - t.Run("Execute", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - now := time.Now() - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) - // Archive cleanup is harmless, because in the test environment there are none - // and is thus an NOOP operation and therefore doesn't interfere with any other - // tests. - req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Check for the latest run time for this cron, to ensure it has been run. - req = NewRequest(t, "GET", "/api/v1/admin/cron"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - crons := DecodeJSON(t, resp, []api.Cron{}) - - for _, cron := range crons { - if cron.Name == "archive_cleanup" { - assert.True(t, now.Before(cron.Prev)) - } - } - }) + for _, testCase := range []struct { + username string + newName string + status int + }{ + {"user2", "User2", http.StatusNoContent}, + {"User2", "User2-2-2", http.StatusNoContent}, + // the old user name still be used by with a redirect + {"User2", "user1", http.StatusTemporaryRedirect}, + {"User2-2-2", "user1", http.StatusUnprocessableEntity}, + {"User2-2-2", "user2", http.StatusNoContent}, + } { + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/admin/users/%s/rename", testCase.username), map[string]string{ + // required + "new_name": testCase.newName, + }).AddTokenAuth(token) + MakeRequest(t, req, testCase.status) + } } func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) { diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 22f6472e0be..2c35df89cfb 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -91,21 +91,6 @@ func testAPIDeleteBranch(t *testing.T, branchName string, expectedHTTPStatus int MakeRequest(t, req, expectedHTTPStatus) } -func TestAPIGetBranch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - for _, test := range []struct { - BranchName string - Exists bool - }{ - {"master", true}, - {"master/doesnotexist", false}, - {"feature/1", true}, - {"feature/1/doesnotexist", false}, - } { - testAPIGetBranch(t, test.BranchName, test.Exists) - } -} - func TestAPICreateBranch(t *testing.T) { onGiteaRun(t, testAPICreateBranches) } @@ -275,8 +260,6 @@ func TestAPIRenameBranch(t *testing.T) { } func TestAPIUpdateBranchReference(t *testing.T) { - defer tests.PrepareTestEnv(t)() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { ctx := NewAPITestContext(t, "user2", "update-branch", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() @@ -312,34 +295,23 @@ func TestAPIUpdateBranchReference(t *testing.T) { assert.NotEmpty(t, newCommit) })(t) - updateReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: newCommit, - OldCommitID: featureInitialCommit, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, updateReq, http.StatusNoContent) + updateBranch := func(newCommitID, oldCommitID string, force bool, expectedStatus int) { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ + NewCommitID: newCommitID, + OldCommitID: oldCommitID, + Force: force, + }).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, expectedStatus) + } + updateBranch(newCommit, featureInitialCommit, false, http.StatusNoContent) t.Run("FastForwardApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { assert.Equal(t, newCommit, branch.Commit.ID) })) - staleReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: newCommit, - OldCommitID: featureInitialCommit, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, staleReq, http.StatusUnprocessableEntity) - - nonFFReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: featureInitialCommit, - OldCommitID: newCommit, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, nonFFReq, http.StatusUnprocessableEntity) - - forceReq := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, "feature"), &api.UpdateBranchRepoOption{ - NewCommitID: featureInitialCommit, - OldCommitID: newCommit, - Force: true, - }).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, forceReq, http.StatusNoContent) + updateBranch(newCommit, featureInitialCommit, false, http.StatusUnprocessableEntity) + updateBranch(featureInitialCommit, newCommit, false, http.StatusUnprocessableEntity) + updateBranch(featureInitialCommit, newCommit, true, http.StatusNoContent) t.Run("ForceApplied", doAPIGetBranch(ctx, "feature", func(t *testing.T, branch api.Branch) { assert.Equal(t, featureInitialCommit, branch.Commit.ID) @@ -357,6 +329,19 @@ func testAPIRenameBranch(t *testing.T, doerName, ownerName, repoName, from, to s func TestAPIBranchProtection(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("GetBranch", func(t *testing.T) { + for _, test := range []struct { + BranchName string + Exists bool + }{ + {"master", true}, + {"master/doesnotexist", false}, + {"feature/1", true}, + {"feature/1/doesnotexist", false}, + } { + testAPIGetBranch(t, test.BranchName, test.Exists) + } + }) t.Run("Basic", testAPIBranchProtectionBasic) t.Run("BypassAllowlistValidation", testAPIBranchProtectionBypassAllowlistValidation) } diff --git a/tests/integration/api_comment_attachment_test.go b/tests/integration/api_comment_attachment_test.go index e7b36e0b27e..45f5401a697 100644 --- a/tests/integration/api_comment_attachment_test.go +++ b/tests/integration/api_comment_attachment_test.go @@ -22,129 +22,106 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIGetCommentAttachment(t *testing.T) { +func TestAPICommentAttachments(t *testing.T) { defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - assert.NoError(t, comment.LoadIssue(t.Context())) - assert.NoError(t, comment.LoadAttachments(t.Context())) - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: comment.Attachments[0].ID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - t.Run("UnrelatedCommentID", func(t *testing.T) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + t.Run("Get", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + assert.NoError(t, comment.LoadIssue(t.Context())) + assert.NoError(t, comment.LoadAttachments(t.Context())) + attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: comment.Attachments[0].ID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + + t.Run("UnrelatedCommentID", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) + + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + session.MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) + + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + + expect := convert.ToAPIAttachment(t.Context(), repo, attachment) + assert.Equal(t, expect.ID, apiAttachment.ID) + assert.Equal(t, expect.Name, apiAttachment.Name) + assert.Equal(t, expect.UUID, apiAttachment.UUID) + assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix()) + assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL) }) + t.Run("List", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). - AddTokenAuth(token) - session.MakeRequest(t, req, http.StatusOK) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + apiAttachments := DecodeJSON(t, resp, []*api.Attachment{}) + expectedCount := unittest.GetCount(t, &repo_model.Attachment{CommentID: comment.ID}) + assert.Len(t, apiAttachments, expectedCount) - expect := convert.ToAPIAttachment(t.Context(), repo, attachment) - assert.Equal(t, expect.ID, apiAttachment.ID) - assert.Equal(t, expect.Name, apiAttachment.Name) - assert.Equal(t, expect.UUID, apiAttachment.UUID) - assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix()) - assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL) -} + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachments[0].ID, CommentID: comment.ID}) + }) + t.Run("Create", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) -func TestAPIListCommentAttachments(t *testing.T) { - defer tests.PrepareTestEnv(t)() + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + for _, test := range []struct { + filename string + status int + }{ + {"file.bad", http.StatusUnprocessableEntity}, + {"image.png", http.StatusCreated}, + } { + t.Run(test.filename, func(t *testing.T) { + body := &bytes.Buffer{} - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + // Setup multi-part + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", test.filename) + assert.NoError(t, err) + if test.status == http.StatusCreated { + _, err = part.Write(testGeneratePngBytes()) + assert.NoError(t, err) + } + err = writer.Close() + assert.NoError(t, err) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) - - apiAttachments := DecodeJSON(t, resp, []*api.Attachment{}) - expectedCount := unittest.GetCount(t, &repo_model.Attachment{CommentID: comment.ID}) - assert.Len(t, apiAttachments, expectedCount) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachments[0].ID, CommentID: comment.ID}) -} - -func TestAPICreateCommentAttachment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - body := &bytes.Buffer{} - - // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", "image.png") - assert.NoError(t, err) - _, err = part.Write(testGeneratePngBytes()) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). - AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) - resp := session.MakeRequest(t, req, http.StatusCreated) - - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID}) -} - -func TestAPICreateCommentAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - body := &bytes.Buffer{} - - // Setup multi-part. - writer := multipart.NewWriter(body) - _, err := writer.CreateFormFile("attachment", filename) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). - AddTokenAuth(token). - SetHeader("Content-Type", writer.FormDataContentType()) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body). + AddTokenAuth(token). + SetHeader("Content-Type", writer.FormDataContentType()) + resp := session.MakeRequest(t, req, test.status) + if test.status == http.StatusCreated { + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID}) + } + }) + } + }) } func TestAPIEditCommentAttachment(t *testing.T) { defer tests.PrepareTestEnv(t)() - const newAttachmentName = "newAttachmentName.txt" - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6}) comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) @@ -153,35 +130,26 @@ func TestAPIEditCommentAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", - repoOwner.Name, repo.Name, comment.ID, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": newAttachmentName, - }).AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusCreated) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID, Name: apiAttachment.Name}) -} - -func TestAPIEditCommentAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6}) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", - repoOwner.Name, repo.Name, comment.ID, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": filename, - }).AddTokenAuth(token) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) + for _, test := range []struct { + filename string + status int + }{ + {"file.bad", http.StatusUnprocessableEntity}, + {"newAttachmentName.txt", http.StatusCreated}, + } { + t.Run(test.filename, func(t *testing.T) { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", + repoOwner.Name, repo.Name, comment.ID, attachment.ID) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "name": test.filename, + }).AddTokenAuth(token) + resp := session.MakeRequest(t, req, test.status) + if test.status == http.StatusCreated { + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID, Name: apiAttachment.Name}) + } + }) + } } func TestAPIDeleteCommentAttachment(t *testing.T) { diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 8ef14256d6b..d60c3482604 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -21,68 +21,143 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIListRepoComments(t *testing.T) { +func TestAPIComments(t *testing.T) { defer tests.PrepareTestEnv(t)() - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, - unittest.Cond("type = ?", issues_model.CommentTypeComment)) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) - req := NewRequest(t, "GET", link.String()) - resp := MakeRequest(t, req, http.StatusOK) - - apiComments := DecodeJSON(t, resp, []*api.Comment{}) - assert.Len(t, apiComments, 2) - for _, apiComment := range apiComments { - c := &issues_model.Comment{ID: apiComment.ID} - unittest.AssertExistsAndLoadBean(t, c, + t.Run("ListRepoComments", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, unittest.Cond("type = ?", issues_model.CommentTypeComment)) - unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: c.IssueID, RepoID: repo.ID}) - } + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // test before and since filters - query := url.Values{} - before := "2000-01-01T00:00:11+00:00" // unix: 946684811 - since := "2000-01-01T00:00:12+00:00" // unix: 946684812 - query.Add("before", before) - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = MakeRequest(t, req, http.StatusOK) - apiComments = DecodeJSON(t, resp, []*api.Comment{}) - assert.Len(t, apiComments, 1) - assert.EqualValues(t, 2, apiComments[0].ID) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments", repoOwner.Name, repo.Name)) + req := NewRequest(t, "GET", link.String()) + resp := MakeRequest(t, req, http.StatusOK) - query.Del("before") - query.Add("since", since) - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = MakeRequest(t, req, http.StatusOK) - apiComments = DecodeJSON(t, resp, []*api.Comment{}) - assert.Len(t, apiComments, 1) - assert.EqualValues(t, 3, apiComments[0].ID) -} + apiComments := DecodeJSON(t, resp, []*api.Comment{}) + assert.Len(t, apiComments, 2) + for _, apiComment := range apiComments { + c := &issues_model.Comment{ID: apiComment.ID} + unittest.AssertExistsAndLoadBean(t, c, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: c.IssueID, RepoID: repo.ID}) + } -func TestAPIListIssueComments(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // test before and since filters + query := url.Values{} + before := "2000-01-01T00:00:11+00:00" // unix: 946684811 + since := "2000-01-01T00:00:12+00:00" // unix: 946684812 + query.Add("before", before) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + apiComments = DecodeJSON(t, resp, []*api.Comment{}) + assert.Len(t, apiComments, 1) + assert.EqualValues(t, 2, apiComments[0].ID) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, - unittest.Cond("type = ?", issues_model.CommentTypeComment)) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + query.Del("before") + query.Add("since", since) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = MakeRequest(t, req, http.StatusOK) + apiComments = DecodeJSON(t, resp, []*api.Comment{}) + assert.Len(t, apiComments, 1) + assert.EqualValues(t, 3, apiComments[0].ID) + }) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", repoOwner.Name, repo.Name, issue.Index). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("ListIssueComments", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - comments := DecodeJSON(t, resp, []*api.Comment{}) - expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}, - unittest.Cond("type = ?", issues_model.CommentTypeComment)) - assert.Len(t, comments, expectedCount) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments", repoOwner.Name, repo.Name, issue.Index). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + comments := DecodeJSON(t, resp, []*api.Comment{}) + expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}, + unittest.Cond("type = ?", issues_model.CommentTypeComment)) + assert.Len(t, comments, expectedCount) + }) + + t.Run("GetComment", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + assert.NoError(t, comment.LoadIssue(t.Context())) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + apiComment := DecodeJSON(t, resp, &api.Comment{}) + + assert.NoError(t, comment.LoadPoster(t.Context())) + expect := convert.ToAPIComment(t.Context(), repo, comment) + + assert.Equal(t, expect.ID, apiComment.ID) + assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) + assert.Equal(t, expect.Body, apiComment.Body) + assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix()) + }) + + t.Run("ListIssueTimeline", func(t *testing.T) { + // load comment + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + // make request + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline", repoOwner.Name, repo.Name, issue.Index) + resp := MakeRequest(t, req, http.StatusOK) + + // check if lens of list returned by API and + // lists extracted directly from DB are the same + comments := DecodeJSON(t, resp, []*api.TimelineComment{}) + expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}) + assert.Len(t, comments, expectedCount) + }) + + t.Run("GetSystemUserComment", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + body := "Hello " + systemUser.Name + comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{ + Type: issues_model.CommentTypeComment, + Doer: systemUser, + Repo: repo, + Issue: issue, + Content: body, + }) + assert.NoError(t, err) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) + resp := MakeRequest(t, req, http.StatusOK) + + apiComment := DecodeJSON(t, resp, &api.Comment{}) + + if assert.NotNil(t, apiComment.Poster) { + if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) { + assert.NoError(t, comment.LoadPoster(t.Context())) + assert.Equal(t, systemUser.Name, apiComment.Poster.UserName) + } + } + assert.Equal(t, body, apiComment.Body) + } + }) } func TestAPICreateComment(t *testing.T) { @@ -105,92 +180,25 @@ func TestAPICreateComment(t *testing.T) { assert.Equal(t, commentBody, updatedComment.Body) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) - t.Run("BlockedByRepoOwner", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ - "body": commentBody, - }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("BlockedByIssuePoster", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 13}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ - "body": commentBody, - }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) -} - -func TestAPIGetComment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - assert.NoError(t, comment.LoadIssue(t.Context())) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) - MakeRequest(t, req, http.StatusOK) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiComment := DecodeJSON(t, resp, &api.Comment{}) - - assert.NoError(t, comment.LoadPoster(t.Context())) - expect := convert.ToAPIComment(t.Context(), repo, comment) - - assert.Equal(t, expect.ID, apiComment.ID) - assert.Equal(t, expect.Poster.FullName, apiComment.Poster.FullName) - assert.Equal(t, expect.Body, apiComment.Body) - assert.Equal(t, expect.Created.Unix(), apiComment.Created.Unix()) -} - -func TestAPIGetSystemUserComment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - for _, systemUser := range []*user_model.User{ - user_model.NewGhostUser(), - user_model.NewActionsUser(), + for _, tc := range []struct { + name string + issueID int64 + }{ + {"BlockedByRepoOwner", 1}, + {"BlockedByIssuePoster", 13}, } { - body := "Hello " + systemUser.Name - comment, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{ - Type: issues_model.CommentTypeComment, - Doer: systemUser, - Repo: repo, - Issue: issue, - Content: body, + t.Run(tc.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: tc.issueID}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", repo.OwnerName, repo.Name, issue.Index), map[string]string{ + "body": commentBody, + }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) + MakeRequest(t, req, http.StatusForbidden) }) - assert.NoError(t, err) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) - resp := MakeRequest(t, req, http.StatusOK) - - apiComment := DecodeJSON(t, resp, &api.Comment{}) - - if assert.NotNil(t, apiComment.Poster) { - if assert.Equal(t, systemUser.ID, apiComment.Poster.ID) { - assert.NoError(t, comment.LoadPoster(t.Context())) - assert.Equal(t, systemUser.Name, apiComment.Poster.UserName) - } - } - assert.Equal(t, body, apiComment.Body) } } @@ -257,22 +265,3 @@ func TestAPIDeleteComment(t *testing.T) { unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) } - -func TestAPIListIssueTimeline(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // load comment - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - // make request - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/timeline", repoOwner.Name, repo.Name, issue.Index) - resp := MakeRequest(t, req, http.StatusOK) - - // check if lens of list returned by API and - // lists extracted directly from DB are the same - comments := DecodeJSON(t, resp, []*api.TimelineComment{}) - expectedCount := unittest.GetCount(t, &issues_model.Comment{IssueID: issue.ID}) - assert.Len(t, comments, expectedCount) -} diff --git a/tests/integration/api_gitignore_templates_test.go b/tests/integration/api_gitignore_templates_test.go index 50e224bf67f..a639c3a4250 100644 --- a/tests/integration/api_gitignore_templates_test.go +++ b/tests/integration/api_gitignore_templates_test.go @@ -15,37 +15,37 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIListGitignoresTemplates(t *testing.T) { +func TestAPIGitignoreTemplates(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/gitignore/templates") - resp := MakeRequest(t, req, http.StatusOK) + t.Run("List", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/gitignore/templates") + resp := MakeRequest(t, req, http.StatusOK) - templateList := DecodeJSON(t, resp, []string{}) // this is a very long list - assert.Contains(t, templateList, "C++") - assert.Contains(t, templateList, "Go") -} - -func TestAPIGetGitignoreTemplateInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // If Gitea has for some reason no Gitignore templates, we need to skip this test - if len(repo_module.Gitignores) == 0 { - return - } - - // Use the first template for the test - templateName := repo_module.Gitignores[0] - - urlStr := "/api/v1/gitignore/templates/" + templateName - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - templateInfo := DecodeJSON(t, resp, &api.GitignoreTemplateInfo{}) - - // We get the text of the template here - text, _ := options.Gitignore(templateName) - - assert.Equal(t, templateInfo.Name, templateName) - assert.Equal(t, templateInfo.Source, string(text)) + templateList := DecodeJSON(t, resp, []string{}) // this is a very long list + assert.Contains(t, templateList, "C++") + assert.Contains(t, templateList, "Go") + }) + + t.Run("Info", func(t *testing.T) { + // If Gitea has for some reason no Gitignore templates, we need to skip this test + if len(repo_module.Gitignores) == 0 { + return + } + + // Use the first template for the test + templateName := repo_module.Gitignores[0] + + urlStr := "/api/v1/gitignore/templates/" + templateName + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + templateInfo := DecodeJSON(t, resp, &api.GitignoreTemplateInfo{}) + + // We get the text of the template here + text, _ := options.Gitignore(templateName) + + assert.Equal(t, templateInfo.Name, templateName) + assert.Equal(t, templateInfo.Source, string(text)) + }) } diff --git a/tests/integration/api_gpg_keys_test.go b/tests/integration/api_gpg_keys_test.go index a2a19d58888..0240414bd33 100644 --- a/tests/integration/api_gpg_keys_test.go +++ b/tests/integration/api_gpg_keys_test.go @@ -47,31 +47,23 @@ func TestGPGKeys(t *testing.T) { for _, tc := range tt { // Basic test on result code t.Run(tc.name, func(t *testing.T) { - t.Run("ViewOwnGPGKeys", func(t *testing.T) { - testViewOwnGPGKeys(t, tc.makeRequest, tc.token, tc.results[0]) - }) - t.Run("ViewGPGKeys", func(t *testing.T) { - testViewGPGKeys(t, tc.makeRequest, tc.token, tc.results[1]) - }) - t.Run("GetGPGKey", func(t *testing.T) { - testGetGPGKey(t, tc.makeRequest, tc.token, tc.results[2]) - }) - t.Run("DeleteGPGKey", func(t *testing.T) { - testDeleteGPGKey(t, tc.makeRequest, tc.token, tc.results[3]) - }) - - t.Run("CreateInvalidGPGKey", func(t *testing.T) { - testCreateInvalidGPGKey(t, tc.makeRequest, tc.token, tc.results[4]) - }) - t.Run("CreateNoneRegisteredEmailGPGKey", func(t *testing.T) { - testCreateNoneRegisteredEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[5]) - }) - t.Run("CreateValidGPGKey", func(t *testing.T) { - testCreateValidGPGKey(t, tc.makeRequest, tc.token, tc.results[6]) - }) - t.Run("CreateValidSecondaryEmailGPGKeyNotActivated", func(t *testing.T) { - testCreateValidSecondaryEmailGPGKey(t, tc.makeRequest, tc.token, tc.results[7]) - }) + for index, operation := range []struct { + name string + run func(*testing.T, makeRequestFunc, string, int) + }{ + {"ViewOwnGPGKeys", testViewOwnGPGKeys}, + {"ViewGPGKeys", testViewGPGKeys}, + {"GetGPGKey", testGetGPGKey}, + {"DeleteGPGKey", testDeleteGPGKey}, + {"CreateInvalidGPGKey", testCreateInvalidGPGKey}, + {"CreateNoneRegisteredEmailGPGKey", testCreateNoneRegisteredEmailGPGKey}, + {"CreateValidGPGKey", testCreateValidGPGKey}, + {"CreateValidSecondaryEmailGPGKeyNotActivated", testCreateValidSecondaryEmailGPGKey}, + } { + t.Run(operation.name, func(t *testing.T) { + operation.run(t, tc.makeRequest, tc.token, tc.results[index]) + }) + } }) } @@ -112,29 +104,22 @@ func TestGPGKeys(t *testing.T) { // Check state after basic add t.Run("CheckCommits", func(t *testing.T) { - t.Run("NotSigned", func(t *testing.T) { - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/not-signed"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - branch := DecodeJSON(t, resp, &api.Branch{}) - assert.False(t, branch.Commit.Verification.Verified) - }) - - t.Run("SignedWithNotValidatedEmail", func(t *testing.T) { - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/good-sign-not-yet-validated"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - branch := DecodeJSON(t, resp, &api.Branch{}) - assert.False(t, branch.Commit.Verification.Verified) - }) - - t.Run("SignedWithValidEmail", func(t *testing.T) { - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/good-sign"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - branch := DecodeJSON(t, resp, &api.Branch{}) - assert.True(t, branch.Commit.Verification.Verified) - }) + for _, tc := range []struct { + name, branch string + assert assert.BoolAssertionFunc + }{ + {"NotSigned", "not-signed", assert.False}, + {"SignedWithNotValidatedEmail", "good-sign-not-yet-validated", assert.False}, + {"SignedWithValidEmail", "good-sign", assert.True}, + } { + t.Run(tc.name, func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16/branches/"+tc.branch). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + branch := DecodeJSON(t, resp, &api.Branch{}) + tc.assert(t, branch.Commit.Verification.Verified) + }) + } }) } diff --git a/tests/integration/api_httpsig_test.go b/tests/integration/api_httpsig_test.go index d100d0728ed..3056d936462 100644 --- a/tests/integration/api_httpsig_test.go +++ b/tests/integration/api_httpsig_test.go @@ -51,93 +51,92 @@ fhTNAzWwZoQ91aHdAAAAFHUwMDIyMTQ2QGljdHMtcC1ueC03AQIDBAUG httpsigCertificate = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgiR7SU8gmZLhopx4Y03nOXVuAb+4fyMcJYjMGcE1Z2oEAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqdAAAAAAAAAAEAAAABAAAABXVzZXIxAAAACQAAAAV1c2VyMQAAAABimoIOAAAAAMCWkRMAAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABlwAAAAdzc2gtcnNhAAAAAwEAAQAAAYEAm+AwtXTBZyeqV1qOxjMU3Ibc5iR2M3zerGfRQDxUeIozC3xpIvqJbzjDuRapdf8hpxn2xC0GtUusuLIUr4/+Svs1BUnJhF2H9xnK/O0aopS5MpNekUvnBzQdbvO8Ux2xE2mt58giXhkEaXeCEODSqG++OZsA2e40AR/AGRJ4OdDofMvH4vLJAQQc2mKdYpYL8xu+NC+7nsenx1etpsqtEl3gmvqCVI6t9uhVPMvlbGt9h/AN3u7ToF2T3bdk1TZbcdkvR9ljvETIuy32ksAETX8tc7vm30edK+nn/GMeWCgjM+MFm9Uh1NRkvNNJozo5SJy0DkWETTJUsEdfry5VQ3IjqhWqQ0m4/mDlTmsEdEdWqpUiqWZLd9w7jgT8fanuglZyIu2fj8fyqjPjiws5S2P0Uvi28UKQ1nH01UYj/kuakU3BNzN1IqDf3tARP9fjKV/dCBqb1ZAOtyC2GyhGuGzNwEi+woUwq+sTeV0/hqVSb3hSitXHzcfRMRyOK82BAAABlAAAAAxyc2Etc2hhMi01MTIAAAGAMBfgZFvz4BdxriGKYd6eRhMo6hf+I8S9uzNRsflJXHuA+HR9ExIm/Q9JjKmfThQzNyGGBOBILaDU205SAJuG+kk3SieSQDd75ZQd8YmNlCc+516AriOsTiyVCupnf3I2euTjMZqEZbJcBbkBljppTOWQVN7xxE8QakDfGhg0+RjJE9wYOTmkKpDBfII5Nw8V5DoOD7kNEpXYqHdy/8lVxpqUYNIP1J0dNP4f6qBcZcM1PDA12q8zwIGqSNNjf2UXY/Nr8nv9CnK4fB8NDOPKTBa4cm48BGbvM/X0l6dYKswuZ9Np8lw+y6+GxTgznGCrkzMmuEV4FzSq4xHp41H2L2MTwUkwYaeyG1VP6aWkvn6zPkSxaaJDfQX7CAFe17IhIGXR0UPLjKjh35nDLzMWb/W6/W1lK9YkZNHXSf7Z9m9MUAZN7yQgOggGsuYEW4imZxvZizMd+fdDu9mbhr0FDis89I7MSJDnyYRE9FXS7p3QpppBwGcss/9yV3JV3Bjc` ) -func TestHTTPSigPubKey(t *testing.T) { - // Add our public key to user1 - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.SSH.MinimumKeySizeCheck, false)() - session := loginUser(t, "user1") - token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)) - keyType := "ssh-rsa" - keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqd" - rawKeyBody := api.CreateKeyOption{ - Title: "test-key", - Key: keyType + " " + keyContent, - } - req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", rawKeyBody). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - // parse our private key and create the httpsig request - sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) - keyID := ssh.FingerprintSHA256(sshSigner.PublicKey()) - - // create the request - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) - req = NewRequest(t, "GET", "/api/v1/admin/users"). - AddTokenAuth(token) - - signer, _, err := httpsig.NewSSHSigner(sshSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)"}, httpsig.Signature, 10) - if err != nil { - t.Fatal(err) - } - - // sign the request - err = signer.SignRequest(keyID, req.Request, nil) - if err != nil { - t.Fatal(err) - } - - // make the request - MakeRequest(t, req, http.StatusOK) -} - -func TestHTTPSigCert(t *testing.T) { - // Add our public key to user1 +func TestHTTPSig(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - req := NewRequestWithValues(t, "POST", "/user/settings/keys", map[string]string{ - "content": "user1", - "title": "principal", - "type": "principal", + t.Run("pub-key", func(t *testing.T) { + // Add our public key to user1 + defer test.MockVariableValue(&setting.SSH.MinimumKeySizeCheck, false)() + token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)) + keyType := "ssh-rsa" + keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqd" + rawKeyBody := api.CreateKeyOption{ + Title: "test-key", + Key: keyType + " " + keyContent, + } + req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", rawKeyBody). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + + // parse our private key and create the httpsig request + sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) + keyID := ssh.FingerprintSHA256(sshSigner.PublicKey()) + + // create the request + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) + req = NewRequest(t, "GET", "/api/v1/admin/users"). + AddTokenAuth(token) + + signer, _, err := httpsig.NewSSHSigner(sshSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)"}, httpsig.Signature, 10) + if err != nil { + t.Fatal(err) + } + + // sign the request + err = signer.SignRequest(keyID, req.Request, nil) + if err != nil { + t.Fatal(err) + } + + // make the request + MakeRequest(t, req, http.StatusOK) }) + t.Run("cert", func(t *testing.T) { + // Add our public key to user1 + req := NewRequestWithValues(t, "POST", "/user/settings/keys", map[string]string{ + "content": "user1", + "title": "principal", + "type": "principal", + }) - session.MakeRequest(t, req, http.StatusSeeOther) - pkcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(httpsigCertificate)) - if err != nil { - t.Fatal(err) - } + session.MakeRequest(t, req, http.StatusSeeOther) + pkcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(httpsigCertificate)) + if err != nil { + t.Fatal(err) + } - // parse our private key and create the httpsig request - sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) - keyID := "gitea" + // parse our private key and create the httpsig request + sshSigner, _ := ssh.ParsePrivateKey([]byte(httpsigPrivateKey)) + keyID := "gitea" - // create our certificate signer using the ssh signer and our certificate - cert, ok := pkcert.(*ssh.Certificate) - require.True(t, ok) - certSigner, err := ssh.NewCertSigner(cert, sshSigner) - if err != nil { - t.Fatal(err) - } + // create our certificate signer using the ssh signer and our certificate + cert, ok := pkcert.(*ssh.Certificate) + require.True(t, ok) + certSigner, err := ssh.NewCertSigner(cert, sshSigner) + if err != nil { + t.Fatal(err) + } - // create the request - req = NewRequest(t, "GET", "/api/v1/admin/users") + // create the request + req = NewRequest(t, "GET", "/api/v1/admin/users") - // add our cert to the request - certString := base64.RawStdEncoding.EncodeToString(cert.Marshal()) - req.SetHeader("x-ssh-certificate", certString) + // add our cert to the request + certString := base64.RawStdEncoding.EncodeToString(cert.Marshal()) + req.SetHeader("x-ssh-certificate", certString) - signer, _, err := httpsig.NewSSHSigner(certSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)", "x-ssh-certificate"}, httpsig.Signature, 10) - if err != nil { - t.Fatal(err) - } + signer, _, err := httpsig.NewSSHSigner(certSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)", "x-ssh-certificate"}, httpsig.Signature, 10) + if err != nil { + t.Fatal(err) + } - // sign the request - err = signer.SignRequest(keyID, req.Request, nil) - if err != nil { - t.Fatal(err) - } + // sign the request + err = signer.SignRequest(keyID, req.Request, nil) + if err != nil { + t.Fatal(err) + } - // make the request - MakeRequest(t, req, http.StatusOK) + // make the request + MakeRequest(t, req, http.StatusOK) + }) } diff --git a/tests/integration/api_issue_attachment_test.go b/tests/integration/api_issue_attachment_test.go index 9656946eefc..77d39894d99 100644 --- a/tests/integration/api_issue_attachment_test.go +++ b/tests/integration/api_issue_attachment_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIGetIssueAttachment(t *testing.T) { +func TestAPIIssueAttachment(t *testing.T) { defer tests.PrepareTestEnv(t)() attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) @@ -30,38 +30,60 @@ func TestAPIGetIssueAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID)). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + for _, operation := range []string{"get", "list", "edit-unallowed", "edit"} { + t.Run(operation, func(t *testing.T) { + token := readToken + if operation == "edit-unallowed" || operation == "edit" { + token = writeToken + } - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) + switch operation { + case "get": + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID)). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - commentAttachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 3, RepoID: repo.ID}) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: commentAttachment.IssueID}).Index, commentAttachment.ID)). - AddTokenAuth(token) - session.MakeRequest(t, req, http.StatusNotFound) -} + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) -func TestAPIListIssueAttachments(t *testing.T) { - defer tests.PrepareTestEnv(t)() + commentAttachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 3, RepoID: repo.ID}) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: commentAttachment.IssueID}).Index, commentAttachment.ID)). + AddTokenAuth(token) + session.MakeRequest(t, req, http.StatusNotFound) + case "list": + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index)). + AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusOK) + apiAttachment := DecodeJSON(t, resp, []api.Attachment{}) - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment[0].ID, IssueID: issue.ID}) + case "edit-unallowed": + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: attachment.IssueID}) + filename := "file.bad" + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "name": filename, + }).AddTokenAuth(token) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + case "edit": + const newAttachmentName = "hello_world.txt" - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index)). - AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusOK) - apiAttachment := DecodeJSON(t, resp, []api.Attachment{}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", + repoOwner.Name, repo.Name, issue.Index, attachment.ID) + req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ + "name": newAttachmentName, + }).AddTokenAuth(token) + resp := session.MakeRequest(t, req, http.StatusCreated) + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment[0].ID, IssueID: issue.ID}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID, Name: apiAttachment.Name}) + } + }) + } } func TestAPICreateIssueAttachment(t *testing.T) { @@ -74,95 +96,38 @@ func TestAPICreateIssueAttachment(t *testing.T) { session := loginUser(t, repoOwner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - body := &bytes.Buffer{} + for _, testCase := range []struct { + filename string + status int + }{ + {"file.bad", http.StatusUnprocessableEntity}, + {"image.png", http.StatusCreated}, + } { + t.Run(testCase.filename, func(t *testing.T) { + body := &bytes.Buffer{} - // Setup multi-part - writer := multipart.NewWriter(body) - part, err := writer.CreateFormFile("attachment", "image.png") - assert.NoError(t, err) - _, err = part.Write(testGeneratePngBytes()) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) + // Setup multi-part + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("attachment", testCase.filename) + assert.NoError(t, err) + if testCase.status == http.StatusCreated { + _, err = part.Write(testGeneratePngBytes()) + assert.NoError(t, err) + } + err = writer.Close() + assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). - AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) - resp := session.MakeRequest(t, req, http.StatusCreated) + req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). + AddTokenAuth(token) + req.Header.Add("Content-Type", writer.FormDataContentType()) + resp := session.MakeRequest(t, req, testCase.status) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) -} - -func TestAPICreateIssueAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - body := &bytes.Buffer{} - - // Setup multi-part. - writer := multipart.NewWriter(body) - _, err := writer.CreateFormFile("attachment", filename) - assert.NoError(t, err) - err = writer.Close() - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body). - AddTokenAuth(token) - req.Header.Add("Content-Type", writer.FormDataContentType()) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) -} - -func TestAPIEditIssueAttachment(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - const newAttachmentName = "hello_world.txt" - - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", - repoOwner.Name, repo.Name, issue.Index, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": newAttachmentName, - }).AddTokenAuth(token) - resp := session.MakeRequest(t, req, http.StatusCreated) - apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) - - unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID, Name: apiAttachment.Name}) -} - -func TestAPIEditIssueAttachmentWithUnallowedFile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: attachment.IssueID}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - filename := "file.bad" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d", repoOwner.Name, repo.Name, issue.Index, attachment.ID) - req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ - "name": filename, - }).AddTokenAuth(token) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) + if testCase.status == http.StatusCreated { + apiAttachment := DecodeJSON(t, resp, &api.Attachment{}) + unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID}) + } + }) + } } func TestAPIDeleteIssueAttachment(t *testing.T) { diff --git a/tests/integration/api_issue_config_test.go b/tests/integration/api_issue_config_test.go index 88dcd539203..426b7ef697b 100644 --- a/tests/integration/api_issue_config_test.go +++ b/tests/integration/api_issue_config_test.go @@ -50,6 +50,51 @@ func TestAPIRepoGetIssueConfig(t *testing.T) { assert.Empty(t, issueConfig.ContactLinks) }) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) + + t.Run("Validate/Valid", func(t *testing.T) { + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + issueConfigValidation := DecodeJSON(t, resp, &api.IssueConfigValidation{}) + + assert.True(t, issueConfigValidation.Valid) + assert.Empty(t, issueConfigValidation.Message) + }) + + t.Run("Paths", func(t *testing.T) { + templateConfigCandidates := []string{ + ".gitea/ISSUE_TEMPLATE/config", + ".gitea/issue_template/config", + ".github/ISSUE_TEMPLATE/config", + ".github/issue_template/config", + } + + for _, candidate := range templateConfigCandidates { + for _, extension := range []string{".yaml", ".yml"} { + fullPath := candidate + extension + t.Run(fullPath, func(t *testing.T) { + configMap := make(map[string]any) + configMap["blank_issues_enabled"] = false + + configData, err := yaml.Marshal(configMap) + assert.NoError(t, err) + + _, err = createFile(owner, repo, fullPath, string(configData)) + assert.NoError(t, err) + + issueConfig := getIssueConfig(t, owner.Name, repo.Name) + + assert.False(t, issueConfig.BlankIssuesEnabled) + assert.Empty(t, issueConfig.ContactLinks) + + _, err = deleteFileInBranch(owner, repo, fullPath, repo.DefaultBranch) + assert.NoError(t, err) + }) + } + } + }) + t.Run("DisableBlankIssues", func(t *testing.T) { config := make(map[string]any) config["blank_issues_enabled"] = false @@ -62,107 +107,37 @@ func TestAPIRepoGetIssueConfig(t *testing.T) { assert.Empty(t, issueConfig.ContactLinks) }) - t.Run("ContactLinks", func(t *testing.T) { - contactLink := make(map[string]string) - contactLink["name"] = "TestName" - contactLink["url"] = "https://example.com" - contactLink["about"] = "TestAbout" + for _, name := range []string{"ContactLinks", "Full"} { + t.Run(name, func(t *testing.T) { + contactLink := make(map[string]string) + contactLink["name"] = "TestName" + contactLink["url"] = "https://example.com" + contactLink["about"] = "TestAbout" - config := make(map[string]any) - config["contact_links"] = []map[string]string{contactLink} + config := make(map[string]any) + if name == "Full" { + config["blank_issues_enabled"] = false + } + config["contact_links"] = []map[string]string{contactLink} - createIssueConfig(t, owner, repo, config) + createIssueConfig(t, owner, repo, config) - issueConfig := getIssueConfig(t, owner.Name, repo.Name) - - assert.True(t, issueConfig.BlankIssuesEnabled) - assert.Len(t, issueConfig.ContactLinks, 1) - - assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) - assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) - assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) - }) - - t.Run("Full", func(t *testing.T) { - contactLink := make(map[string]string) - contactLink["name"] = "TestName" - contactLink["url"] = "https://example.com" - contactLink["about"] = "TestAbout" - - config := make(map[string]any) - config["blank_issues_enabled"] = false - config["contact_links"] = []map[string]string{contactLink} - - createIssueConfig(t, owner, repo, config) - - issueConfig := getIssueConfig(t, owner.Name, repo.Name) - - assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Len(t, issueConfig.ContactLinks, 1) - - assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) - assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) - assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) - }) -} - -func TestAPIRepoIssueConfigPaths(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - templateConfigCandidates := []string{ - ".gitea/ISSUE_TEMPLATE/config", - ".gitea/issue_template/config", - ".github/ISSUE_TEMPLATE/config", - ".github/issue_template/config", - } - - for _, candidate := range templateConfigCandidates { - for _, extension := range []string{".yaml", ".yml"} { - fullPath := candidate + extension - t.Run(fullPath, func(t *testing.T) { - configMap := make(map[string]any) - configMap["blank_issues_enabled"] = false - - configData, err := yaml.Marshal(configMap) - assert.NoError(t, err) - - _, err = createFile(owner, repo, fullPath, string(configData)) - assert.NoError(t, err) - - issueConfig := getIssueConfig(t, owner.Name, repo.Name) + issueConfig := getIssueConfig(t, owner.Name, repo.Name) + if name == "Full" { assert.False(t, issueConfig.BlankIssuesEnabled) - assert.Empty(t, issueConfig.ContactLinks) + } else { + assert.True(t, issueConfig.BlankIssuesEnabled) + } + assert.Len(t, issueConfig.ContactLinks, 1) - _, err = deleteFileInBranch(owner, repo, fullPath, repo.DefaultBranch) - assert.NoError(t, err) - }) - } + assert.Equal(t, "TestName", issueConfig.ContactLinks[0].Name) + assert.Equal(t, "https://example.com", issueConfig.ContactLinks[0].URL) + assert.Equal(t, "TestAbout", issueConfig.ContactLinks[0].About) + }) } -} -func TestAPIRepoValidateIssueConfig(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 49}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issue_config/validate", owner.Name, repo.Name) - - t.Run("Valid", func(t *testing.T) { - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - issueConfigValidation := DecodeJSON(t, resp, &api.IssueConfigValidation{}) - - assert.True(t, issueConfigValidation.Valid) - assert.Empty(t, issueConfigValidation.Message) - }) - - t.Run("Invalid", func(t *testing.T) { + t.Run("Validate/Invalid", func(t *testing.T) { config := make(map[string]any) config["blank_issues_enabled"] = "Test" @@ -176,20 +151,18 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) { assert.False(t, issueConfigValidation.Valid) assert.NotEmpty(t, issueConfigValidation.Message) }) -} - -func TestAPIRepoIssueConfigRequiresCodeUnit(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) - - for _, path := range []string{ - fmt.Sprintf("/api/v1/repos/%s/issue_config", repo.FullName()), - fmt.Sprintf("/api/v1/repos/%s/issue_config/validate", repo.FullName()), - } { - req := NewRequest(t, "GET", path).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - } + + t.Run("RequiresCodeUnit", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) + + for _, path := range []string{ + fmt.Sprintf("/api/v1/repos/%s/issue_config", repo.FullName()), + fmt.Sprintf("/api/v1/repos/%s/issue_config/validate", repo.FullName()), + } { + req := NewRequest(t, "GET", path).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + } + }) } diff --git a/tests/integration/api_issue_dependency_test.go b/tests/integration/api_issue_dependency_test.go index 5685c217baf..64b9bf5e490 100644 --- a/tests/integration/api_issue_dependency_test.go +++ b/tests/integration/api_issue_dependency_test.go @@ -37,155 +37,105 @@ func enableRepoDependencies(t *testing.T, repoID int64) { func TestAPICreateIssueDependencyCrossRepoPermission(t *testing.T) { defer tests.PrepareTestEnv(t)() - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) - dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, dependencyRepo.IsPrivate) - dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) + for _, method := range []string{"POST", "DELETE"} { + t.Run(method, func(t *testing.T) { + if method == "DELETE" { + assert.NoError(t, unittest.LoadFixtures()) + } - enableRepoDependencies(t, targetIssue.RepoID) - enableRepoDependencies(t, dependencyRepo.ID) + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) + dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, dependencyRepo.IsPrivate) + dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - // remove user 40 access from target repository - _, err := db.DeleteByID[access_model.Access](t.Context(), 30) - assert.NoError(t, err) + enableRepoDependencies(t, targetIssue.RepoID) + enableRepoDependencies(t, dependencyRepo.ID) - url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) - dependencyMeta := &api.IssueMeta{ - Owner: "org3", - Name: "repo3", - Index: dependencyIssue.Index, + if method == "DELETE" { + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) + } + + // remove user 40 access from target repository + _, err := db.DeleteByID[access_model.Access](t.Context(), 30) + assert.NoError(t, err) + + url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) + dependencyMeta := &api.IssueMeta{ + Owner: "org3", + Name: "repo3", + Index: dependencyIssue.Index, + } + + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + // user40 has no access to both target issue and dependency issue + writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) + checkDependency := func(expectedStatus int, exists bool) { + req := NewRequestWithJSON(t, method, url, dependencyMeta). + AddTokenAuth(writerToken) + MakeRequest(t, req, expectedStatus) + if exists { + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + } else { + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ + IssueID: targetIssue.ID, + DependencyID: dependencyIssue.ID, + }) + } + } + checkDependency(http.StatusNotFound, method == "DELETE") + + // add user40 as a collaborator to dependency repository with read permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + + // try again after getting read permission to dependency repository + checkDependency(http.StatusNotFound, method == "DELETE") + + // add user40 as a collaborator to target repository with write permission + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + + checkDependency(http.StatusCreated, method == "POST") + }) } - user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - // user40 has no access to both target issue and dependency issue - writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) - req := NewRequestWithJSON(t, "POST", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) + t.Run("WebDelete", func(t *testing.T) { + assert.NoError(t, unittest.LoadFixtures()) - // add user40 as a collaborator to dependency repository with read permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) + dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, dependencyRepo.IsPrivate) + dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - // try again after getting read permission to dependency repository - req = NewRequestWithJSON(t, "POST", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) + enableRepoDependencies(t, targetIssue.RepoID) + enableRepoDependencies(t, dependencyRepo.ID) - // add user40 as a collaborator to target repository with write permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) - req = NewRequestWithJSON(t, "POST", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusCreated) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, + user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) + + session := loginUser(t, user40.Name) + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ + "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), + "dependencyType": "blockedBy", + }) + resp := session.MakeRequest(t, req, http.StatusBadRequest) + assert.Equal(t, "Permission denied.", test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) + + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) + + req = NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ + "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), + "dependencyType": "blockedBy", + }) + session.MakeRequest(t, req, http.StatusOK) + unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) }) } - -func TestAPIDeleteIssueDependencyCrossRepoPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) - dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, dependencyRepo.IsPrivate) - dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - - enableRepoDependencies(t, targetIssue.RepoID) - enableRepoDependencies(t, dependencyRepo.ID) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) - - // remove user 40 access from target repository - _, err := db.DeleteByID[access_model.Access](t.Context(), 30) - assert.NoError(t, err) - - url := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/dependencies", "user2", "repo1", targetIssue.Index) - dependencyMeta := &api.IssueMeta{ - Owner: "org3", - Name: "repo3", - Index: dependencyIssue.Index, - } - - user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - // user40 has no access to both target issue and dependency issue - writerToken := getUserToken(t, "user40", auth_model.AccessTokenScopeWriteIssue) - req := NewRequestWithJSON(t, "DELETE", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) - - // add user40 as a collaborator to dependency repository with read permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) - - // try again after getting read permission to dependency repository - req = NewRequestWithJSON(t, "DELETE", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusNotFound) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) - - // add user40 as a collaborator to target repository with write permission - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) - - req = NewRequestWithJSON(t, "DELETE", url, dependencyMeta). - AddTokenAuth(writerToken) - MakeRequest(t, req, http.StatusCreated) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{ - IssueID: targetIssue.ID, - DependencyID: dependencyIssue.ID, - }) -} - -func TestWebDeleteIssueDependencyCrossRepoPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - targetIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: targetRepo.ID, Index: 1}) - dependencyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, dependencyRepo.IsPrivate) - dependencyIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: dependencyRepo.ID, Index: 1}) - - enableRepoDependencies(t, targetIssue.RepoID) - enableRepoDependencies(t, dependencyRepo.ID) - - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.NoError(t, issues_model.CreateIssueDependency(t.Context(), user1, targetIssue, dependencyIssue)) - - user40 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user40, perm.AccessModeWrite)) - - session := loginUser(t, user40.Name) - req := NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ - "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), - "dependencyType": "blockedBy", - }) - resp := session.MakeRequest(t, req, http.StatusBadRequest) - assert.Equal(t, "Permission denied.", test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) - - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), dependencyRepo, user40, perm.AccessModeRead)) - - req = NewRequestWithValues(t, "POST", fmt.Sprintf("/user2/repo1/issues/%d/dependency/delete", targetIssue.Index), map[string]string{ - "removeDependencyID": strconv.FormatInt(dependencyIssue.ID, 10), - "dependencyType": "blockedBy", - }) - session.MakeRequest(t, req, http.StatusOK) - unittest.AssertNotExistsBean(t, &issues_model.IssueDependency{IssueID: targetIssue.ID, DependencyID: dependencyIssue.ID}) -} diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index 35a2873ce78..1ced709204f 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -22,71 +22,121 @@ import ( func TestAPIModifyLabels(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner.Name, repo.Name) + t.Run("AddIssueLabelsWithLabelNames", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6, RepoID: repo.ID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: repo.ID}) + orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4, OrgID: owner.ID}) - // CreateLabel - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 1", - Color: "abcdef", - Description: "test label", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiLabel := DecodeJSON(t, resp, &api.Label{}) - dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, RepoID: repo.ID}) - assert.Equal(t, dbLabel.Name, apiLabel.Name) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + user1Session := loginUser(t, "user1") + token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 2", - Color: "#123456", - Description: "jet another test label", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "WrongTestL", - Color: "#12345g", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // add the org label and the repo label to the issue + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) + req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ + Labels: []any{repoLabel.Name, orgLabel.Name}, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + apiLabels := DecodeJSON(t, resp, []*api.Label{}) + assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID})) + var apiLabelNames []string + for _, label := range apiLabels { + apiLabelNames = append(apiLabelNames, label.Name) + } + assert.ElementsMatch(t, apiLabelNames, []string{repoLabel.Name, orgLabel.Name}) - // ListLabels - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabels := DecodeJSON(t, resp, []*api.Label{}) - assert.Len(t, apiLabels, 2) + // delete labels + req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) - // GetLabel - singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner.Name, repo.Name, dbLabel.ID) - req = NewRequest(t, "GET", singleURLStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + for _, testCase := range []struct { + name string + repoID int64 + labelCount int + }{ + {"Repository", 2, 2}, + {"Organization", 3, 4}, + } { + t.Run(testCase.name, func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: testCase.repoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + username := owner.Name + scopes := []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteIssue} + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner.Name, repo.Name) + labelFilter := &issues_model.Label{RepoID: repo.ID} + if testCase.name == "Organization" { + username = "user1" + scopes = []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization} + urlStr = fmt.Sprintf("/api/v1/orgs/%s/labels", owner.Name) + labelFilter = &issues_model.Label{OrgID: owner.ID} + } + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session, scopes...) - // EditLabel - newName := "LabelNewName" - newColor := "09876a" - newColorWrong := "09g76a" - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Name: &newName, - Color: &newColor, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, newColor, apiLabel.Color) - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Color: &newColorWrong, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // CreateLabel + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 1", + Color: "abcdef", + Description: "test label", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + apiLabel := DecodeJSON(t, resp, &api.Label{}) + labelFilter.ID = apiLabel.ID + dbLabel := unittest.AssertExistsAndLoadBean(t, labelFilter) + assert.Equal(t, dbLabel.Name, apiLabel.Name) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) - // DeleteLabel - req = NewRequest(t, "DELETE", singleURLStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "TestL 2", + Color: "#123456", + Description: "jet another test label", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusCreated) + req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ + Name: "WrongTestL", + Color: "#12345g", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // ListLabels + req = NewRequest(t, "GET", urlStr). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiLabels := DecodeJSON(t, resp, []*api.Label{}) + assert.Len(t, apiLabels, testCase.labelCount) + + // GetLabel + singleURLStr := fmt.Sprintf("%s/%d", urlStr, dbLabel.ID) + req = NewRequest(t, "GET", singleURLStr). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiLabel = DecodeJSON(t, resp, &api.Label{}) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + + // EditLabel + newName := "LabelNewName" + newColor := "09876a" + newColorWrong := "09g76a" + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Name: &newName, + Color: &newColor, + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiLabel = DecodeJSON(t, resp, &api.Label{}) + assert.Equal(t, newColor, apiLabel.Color) + req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ + Color: &newColorWrong, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // DeleteLabel + req = NewRequest(t, "DELETE", singleURLStr). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) + } } func TestAPIAddIssueLabels(t *testing.T) { @@ -144,37 +194,6 @@ func TestAPIDeleteIssueLabelCrossRepo(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) } -func TestAPIAddIssueLabelsWithLabelNames(t *testing.T) { - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6, RepoID: repo.ID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - repoLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 10, RepoID: repo.ID}) - orgLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 4, OrgID: owner.ID}) - - user1Session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteIssue) - - // add the org label and the repo label to the issue - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner.Name, repo.Name, issue.Index) - req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ - Labels: []any{repoLabel.Name, orgLabel.Name}, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiLabels := DecodeJSON(t, resp, []*api.Label{}) - assert.Len(t, apiLabels, unittest.GetCount(t, &issues_model.IssueLabel{IssueID: issue.ID})) - var apiLabelNames []string - for _, label := range apiLabels { - apiLabelNames = append(apiLabelNames, label.Name) - } - assert.ElementsMatch(t, apiLabelNames, []string{repoLabel.Name, orgLabel.Name}) - - // delete labels - req = NewRequest(t, "DELETE", urlStr).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} - func TestAPIAddIssueLabelsWithSharedRepoAndOrgLabelName(t *testing.T) { assert.NoError(t, unittest.LoadFixtures()) @@ -255,74 +274,3 @@ func TestAPIReplaceIssueLabelsWithLabelNames(t *testing.T) { assert.Equal(t, label.Name, apiLabels[0].Name) } } - -func TestAPIModifyOrgLabels(t *testing.T) { - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - user := "user1" - session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) - urlStr := fmt.Sprintf("/api/v1/orgs/%s/labels", owner.Name) - - // CreateLabel - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 1", - Color: "abcdef", - Description: "test label", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiLabel := DecodeJSON(t, resp, &api.Label{}) - dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, OrgID: owner.ID}) - assert.Equal(t, dbLabel.Name, apiLabel.Name) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) - - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "TestL 2", - Color: "#123456", - Description: "jet another test label", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ - Name: "WrongTestL", - Color: "#12345g", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - // ListLabels - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabels := DecodeJSON(t, resp, []*api.Label{}) - assert.Len(t, apiLabels, 4) - - // GetLabel - singleURLStr := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", owner.Name, dbLabel.ID) - req = NewRequest(t, "GET", singleURLStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) - - // EditLabel - newName := "LabelNewName" - newColor := "09876a" - newColorWrong := "09g76a" - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Name: &newName, - Color: &newColor, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiLabel = DecodeJSON(t, resp, &api.Label{}) - assert.Equal(t, newColor, apiLabel.Color) - req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ - Color: &newColorWrong, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - // DeleteLabel - req = NewRequest(t, "DELETE", singleURLStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} diff --git a/tests/integration/api_issue_pin_test.go b/tests/integration/api_issue_pin_test.go index 3b10ee4087b..b25eb0d0355 100644 --- a/tests/integration/api_issue_pin_test.go +++ b/tests/integration/api_issue_pin_test.go @@ -19,34 +19,31 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIPinIssue(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Check if the Issue is pinned - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d", repo.OwnerName, repo.Name, issue.Index)) - resp := MakeRequest(t, req, http.StatusOK) - issueAPI := DecodeJSON(t, resp, &api.Issue{}) - assert.Equal(t, 1, issueAPI.PinOrder) -} - func TestAPIUnpinIssue(t *testing.T) { defer tests.PrepareTestEnv(t)() - assert.NoError(t, unittest.LoadFixtures()) + t.Run("ListPinnedPullRequests", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/pinned", repo.OwnerName, repo.Name)) + resp := MakeRequest(t, req, http.StatusOK) + prList := DecodeJSON(t, resp, []api.PullRequest{}) + + assert.Empty(t, prList) + }) + + t.Run("NewPinAllowed", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/new_pin_allowed", owner.Name, repo.Name)) + resp := MakeRequest(t, req, http.StatusOK) + + newPinsAllowed := DecodeJSON(t, resp, &api.NewIssuePinsAllowed{}) + + assert.True(t, newPinsAllowed.Issues) + assert.True(t, newPinsAllowed.PullRequests) + }) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) @@ -66,6 +63,14 @@ func TestAPIUnpinIssue(t *testing.T) { issueAPI := DecodeJSON(t, resp, &api.Issue{}) assert.Equal(t, 1, issueAPI.PinOrder) + // Check if the Issue is in the List + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/pinned", repo.OwnerName, repo.Name)) + resp = MakeRequest(t, req, http.StatusOK) + issueList := DecodeJSON(t, resp, []api.Issue{}) + + assert.Len(t, issueList, 1) + assert.Equal(t, issue.ID, issueList[0].ID) + // Unpin the Issue req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). AddTokenAuth(token) @@ -81,8 +86,6 @@ func TestAPIUnpinIssue(t *testing.T) { func TestAPIMoveIssuePin(t *testing.T) { defer tests.PrepareTestEnv(t)() - assert.NoError(t, unittest.LoadFixtures()) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2, RepoID: repo.ID}) @@ -124,58 +127,3 @@ func TestAPIMoveIssuePin(t *testing.T) { issueAPI4 := DecodeJSON(t, resp, &api.Issue{}) assert.Equal(t, 1, issueAPI4.PinOrder) } - -func TestAPIListPinnedIssues(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - // Pin the Issue - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", repo.OwnerName, repo.Name, issue.Index)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Check if the Issue is in the List - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/issues/pinned", repo.OwnerName, repo.Name)) - resp := MakeRequest(t, req, http.StatusOK) - issueList := DecodeJSON(t, resp, []api.Issue{}) - - assert.Len(t, issueList, 1) - assert.Equal(t, issue.ID, issueList[0].ID) -} - -func TestAPIListPinnedPullrequests(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - assert.NoError(t, unittest.LoadFixtures()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/pinned", repo.OwnerName, repo.Name)) - resp := MakeRequest(t, req, http.StatusOK) - prList := DecodeJSON(t, resp, []api.PullRequest{}) - - assert.Empty(t, prList) -} - -func TestAPINewPinAllowed(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/new_pin_allowed", owner.Name, repo.Name)) - resp := MakeRequest(t, req, http.StatusOK) - - newPinsAllowed := DecodeJSON(t, resp, &api.NewIssuePinsAllowed{}) - - assert.True(t, newPinsAllowed.Issues) - assert.True(t, newPinsAllowed.PullRequests) -} diff --git a/tests/integration/api_issue_reaction_test.go b/tests/integration/api_issue_reaction_test.go index daa7bb4d17b..931881af063 100644 --- a/tests/integration/api_issue_reaction_test.go +++ b/tests/integration/api_issue_reaction_test.go @@ -31,143 +31,140 @@ func TestAPIIssuesReactions(t *testing.T) { session := loginUser(t, owner.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner.Name, issue.Repo.Name, issue.Index) + t.Run("Issue", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner.Name, issue.Repo.Name, issue.Index) - // Try to add not allowed reaction - req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "wrong", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) + // Try to add not allowed reaction + req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "wrong", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) - // Delete not allowed reaction - req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ - Reaction: "zzz", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + // Delete not allowed reaction + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "zzz", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) - // Add allowed reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "rocket", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) + // Add allowed reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) - // Add existing reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "rocket", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + // Add existing reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - // Blocked user can't react to comment - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "rocket", - }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteIssue)) - MakeRequest(t, req, http.StatusForbidden) + // Blocked user can't react to comment + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "rocket", + }).AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteIssue)) + MakeRequest(t, req, http.StatusForbidden) - // Get end result of reaction list of issue #1 - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) - expectResponse := make(map[int]api.Reaction) - expectResponse[0] = api.Reaction{ - User: convert.ToUser(t.Context(), user2, user2), - Reaction: "eyes", - Created: time.Unix(1573248003, 0), - } - expectResponse[1] = *apiNewReaction - assert.Len(t, apiReactions, 2) - for i, r := range apiReactions { - assert.Equal(t, expectResponse[i].Reaction, r.Reaction) - assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) - assert.Equal(t, expectResponse[i].User.ID, r.User.ID) - } -} + // Get end result of reaction list of issue #1 + req = NewRequest(t, "GET", urlStr). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) + expectResponse := make(map[int]api.Reaction) + expectResponse[0] = api.Reaction{ + User: convert.ToUser(t.Context(), user2, user2), + Reaction: "eyes", + Created: time.Unix(1573248003, 0), + } + expectResponse[1] = *apiNewReaction + assert.Len(t, apiReactions, 2) + for i, r := range apiReactions { + assert.Equal(t, expectResponse[i].Reaction, r.Reaction) + assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) + assert.Equal(t, expectResponse[i].User.ID, r.User.ID) + } + }) -func TestAPICommentReactions(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("Comment", func(t *testing.T) { + comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) + _ = comment.LoadIssue(t.Context()) + issue := comment.Issue + _ = issue.LoadRepo(t.Context()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2}) - _ = comment.LoadIssue(t.Context()) - issue := comment.Issue - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", owner.Name, issue.Repo.Name, comment.ID) - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + // Try to add not allowed reaction + req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "wrong", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", owner.Name, issue.Repo.Name, comment.ID) + // Delete none existing reaction + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "eyes", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) - // Try to add not allowed reaction - req := NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "wrong", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) + t.Run("UnrelatedCommentID", func(t *testing.T) { + // Using the ID of a comment that does not belong to the repository must fail + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", repoOwner.Name, repo.Name, comment.ID) + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ + Reaction: "+1", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + Reaction: "+1", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) - // Delete none existing reaction - req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ - Reaction: "eyes", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", urlStr). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) - t.Run("UnrelatedCommentID", func(t *testing.T) { - // Using the ID of a comment that does not belong to the repository must fail - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions", repoOwner.Name, repo.Name, comment.ID) + // Add allowed reaction req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ Reaction: "+1", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{ + resp := MakeRequest(t, req, http.StatusCreated) + apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) + + // Add existing reaction + req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ Reaction: "+1", }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + MakeRequest(t, req, http.StatusOK) + // Get end result of reaction list of issue #1 req = NewRequest(t, "GET", urlStr). AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + resp = MakeRequest(t, req, http.StatusOK) + apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) + expectResponse := make(map[int]api.Reaction) + expectResponse[0] = api.Reaction{ + User: convert.ToUser(t.Context(), user2, user2), + Reaction: "laugh", + Created: time.Unix(1573248004, 0), + } + expectResponse[1] = api.Reaction{ + User: convert.ToUser(t.Context(), user1, user1), + Reaction: "laugh", + Created: time.Unix(1573248005, 0), + } + expectResponse[2] = *apiNewReaction + assert.Len(t, apiReactions, 3) + for i, r := range apiReactions { + assert.Equal(t, expectResponse[i].Reaction, r.Reaction) + assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) + assert.Equal(t, expectResponse[i].User.ID, r.User.ID) + } }) - - // Add allowed reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "+1", - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiNewReaction := DecodeJSON(t, resp, &api.Reaction{}) - - // Add existing reaction - req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{ - Reaction: "+1", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - // Get end result of reaction list of issue #1 - req = NewRequest(t, "GET", urlStr). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiReactions := DecodeJSON(t, resp, []*api.Reaction{}) - expectResponse := make(map[int]api.Reaction) - expectResponse[0] = api.Reaction{ - User: convert.ToUser(t.Context(), user2, user2), - Reaction: "laugh", - Created: time.Unix(1573248004, 0), - } - expectResponse[1] = api.Reaction{ - User: convert.ToUser(t.Context(), user1, user1), - Reaction: "laugh", - Created: time.Unix(1573248005, 0), - } - expectResponse[2] = *apiNewReaction - assert.Len(t, apiReactions, 3) - for i, r := range apiReactions { - assert.Equal(t, expectResponse[i].Reaction, r.Reaction) - assert.Equal(t, expectResponse[i].Created.Unix(), r.Created.Unix()) - assert.Equal(t, expectResponse[i].User.ID, r.User.ID) - } } diff --git a/tests/integration/api_issue_stopwatch_test.go b/tests/integration/api_issue_stopwatch_test.go index 225c64e5942..134bbb52de2 100644 --- a/tests/integration/api_issue_stopwatch_test.go +++ b/tests/integration/api_issue_stopwatch_test.go @@ -40,55 +40,36 @@ func TestAPIListStopWatches(t *testing.T) { assert.Equal(t, repo.OwnerName, apiWatches[0].RepoOwnerName) assert.Positive(t, apiWatches[0].Seconds) } -} - -func TestAPIStopStopWatches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", owner.Name, issue.Repo.Name, issue.Index). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - MakeRequest(t, req, http.StatusConflict) -} - -func TestAPICancelStopWatches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete", owner.Name, issue.Repo.Name, issue.Index). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - MakeRequest(t, req, http.StatusConflict) -} - -func TestAPIStartStopWatches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) - _ = issue.LoadRepo(t.Context()) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - - req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start", owner.Name, issue.Repo.Name, issue.Index). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - MakeRequest(t, req, http.StatusConflict) + + for _, testCase := range []struct { + name string + issueID int64 + userID int64 + method string + urlFormat string + expectedCode int + }{ + {"stop", 2, 2, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", http.StatusCreated}, + {"cancel", 1, 1, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete", http.StatusNoContent}, + {"start", 3, 2, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start", http.StatusCreated}, + } { + t.Run(testCase.name, func(t *testing.T) { + if testCase.name == "start" { + defer tests.PrepareTestEnv(t)() + } + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: testCase.issueID}) + _ = issue.LoadRepo(t.Context()) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.userID}) + + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + + req := NewRequestf(t, testCase.method, testCase.urlFormat, owner.Name, issue.Repo.Name, issue.Index). + AddTokenAuth(token) + MakeRequest(t, req, testCase.expectedCode) + MakeRequest(t, req, http.StatusConflict) + }) + } } diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go index 34fe8c2a69e..75dea04a301 100644 --- a/tests/integration/api_issue_subscription_test.go +++ b/tests/integration/api_issue_subscription_test.go @@ -56,25 +56,19 @@ func TestAPIIssueSubscriptions(t *testing.T) { issue1Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue1.RepoID}) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s", issue1Repo.OwnerName, issue1Repo.Name, issue1.Index, owner.Name) - req := NewRequest(t, "DELETE", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - testSubscription(issue1, false) - - req = NewRequest(t, "DELETE", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - testSubscription(issue1, false) + for _, expectedCode := range []int{http.StatusCreated, http.StatusOK} { + req := NewRequest(t, "DELETE", urlStr). + AddTokenAuth(token) + MakeRequest(t, req, expectedCode) + testSubscription(issue1, false) + } issue5Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue5.RepoID}) urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/subscriptions/%s", issue5Repo.OwnerName, issue5Repo.Name, issue5.Index, owner.Name) - req = NewRequest(t, "PUT", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - testSubscription(issue5, true) - - req = NewRequest(t, "PUT", urlStr). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - testSubscription(issue5, true) + for _, expectedCode := range []int{http.StatusCreated, http.StatusOK} { + req := NewRequest(t, "PUT", urlStr). + AddTokenAuth(token) + MakeRequest(t, req, expectedCode) + testSubscription(issue5, true) + } } diff --git a/tests/integration/api_issue_templates_test.go b/tests/integration/api_issue_templates_test.go deleted file mode 100644 index c5326cd2bfd..00000000000 --- a/tests/integration/api_issue_templates_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "net/url" - "testing" - - auth_model "gitea.dev/models/auth" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIIssueTemplateList(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) - - // no issue template - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") - resp := MakeRequest(t, req, http.StatusOK) - issueTemplates := DecodeJSON(t, resp, []*api.IssueTemplate{}) - assert.Empty(t, issueTemplates) - - // one correct issue template and some incorrect issue templates - err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `---- -name: foo -about: bar ----- -`) - assert.NoError(t, err) - - err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`) - assert.NoError(t, err) - - err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `) - assert.NoError(t, err) - - req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") - resp = MakeRequest(t, req, http.StatusOK) - issueTemplates = DecodeJSON(t, resp, []*api.IssueTemplate{}) - assert.Len(t, issueTemplates, 1) - assert.Equal(t, "foo", issueTemplates[0].Name) - assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning")) - }) -} - -func TestAPIIssueTemplateRequiresCodeUnit(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) - issueTemplatesURL := "/api/v1/repos/" + repo.FullName() + "/issue_templates" - languagesURL := "/api/v1/repos/" + repo.FullName() + "/languages" - - req := NewRequest(t, "GET", issueTemplatesURL).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", languagesURL).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index 225d8104e78..c6ffef19d4e 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -27,6 +27,56 @@ import ( "github.com/stretchr/testify/assert" ) +func TestAPIIssueTemplateList(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"}) + + // no issue template + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") + resp := MakeRequest(t, req, http.StatusOK) + issueTemplates := DecodeJSON(t, resp, []*api.IssueTemplate{}) + assert.Empty(t, issueTemplates) + + // one correct issue template and some incorrect issue templates + err := createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-ok.md", repo.DefaultBranch, `---- +name: foo +about: bar +---- +`) + assert.NoError(t, err) + + err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err1.yml", repo.DefaultBranch, `name: '`) + assert.NoError(t, err) + + err = createOrReplaceFileInBranch(user, repo, ".gitea/ISSUE_TEMPLATE/tmpl-err2.yml", repo.DefaultBranch, `other: `) + assert.NoError(t, err) + + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/issue_templates") + resp = MakeRequest(t, req, http.StatusOK) + issueTemplates = DecodeJSON(t, resp, []*api.IssueTemplate{}) + assert.Len(t, issueTemplates, 1) + assert.Equal(t, "foo", issueTemplates[0].Name) + assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning")) + }) +} + +func TestAPIIssueTemplateRequiresCodeUnit(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) + issueTemplatesURL := "/api/v1/repos/" + repo.FullName() + "/issue_templates" + languagesURL := "/api/v1/repos/" + repo.FullName() + "/languages" + + req := NewRequest(t, "GET", issueTemplatesURL).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", languagesURL).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) +} + func TestAPIIssue(t *testing.T) { defer tests.PrepareTestEnv(t)() t.Run("ListIssues", testAPIListIssues) diff --git a/tests/integration/api_issue_tracked_time_test.go b/tests/integration/api_issue_tracked_time_test.go index 0da5548a052..7640e25d51c 100644 --- a/tests/integration/api_issue_tracked_time_test.go +++ b/tests/integration/api_issue_tracked_time_test.go @@ -26,105 +26,103 @@ import ( func TestAPIGetTrackedTimes(t *testing.T) { defer tests.PrepareTestEnv(t)() - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.NoError(t, issue2.LoadRepo(t.Context())) + t.Run("issue times", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(t.Context())) - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times", user2.Name, issue2.Repo.Name, issue2.Index). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) - expect, err := issues_model.GetTrackedTimes(t.Context(), &issues_model.FindTrackedTimesOptions{IssueID: issue2.ID}) - assert.NoError(t, err) - assert.Len(t, apiTimes, 3) - - for i, time := range expect { - assert.Equal(t, time.ID, apiTimes[i].ID) - assert.Equal(t, issue2.Title, apiTimes[i].Issue.Title) - assert.Equal(t, issue2.ID, apiTimes[i].IssueID) - assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) - assert.Equal(t, time.Time, apiTimes[i].Time) - user, err := user_model.GetUserByID(t.Context(), time.UserID) - assert.NoError(t, err) - assert.Equal(t, user.Name, apiTimes[i].UserName) - } - - // test filter - since := "2000-01-01T00%3A00%3A02%2B00%3A00" // 946684802 - before := "2000-01-01T00%3A00%3A12%2B00%3A00" // 946684812 - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?since=%s&before=%s", user2.Name, issue2.Repo.Name, issue2.Index, since, before). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - filterAPITimes := DecodeJSON(t, resp, api.TrackedTimeList{}) - assert.Len(t, filterAPITimes, 2) - assert.Equal(t, int64(3), filterAPITimes[0].ID) - assert.Equal(t, int64(6), filterAPITimes[1].ID) -} - -// TestAPIGetTrackedTimesNonExistentUserFilter ensures filtering by a user that -// does not exist returns a clean 404 instead of panicking (nil pointer dereference). -func TestAPIGetTrackedTimesNonExistentUserFilter(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.NoError(t, issue2.LoadRepo(t.Context())) - - session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadRepository) - - for _, tc := range []struct { - name string - url string - }{ - {"repository level", fmt.Sprintf("/api/v1/repos/%s/%s/times?user=nonexistentuser", user2.Name, issue2.Repo.Name)}, - {"issue level", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/times?user=nonexistentuser", user2.Name, issue2.Repo.Name, issue2.Index)}, - } { - t.Run(tc.name, func(t *testing.T) { - req := NewRequest(t, "GET", tc.url).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusNotFound) - - assert.True(t, json.Valid(resp.Body.Bytes()), "response body must be a single JSON value, got: %s", resp.Body.Bytes()) - - var apiError api.APIError - DecodeJSON(t, resp, &apiError) - assert.Contains(t, apiError.Message, "user does not exist") - }) - } - - t.Run("existing user", func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/times?user=%s", user2.Name, issue2.Repo.Name, user2.Name).AddTokenAuth(token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times", user2.Name, issue2.Repo.Name, issue2.Index). + AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, api.TrackedTimeList{}) - }) -} + apiTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) + expect, err := issues_model.GetTrackedTimes(t.Context(), &issues_model.FindTrackedTimesOptions{IssueID: issue2.ID}) + assert.NoError(t, err) + assert.Len(t, apiTimes, 3) -func TestAPIUserTrackedTimesOmitsInaccessiblePrivateIssues(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, privateRepo.IsPrivate) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: privateRepo.ID}) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) - _, err := issues_model.AddTime(t.Context(), user, issue, 60, time.Time{}) - assert.NoError(t, err) - assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) - req := NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - trackedTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) - for _, trackedTime := range trackedTimes { - if assert.NotNil(t, trackedTime.Issue) { - assert.NotEqual(t, issue.ID, trackedTime.Issue.ID) + for i, time := range expect { + assert.Equal(t, time.ID, apiTimes[i].ID) + assert.Equal(t, issue2.Title, apiTimes[i].Issue.Title) + assert.Equal(t, issue2.ID, apiTimes[i].IssueID) + assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) + assert.Equal(t, time.Time, apiTimes[i].Time) + user, err := user_model.GetUserByID(t.Context(), time.UserID) + assert.NoError(t, err) + assert.Equal(t, user.Name, apiTimes[i].UserName) } - } + + // test filter + since := "2000-01-01T00%3A00%3A02%2B00%3A00" // 946684802 + before := "2000-01-01T00%3A00%3A12%2B00%3A00" // 946684812 + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?since=%s&before=%s", user2.Name, issue2.Repo.Name, issue2.Index, since, before). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + filterAPITimes := DecodeJSON(t, resp, api.TrackedTimeList{}) + assert.Len(t, filterAPITimes, 2) + assert.Equal(t, int64(3), filterAPITimes[0].ID) + assert.Equal(t, int64(6), filterAPITimes[1].ID) + }) + + // TestAPIGetTrackedTimesNonExistentUserFilter ensures filtering by a user that + // does not exist returns a clean 404 instead of panicking (nil pointer dereference). + t.Run("nonexistent user filter", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) + assert.NoError(t, issue2.LoadRepo(t.Context())) + + session := loginUser(t, user2.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadRepository) + + for _, tc := range []struct { + name string + url string + }{ + {"repository level", fmt.Sprintf("/api/v1/repos/%s/%s/times?user=nonexistentuser", user2.Name, issue2.Repo.Name)}, + {"issue level", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/times?user=nonexistentuser", user2.Name, issue2.Repo.Name, issue2.Index)}, + } { + t.Run(tc.name, func(t *testing.T) { + req := NewRequest(t, "GET", tc.url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusNotFound) + + assert.True(t, json.Valid(resp.Body.Bytes()), "response body must be a single JSON value, got: %s", resp.Body.Bytes()) + + var apiError api.APIError + DecodeJSON(t, resp, &apiError) + assert.Contains(t, apiError.Message, "user does not exist") + }) + } + + t.Run("existing user", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/times?user=%s", user2.Name, issue2.Repo.Name, user2.Name).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, api.TrackedTimeList{}) + }) + }) + + t.Run("omit inaccessible private issues", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, privateRepo.IsPrivate) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: privateRepo.ID}) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) + _, err := issues_model.AddTime(t.Context(), user, issue, 60, time.Time{}) + assert.NoError(t, err) + assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) + + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser) + req := NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + trackedTimes := DecodeJSON(t, resp, api.TrackedTimeList{}) + for _, trackedTime := range trackedTimes { + if assert.NotNil(t, trackedTime.Issue) { + assert.NotEqual(t, issue.ID, trackedTime.Issue.ID) + } + } + }) } func TestAPIDeleteTrackedTime(t *testing.T) { diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go index 3da6294238f..0224a4a0ca0 100644 --- a/tests/integration/api_keys_test.go +++ b/tests/integration/api_keys_test.go @@ -24,91 +24,78 @@ import ( func TestViewDeployKeysNoLogin(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys") - MakeRequest(t, req, http.StatusUnauthorized) -} -func TestCreateDeployKeyNoLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/keys", api.CreateKeyOption{ - Title: "title", - Key: "key", - }) - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestGetDeployKeyNoLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys/1") - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestDeleteDeployKeyNoLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestCreateReadOnlyDeployKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) - rawKeyBody := api.CreateKeyOption{ - Title: "read-only", - Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - ReadOnly: true, - } - req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - - newDeployKey := DecodeJSON(t, resp, &api.DeployKey{}) - unittest.AssertExistsAndLoadBean(t, &deploykey_model.DeployKey{ - ID: newDeployKey.ID, - Name: rawKeyBody.Title, - Mode: perm.AccessModeRead, + t.Run("view", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys") + MakeRequest(t, req, http.StatusUnauthorized) }) - // Using the ID of a key that does not belong to the repository must fail - { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d", repoOwner.Name, repo.Name, newDeployKey.ID)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + t.Run("create", func(t *testing.T) { + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/keys", api.CreateKeyOption{ + Title: "title", + Key: "key", + }) + MakeRequest(t, req, http.StatusUnauthorized) + }) - session5 := loginUser(t, "user5") - token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d", newDeployKey.ID)). - AddTokenAuth(token5) - MakeRequest(t, req, http.StatusNotFound) - } + t.Run("get", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/keys/1") + MakeRequest(t, req, http.StatusUnauthorized) + }) + + t.Run("delete", func(t *testing.T) { + req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo1/keys/1") + MakeRequest(t, req, http.StatusUnauthorized) + }) } -func TestCreateReadWriteDeployKey(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) - repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) +func TestCreateDeployKey(t *testing.T) { + for _, testCase := range []struct { + name string + readOnly bool + mode perm.AccessMode + }{ + {"read-only", true, perm.AccessModeRead}, + {"read-write", false, perm.AccessModeWrite}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) + repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) - rawKeyBody := api.CreateKeyOption{ - Title: "read-write", - Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + session := loginUser(t, repoOwner.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys", repoOwner.Name, repo.Name) + rawKeyBody := api.CreateKeyOption{ + Title: testCase.name, + Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", + ReadOnly: testCase.readOnly, + } + req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + newDeployKey := DecodeJSON(t, resp, &api.DeployKey{}) + unittest.AssertExistsAndLoadBean(t, &deploykey_model.DeployKey{ + ID: newDeployKey.ID, + Name: rawKeyBody.Title, + Mode: testCase.mode, + }) + + // Using the ID of a key that does not belong to the repository must fail + if testCase.readOnly { + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d", repoOwner.Name, repo.Name, newDeployKey.ID)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + + session5 := loginUser(t, "user5") + token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d", newDeployKey.ID)). + AddTokenAuth(token5) + MakeRequest(t, req, http.StatusNotFound) + } + }) } - req := NewRequestWithJSON(t, "POST", keysURL, rawKeyBody). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - - newDeployKey := DecodeJSON(t, resp, &api.DeployKey{}) - unittest.AssertExistsAndLoadBean(t, &deploykey_model.DeployKey{ - ID: newDeployKey.ID, - Name: rawKeyBody.Title, - Mode: perm.AccessModeWrite, - }) } func TestCreateUserKey(t *testing.T) { @@ -139,30 +126,25 @@ func TestCreateUserKey(t *testing.T) { }) // Search by fingerprint - req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + for _, url := range []string{ + "/api/v1/user/keys?fingerprint=" + newPublicKey.Fingerprint, + fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint), + } { + req = NewRequest(t, "GET", url).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) - fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint)). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) + fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Equal(t, user.ID, fingerprintPublicKeys[0].Owner.ID) + } // Fail search by fingerprint req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/keys?fingerprint=%sA", newPublicKey.Fingerprint)). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) + fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) assert.Empty(t, fingerprintPublicKeys) // Fail searching for wrong users key @@ -178,24 +160,18 @@ func TestCreateUserKey(t *testing.T) { token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteUser) // Should find key even though not ours, but we shouldn't know whose it is - req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusOK) + for _, url := range []string{ + "/api/v1/user/keys?fingerprint=" + newPublicKey.Fingerprint, + fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint), + } { + req = NewRequest(t, "GET", url).AddTokenAuth(token2) + resp = MakeRequest(t, req, http.StatusOK) - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Nil(t, fingerprintPublicKeys[0].Owner) - - // Should find key even though not ours, but we shouldn't know whose it is - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", user.Name, newPublicKey.Fingerprint)). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusOK) - - fingerprintPublicKeys = DecodeJSON(t, resp, []api.PublicKey{}) - assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) - assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) - assert.Nil(t, fingerprintPublicKeys[0].Owner) + fingerprintPublicKeys := DecodeJSON(t, resp, []api.PublicKey{}) + assert.Equal(t, newPublicKey.Fingerprint, fingerprintPublicKeys[0].Fingerprint) + assert.Equal(t, newPublicKey.ID, fingerprintPublicKeys[0].ID) + assert.Nil(t, fingerprintPublicKeys[0].Owner) + } // Fail when searching for key if it is not ours req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/keys?fingerprint=%s", "user2", newPublicKey.Fingerprint)). diff --git a/tests/integration/api_label_templates_test.go b/tests/integration/api_label_templates_test.go index 66d4b864260..9427f219636 100644 --- a/tests/integration/api_label_templates_test.go +++ b/tests/integration/api_label_templates_test.go @@ -9,7 +9,9 @@ import ( "strings" "testing" + "gitea.dev/modules/options" repo_module "gitea.dev/modules/repository" + "gitea.dev/modules/setting" api "gitea.dev/modules/structs" "gitea.dev/tests" @@ -19,40 +21,75 @@ import ( func TestAPIListLabelTemplates(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/api/v1/label/templates") - resp := MakeRequest(t, req, http.StatusOK) + t.Run("list labels", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/label/templates") + resp := MakeRequest(t, req, http.StatusOK) - templateList := DecodeJSON(t, resp, []string{}) + templateList := DecodeJSON(t, resp, []string{}) - for i := range repo_module.LabelTemplateFiles { - assert.Equal(t, repo_module.LabelTemplateFiles[i].DisplayName, templateList[i]) - } -} - -func TestAPIGetLabelTemplateInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // If Gitea has for some reason no Label templates, we need to skip this test - if len(repo_module.LabelTemplateFiles) == 0 { - return - } - - // Use the first template for the test - templateName := repo_module.LabelTemplateFiles[0].DisplayName - - urlStr := "/api/v1/label/templates/" + url.PathEscape(templateName) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - templateInfo := DecodeJSON(t, resp, []api.LabelTemplate{}) - - labels, err := repo_module.LoadTemplateLabelsByDisplayName(templateName) - assert.NoError(t, err) - - for i := range labels { - assert.Equal(t, strings.TrimLeft(labels[i].Color, "#"), templateInfo[i].Color) - assert.Equal(t, labels[i].Description, templateInfo[i].Description) - assert.Equal(t, labels[i].Exclusive, templateInfo[i].Exclusive) - assert.Equal(t, labels[i].Name, templateInfo[i].Name) - } + for i := range repo_module.LabelTemplateFiles { + assert.Equal(t, repo_module.LabelTemplateFiles[i].DisplayName, templateList[i]) + } + }) + + t.Run("label info", func(t *testing.T) { + // If Gitea has for some reason no Label templates, we need to skip this test + if len(repo_module.LabelTemplateFiles) == 0 { + return + } + + // Use the first template for the test + templateName := repo_module.LabelTemplateFiles[0].DisplayName + + urlStr := "/api/v1/label/templates/" + url.PathEscape(templateName) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + templateInfo := DecodeJSON(t, resp, []api.LabelTemplate{}) + + labels, err := repo_module.LoadTemplateLabelsByDisplayName(templateName) + assert.NoError(t, err) + + for i := range labels { + assert.Equal(t, strings.TrimLeft(labels[i].Color, "#"), templateInfo[i].Color) + assert.Equal(t, labels[i].Description, templateInfo[i].Description) + assert.Equal(t, labels[i].Exclusive, templateInfo[i].Exclusive) + assert.Equal(t, labels[i].Name, templateInfo[i].Name) + } + }) + + t.Run("list licenses", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/licenses") + resp := MakeRequest(t, req, http.StatusOK) + + licenseList := DecodeJSON(t, resp, []api.LicensesTemplateListEntry{}) + assert.Contains(t, licenseList, api.LicensesTemplateListEntry{ + Key: "MIT", + Name: "MIT", + URL: setting.AppURL + "api/v1/licenses/MIT", + }) + }) + + t.Run("license info", func(t *testing.T) { + // If Gitea has for some reason no License templates, we need to skip this test + if len(repo_module.Licenses) == 0 { + return + } + + // Use the first template for the test + licenseName := repo_module.Licenses[0] + + urlStr := "/api/v1/licenses/" + url.PathEscape(licenseName) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + licenseInfo := DecodeJSON(t, resp, &api.LicenseTemplateInfo{}) + + // We get the text of the template here + text, _ := options.License(licenseName) + + assert.Equal(t, licenseInfo.Key, licenseName) + assert.Equal(t, licenseInfo.Name, licenseName) + assert.Equal(t, licenseInfo.Body, string(text)) + }) } diff --git a/tests/integration/api_license_templates_test.go b/tests/integration/api_license_templates_test.go deleted file mode 100644 index 2f3d3b08305..00000000000 --- a/tests/integration/api_license_templates_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "net/url" - "testing" - - "gitea.dev/modules/options" - repo_module "gitea.dev/modules/repository" - "gitea.dev/modules/setting" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIListLicenseTemplates(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/licenses") - resp := MakeRequest(t, req, http.StatusOK) - - licenseList := DecodeJSON(t, resp, []api.LicensesTemplateListEntry{}) - assert.Contains(t, licenseList, api.LicensesTemplateListEntry{ - Key: "MIT", - Name: "MIT", - URL: setting.AppURL + "api/v1/licenses/MIT", - }) -} - -func TestAPIGetLicenseTemplateInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // If Gitea has for some reason no License templates, we need to skip this test - if len(repo_module.Licenses) == 0 { - return - } - - // Use the first template for the test - licenseName := repo_module.Licenses[0] - - urlStr := "/api/v1/licenses/" + url.PathEscape(licenseName) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - licenseInfo := DecodeJSON(t, resp, &api.LicenseTemplateInfo{}) - - // We get the text of the template here - text, _ := options.License(licenseName) - - assert.Equal(t, licenseInfo.Key, licenseName) - assert.Equal(t, licenseInfo.Name, licenseName) - assert.Equal(t, licenseInfo.Body, string(text)) -} diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index 2a856d4da42..6c87046a63a 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -22,6 +22,24 @@ import ( func TestAPINotification(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("public only", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) + + token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/notifications"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", "/api/v1/notifications/new"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + }) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) @@ -209,23 +227,3 @@ func TestAPINotificationPUT(t *testing.T) { assert.True(t, apiNL[0].Unread) assert.False(t, apiNL[0].Pinned) } - -func TestAPINotificationPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) - - token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/notifications"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", "/api/v1/notifications/new"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) -} diff --git a/tests/integration/api_org_avatar_test.go b/tests/integration/api_org_avatar_test.go deleted file mode 100644 index 7727331dda9..00000000000 --- a/tests/integration/api_org_avatar_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "encoding/base64" - "net/http" - "os" - "path/filepath" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/modules/setting" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestAPIUpdateOrgAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - - // Test what happens if you use a valid image - avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) - assert.NoError(t, err) - if err != nil { - assert.FailNow(t, "Unable to open avatar.png") - } - - opts := api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString(avatar), - } - - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Test what happens if you don't have a valid Base64 string - opts = api.UpdateUserAvatarOption{ - Image: "Invalid", - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) - - // Test what happens if you use a file that is not an image - opts = api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")), - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusInternalServerError) -} - -func TestAPIDeleteOrgAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - - req := NewRequest(t, "DELETE", "/api/v1/orgs/org3/avatar"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) -} diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 5e62f6e0410..c4ebbf309a1 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -4,8 +4,11 @@ package integration import ( + "encoding/base64" "fmt" "net/http" + "os" + "path/filepath" "strings" "testing" "time" @@ -27,8 +30,127 @@ import ( "github.com/stretchr/testify/require" ) +func TestAPIUpdateOrgAvatar(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + + // Test what happens if you use a valid image + avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) + assert.NoError(t, err) + if err != nil { + assert.FailNow(t, "Unable to open avatar.png") + } + + for _, testCase := range []struct { + image string + status int + }{ + {base64.StdEncoding.EncodeToString(avatar), http.StatusNoContent}, + // Test what happens if you don't have a valid Base64 string + {"Invalid", http.StatusBadRequest}, + // Test what happens if you use a file that is not an image + {base64.StdEncoding.EncodeToString([]byte("This is not an image")), http.StatusInternalServerError}, + } { + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/avatar", &api.UpdateUserAvatarOption{ + Image: testCase.image, + }).AddTokenAuth(token) + MakeRequest(t, req, testCase.status) + } +} + +func TestAPIDeleteOrgAvatar(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user1") + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + + req := NewRequest(t, "DELETE", "/api/v1/orgs/org3/avatar"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) +} + func TestAPIOrg(t *testing.T) { defer tests.PrepareTestEnv(t)() + + // TestAPIOrgLabelsVisibility ensures the organization label read endpoints honor + // the organization visibility: labels of a private org must not be disclosed to + // users who cannot see the org (GHSA: unauthorized access to private org labels). + t.Run("LabelsVisibility", func(t *testing.T) { + // privated_org (id 23) is a private organization; user5 is its only member. + privateOrg := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 23}) + label := &issues_model.Label{OrgID: privateOrg.ID, Name: "internal-label", Color: "#aabbcc", Description: "private organization label"} + require.NoError(t, issues_model.NewLabel(t.Context(), label)) + + listURL := fmt.Sprintf("/api/v1/orgs/%s/labels", privateOrg.Name) + getURL := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", privateOrg.Name, label.ID) + + t.Run("NonMemberDenied", func(t *testing.T) { + // user2 is not a member of the private org and must not see its labels. + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) + MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusNotFound) + }) + + t.Run("AnonymousDenied", func(t *testing.T) { + MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound) + }) + + t.Run("MemberAllowed", func(t *testing.T) { + token := getUserToken(t, "user5", auth_model.AccessTokenScopeReadOrganization) + resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) + labels := DecodeJSON(t, resp, &[]*api.Label{}) + assert.Len(t, *labels, 1) + MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) + }) + + t.Run("SiteAdminAllowed", func(t *testing.T) { + token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) + MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) + MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) + }) + + t.Run("PublicOrgStillReadable", func(t *testing.T) { + // org3 (id 3) is a public org with labels; non-members may read them. + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) + MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/labels").AddTokenAuth(token), http.StatusOK) + }) + }) + + t.Run("PrivateMembersNotLeaked", func(t *testing.T) { + // privated_org (org 23) has private visibility and a single member, user5 + const orgName = "privated_org" + const memberName = "user5" + + // member publicizes their own membership inside the private org + memberSession := loginUser(t, memberName) + memberToken := getTokenForLoggedInUser(t, memberSession, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "PUT", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) + + // an outsider must not be able to learn about the membership of a private org + outsiderSession := loginUser(t, "user2") + outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeReadOrganization) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members").AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + // the full member list of a private org must not be enumerable by an outsider either + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(outsiderToken) + MakeRequest(t, req, http.StatusNotFound) + + // the member can still see the public membership of their own org + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusNoContent) + // and the member can still list the org's members + req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(memberToken) + MakeRequest(t, req, http.StatusOK) + }) + t.Run("General", testAPIOrgGeneral) t.Run("CreateAndRename", testAPIOrgCreateRename) t.Run("DeleteOrgRepos", testAPIDeleteOrgRepos) @@ -275,38 +397,6 @@ func testAPIOrgGeneral(t *testing.T) { }) } -func TestAPIOrgPrivateMembersNotLeaked(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // privated_org (org 23) has private visibility and a single member, user5 - const orgName = "privated_org" - const memberName = "user5" - - // member publicizes their own membership inside the private org - memberSession := loginUser(t, memberName) - memberToken := getTokenForLoggedInUser(t, memberSession, auth_model.AccessTokenScopeWriteOrganization) - req := NewRequest(t, "PUT", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) - MakeRequest(t, req, http.StatusNoContent) - - // an outsider must not be able to learn about the membership of a private org - outsiderSession := loginUser(t, "user2") - outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeReadOrganization) - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(outsiderToken) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members").AddTokenAuth(outsiderToken) - MakeRequest(t, req, http.StatusNotFound) - // the full member list of a private org must not be enumerable by an outsider either - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(outsiderToken) - MakeRequest(t, req, http.StatusNotFound) - - // the member can still see the public membership of their own org - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/public_members/"+memberName).AddTokenAuth(memberToken) - MakeRequest(t, req, http.StatusNoContent) - // and the member can still list the org's members - req = NewRequest(t, "GET", "/api/v1/orgs/"+orgName+"/members").AddTokenAuth(memberToken) - MakeRequest(t, req, http.StatusOK) -} - func testAPIDeleteOrgRepos(t *testing.T) { org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) orgRepos, err := repo_model.GetOrgRepositories(t.Context(), org3.ID) @@ -336,50 +426,3 @@ func testAPIDeleteOrgRepos(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) // The org contains no repositories, so the API should return StatusNoContent }) } - -// TestAPIOrgLabelsVisibility ensures the organization label read endpoints honor -// the organization visibility: labels of a private org must not be disclosed to -// users who cannot see the org (GHSA: unauthorized access to private org labels). -func TestAPIOrgLabelsVisibility(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // privated_org (id 23) is a private organization; user5 is its only member. - privateOrg := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 23}) - label := &issues_model.Label{OrgID: privateOrg.ID, Name: "internal-label", Color: "#aabbcc", Description: "private organization label"} - require.NoError(t, issues_model.NewLabel(t.Context(), label)) - - listURL := fmt.Sprintf("/api/v1/orgs/%s/labels", privateOrg.Name) - getURL := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", privateOrg.Name, label.ID) - - t.Run("NonMemberDenied", func(t *testing.T) { - // user2 is not a member of the private org and must not see its labels. - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) - MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusNotFound) - }) - - t.Run("AnonymousDenied", func(t *testing.T) { - MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound) - }) - - t.Run("MemberAllowed", func(t *testing.T) { - token := getUserToken(t, "user5", auth_model.AccessTokenScopeReadOrganization) - resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) - labels := DecodeJSON(t, resp, &[]*api.Label{}) - assert.Len(t, *labels, 1) - MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) - }) - - t.Run("SiteAdminAllowed", func(t *testing.T) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) - MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK) - MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK) - }) - - t.Run("PublicOrgStillReadable", func(t *testing.T) { - // org3 (id 3) is a public org with labels; non-members may read them. - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) - MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/labels").AddTokenAuth(token), http.StatusOK) - }) -} diff --git a/tests/integration/api_packages_arch_test.go b/tests/integration/api_packages_arch_test.go index ece8b27f4c6..539712f93c2 100644 --- a/tests/integration/api_packages_arch_test.go +++ b/tests/integration/api_packages_arch_test.go @@ -85,6 +85,16 @@ license = MIT`) rootURL := fmt.Sprintf("/api/packages/%s/arch", user.Name) + uploadPackage := func(t *testing.T, url string, content []byte, expectedStatus int) { + MakeRequest(t, NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).AddBasicAuth(user.Name), expectedStatus) + } + + readIndex := func(t *testing.T, url string) map[string]string { + content, err := test.ReadAllTarGzContent(MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK).Body) + assert.NoError(t, err) + return content + } + t.Run("RepositoryKey", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -110,13 +120,9 @@ license = MIT`) req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusBadRequest) + uploadPackage(t, uploadURL, []byte{}, http.StatusBadRequest) - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + uploadPackage(t, uploadURL, contentAarch64, http.StatusCreated) pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeArch) assert.NoError(t, err) @@ -161,24 +167,16 @@ license = MIT`) return seen }) - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + uploadPackage(t, uploadURL, contentAarch64, http.StatusConflict) // Add same package with different compression leads to conflict - req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader(contentAarch64Gz)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + uploadPackage(t, uploadURL, contentAarch64Gz, http.StatusConflict) }) t.Run("Index", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) - resp := MakeRequest(t, req, http.StatusOK) - - content, err := test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content := readIndex(t, fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)] assert.True(t, has) @@ -222,15 +220,9 @@ license = MIT`) t.Run("Any", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/%s", rootURL, repository), bytes.NewReader(contentAny)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + uploadPackage(t, fmt.Sprintf("%s/%s", rootURL, repository), contentAny, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) - resp := MakeRequest(t, req, http.StatusOK) - - content, err := test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content := readIndex(t, fmt.Sprintf("%s/%s/aarch64/%s", rootURL, repository, arch_service.IndexArchiveFilename)) desc, has := content[fmt.Sprintf("%s-%s/desc", packageName, packageVersion)] assert.True(t, has) @@ -248,7 +240,7 @@ license = MIT`) MakeRequest(t, req, http.StatusOK) } - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/any", rootURL, repository, packageName+"_"+arch_module.AnyArch, packageVersion)). + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s/%s/any", rootURL, repository, packageName+"_"+arch_module.AnyArch, packageVersion)). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) }) @@ -274,18 +266,10 @@ license = MIT`) defer tests.PrintCurrentTest(t)() pkgVer1 := createPackage("gz", "gitea-test", "1.0.0", "aarch64") pkgVer2 := createPackage("gz", "gitea-test", "1.0.1", "aarch64") - req := NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer1)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithBody(t, "PUT", rootURL, bytes.NewReader(pkgVer2)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + uploadPackage(t, rootURL, pkgVer1, http.StatusCreated) + uploadPackage(t, rootURL, pkgVer2, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) - resp := MakeRequest(t, req, http.StatusOK) - - content, err := test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content := readIndex(t, fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) assert.Len(t, content, 2) _, has := content["gitea-test-1.0.0/desc"] @@ -293,14 +277,11 @@ license = MIT`) _, has = content["gitea-test-1.0.1/desc"] assert.True(t, has) - req = NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64"). + req := NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64"). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) - resp = MakeRequest(t, req, http.StatusOK) - content, err = test.ReadAllTarGzContent(resp.Body) - assert.NoError(t, err) + content = readIndex(t, fmt.Sprintf("%s/aarch64/%s", rootURL, arch_service.IndexArchiveFilename)) assert.Len(t, content, 2) _, has = content["gitea-test-1.0.0/desc"] assert.True(t, has) diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go index f179baac3de..8ba1467f289 100644 --- a/tests/integration/api_packages_chef_test.go +++ b/tests/integration/api_packages_chef_test.go @@ -128,29 +128,14 @@ nwIDAQAB req := NewRequest(t, "POST", "/dummy"). SetHeader("X-Ops-Userid", user.Name). SetHeader("X-Ops-Timestamp", time.Now().UTC().Format(time.RFC3339)) - u, err := auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=none") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=1.4") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha2") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) - - req.SetHeader("X-Ops-Sign", "version=1.0;algorithm=sha256") - u, err = auth.Verify(req.Request, nil, nil, nil) - assert.Nil(t, u) - assert.Error(t, err) + for _, signingVersion := range []string{"", "version=none", "version=1.4", "version=1.0;algorithm=sha2", "version=1.0;algorithm=sha256"} { + if signingVersion != "" { + req.SetHeader("X-Ops-Sign", signingVersion) + } + u, err := auth.Verify(req.Request, nil, nil, nil) + assert.Nil(t, u) + assert.Error(t, err) + } }) t.Run("SignedHeaders", func(t *testing.T) { @@ -362,105 +347,67 @@ nwIDAQAB assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s/versions/%s/download", setting.AppURL, root[1:], packageName, packageVersion), info.DownloadURL) }) - t.Run("Search", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + type listCase struct { + Path string + ExpectedTotal int + ExpectedResults int + } - cases := []struct { - Query string - Start int - Items int - ExpectedTotal int - ExpectedResults int - }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"gitea", 0, 10, 0, 0}, - {"test", 0, 10, 1, 1}, - {"test", 1, 10, 1, 0}, - } + for _, list := range []struct { + name string + cases []listCase + }{ + {"Search", []listCase{ + {"/search?q=&start=0&items=0", 1, 1}, + {"/search?q=&start=0&items=10", 1, 1}, + {"/search?q=gitea&start=0&items=10", 0, 0}, + {"/search?q=test&start=0&items=10", 1, 1}, + {"/search?q=test&start=1&items=10", 1, 0}, + }}, + {"EnumeratePackages", []listCase{ + {"/cookbooks?start=0&items=0&sort=", 1, 1}, + {"/cookbooks?start=0&items=10&sort=", 1, 1}, + {"/cookbooks?start=0&items=10&sort=RECENTLY_ADDED", 1, 1}, + {"/cookbooks?start=0&items=10&sort=RECENTLY_UPDATED", 1, 1}, + {"/cookbooks?start=1&items=10&sort=", 1, 0}, + }}, + } { + t.Run(list.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - type Item struct { - CookbookName string `json:"cookbook_name"` - CookbookMaintainer string `json:"cookbook_maintainer"` - CookbookDescription string `json:"cookbook_description"` - Cookbook string `json:"cookbook"` - } - - type Result struct { - Start int `json:"start"` - Total int `json:"total"` - Items []*Item `json:"items"` - } - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/search?q=%s&start=%d&items=%d", root, c.Query, c.Start, c.Items)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, &Result{}) - - assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) - assert.Len(t, result.Items, c.ExpectedResults, "case %d: unexpected result count", i) - - if len(result.Items) == 1 { - item := result.Items[0] - assert.Equal(t, packageName, item.CookbookName) - assert.Equal(t, packageAuthor, item.CookbookMaintainer) - assert.Equal(t, packageDescription, item.CookbookDescription) - assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s", setting.AppURL, root[1:], packageName), item.Cookbook) + type Item struct { + CookbookName string `json:"cookbook_name"` + CookbookMaintainer string `json:"cookbook_maintainer"` + CookbookDescription string `json:"cookbook_description"` + Cookbook string `json:"cookbook"` } - } - }) - t.Run("EnumeratePackages", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - cases := []struct { - Sort string - Start int - Items int - ExpectedTotal int - ExpectedResults int - }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"RECENTLY_ADDED", 0, 10, 1, 1}, - {"RECENTLY_UPDATED", 0, 10, 1, 1}, - {"", 1, 10, 1, 0}, - } - - type Item struct { - CookbookName string `json:"cookbook_name"` - CookbookMaintainer string `json:"cookbook_maintainer"` - CookbookDescription string `json:"cookbook_description"` - Cookbook string `json:"cookbook"` - } - - type Result struct { - Start int `json:"start"` - Total int `json:"total"` - Items []*Item `json:"items"` - } - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/cookbooks?start=%d&items=%d&sort=%s", root, c.Start, c.Items, c.Sort)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, &Result{}) - - assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) - assert.Len(t, result.Items, c.ExpectedResults, "case %d: unexpected result count", i) - - if len(result.Items) == 1 { - item := result.Items[0] - assert.Equal(t, packageName, item.CookbookName) - assert.Equal(t, packageAuthor, item.CookbookMaintainer) - assert.Equal(t, packageDescription, item.CookbookDescription) - assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s", setting.AppURL, root[1:], packageName), item.Cookbook) + type Result struct { + Start int `json:"start"` + Total int `json:"total"` + Items []*Item `json:"items"` } - } - }) + + for i, c := range list.cases { + req := NewRequest(t, "GET", root+c.Path). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + result := DecodeJSON(t, resp, &Result{}) + + assert.Equal(t, c.ExpectedTotal, result.Total, "case %d: unexpected total hits", i) + assert.Len(t, result.Items, c.ExpectedResults, "case %d: unexpected result count", i) + + if len(result.Items) == 1 { + item := result.Items[0] + assert.Equal(t, packageName, item.CookbookName) + assert.Equal(t, packageAuthor, item.CookbookMaintainer) + assert.Equal(t, packageDescription, item.CookbookDescription) + assert.Equal(t, fmt.Sprintf("%s%s/cookbooks/%s", setting.AppURL, root[1:], packageName), item.Cookbook) + } + } + }) + } t.Run("PackageMetadata", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index d464c5df8b5..5b02771be89 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -196,25 +196,30 @@ func TestPackageComposer(t *testing.T) { t.Run("PackageMetadata", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) + assertPackageMetadata := func() []*composer.PackageVersionMetadata { + req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) - result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) + result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) - assert.Contains(t, result.Packages, packageName) - pkgs := result.Packages[packageName] - assert.Len(t, pkgs, 1) - assert.Equal(t, packageName, pkgs[0].Name) - assert.Equal(t, packageVersion, pkgs[0].Version) - assert.Equal(t, packageType, pkgs[0].Type) - assert.Equal(t, packageDescription, pkgs[0].Description) - assert.Len(t, pkgs[0].Authors, 1) - assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) - assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, hex.EncodeToString(contentChecksum[:]), pkgs[0].Dist.Checksum) - assert.Len(t, pkgs[0].Bin, 1) - assert.Equal(t, packageBin, pkgs[0].Bin[0]) + assert.Contains(t, result.Packages, packageName) + pkgs := result.Packages[packageName] + assert.Len(t, pkgs, 1) + assert.Equal(t, packageName, pkgs[0].Name) + assert.Equal(t, packageVersion, pkgs[0].Version) + assert.Equal(t, packageType, pkgs[0].Type) + assert.Equal(t, packageDescription, pkgs[0].Description) + assert.Len(t, pkgs[0].Authors, 1) + assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) + assert.Equal(t, "zip", pkgs[0].Dist.Type) + assert.Equal(t, hex.EncodeToString(contentChecksum[:]), pkgs[0].Dist.Checksum) + assert.Len(t, pkgs[0].Bin, 1) + assert.Equal(t, packageBin, pkgs[0].Bin[0]) + return pkgs + } + + assertPackageMetadata() // Test package linked to repository repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) @@ -226,25 +231,7 @@ func TestPackageComposer(t *testing.T) { err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo1.ID) assert.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) - - assert.Contains(t, result.Packages, packageName) - pkgs = result.Packages[packageName] - assert.Len(t, pkgs, 1) - assert.Equal(t, packageName, pkgs[0].Name) - assert.Equal(t, packageVersion, pkgs[0].Version) - assert.Equal(t, packageType, pkgs[0].Type) - assert.Equal(t, packageDescription, pkgs[0].Description) - assert.Len(t, pkgs[0].Authors, 1) - assert.Equal(t, packageAuthor, pkgs[0].Authors[0].Name) - assert.Equal(t, "zip", pkgs[0].Dist.Type) - assert.Equal(t, hex.EncodeToString(contentChecksum[:]), pkgs[0].Dist.Checksum) - assert.Len(t, pkgs[0].Bin, 1) - assert.Equal(t, packageBin, pkgs[0].Bin[0]) + pkgs := assertPackageMetadata() assert.Equal(t, repo1.HTMLURL(), pkgs[0].Source.URL) assert.Equal(t, "git", pkgs[0].Source.Type) assert.Equal(t, packageVersion, pkgs[0].Source.Reference) @@ -254,11 +241,11 @@ func TestPackageComposer(t *testing.T) { err = packages.SetRepositoryLink(t.Context(), userPkgs[0].ID, repo2.ID) assert.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). + req := NewRequest(t, "GET", fmt.Sprintf("%s/p2/%s/%s.json", url, vendorName, projectName)). AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) + resp := MakeRequest(t, req, http.StatusOK) - result = DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) + result := DecodeJSON(t, resp, &composer.PackageMetadataResponse{}) pkgs = result.Packages[packageName] assert.Len(t, pkgs, 1) assert.Equal(t, repo2.HTMLURL(), pkgs[0].Source.URL) diff --git a/tests/integration/api_packages_conan_test.go b/tests/integration/api_packages_conan_test.go index 90fd3b85bfa..c72db1e70e4 100644 --- a/tests/integration/api_packages_conan_test.go +++ b/tests/integration/api_packages_conan_test.go @@ -278,29 +278,23 @@ func TestPackageConan(t *testing.T) { MakeRequest(t, req, expectedStatusCode) } - t.Run("No Package permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, permission := range []struct { + name string + scope auth_model.AccessTokenScope + expectedAuthStatusCode int + expectedStatusCode int + }{ + {"No Package permission", auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusForbidden}, + {"Package Read permission", auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized}, + {"Package Write permission", auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK}, + {"All permission", auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK}, + } { + t.Run(permission.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusForbidden) - }) - - t.Run("Package Read permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized) - }) - - t.Run("Package Write permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusOK) - }) - - t.Run("All permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusOK) - }) + testCase(t, permission.scope, permission.expectedAuthStatusCode, permission.expectedStatusCode) + }) + } }) t.Run("CheckCredentials", func(t *testing.T) { @@ -366,55 +360,36 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", recipeURL) - resp := MakeRequest(t, req, http.StatusOK) + for _, download := range []struct { + url string + fileName string + hash string + content string + }{ + {recipeURL, conanfileName, "7abc52241c22090782c54731371847a8", buildConanfileContent(name, version1)}, + {fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference), conaninfoName, "7628bfcc5b17f1470c468621a78df394", contentConaninfo}, + } { + req := NewRequest(t, "GET", download.url) + resp := MakeRequest(t, req, http.StatusOK) - fileHashes := DecodeJSON(t, resp, map[string]string{}) - assert.Len(t, fileHashes, 1) - assert.Contains(t, fileHashes, conanfileName) - assert.Equal(t, "7abc52241c22090782c54731371847a8", fileHashes[conanfileName]) + fileHashes := DecodeJSON(t, resp, map[string]string{}) + assert.Len(t, fileHashes, 1) + assert.Contains(t, fileHashes, download.fileName) + assert.Equal(t, download.hash, fileHashes[download.fileName]) - req = NewRequest(t, "GET", recipeURL+"/digest") - resp = MakeRequest(t, req, http.StatusOK) + var downloadURLs map[string]string + for _, endpoint := range []string{"/digest", "/download_urls"} { + req = NewRequest(t, "GET", download.url+endpoint) + resp = MakeRequest(t, req, http.StatusOK) - downloadURLs := DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conanfileName) + downloadURLs = DecodeJSON(t, resp, map[string]string{}) + assert.Contains(t, downloadURLs, download.fileName) + } - req = NewRequest(t, "GET", recipeURL+"/download_urls") - resp = MakeRequest(t, req, http.StatusOK) - - downloadURLs = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conanfileName) - - req = NewRequest(t, "GET", downloadURLs[conanfileName]) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, buildConanfileContent(name, version1), resp.Body.String()) - - packageURL := fmt.Sprintf("%s/packages/%s", recipeURL, conanPackageReference) - - req = NewRequest(t, "GET", packageURL) - resp = MakeRequest(t, req, http.StatusOK) - - fileHashes = DecodeJSON(t, resp, map[string]string{}) - assert.Len(t, fileHashes, 1) - assert.Contains(t, fileHashes, conaninfoName) - assert.Equal(t, "7628bfcc5b17f1470c468621a78df394", fileHashes[conaninfoName]) - - req = NewRequest(t, "GET", packageURL+"/digest") - resp = MakeRequest(t, req, http.StatusOK) - - downloadURLs = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conaninfoName) - - req = NewRequest(t, "GET", packageURL+"/download_urls") - resp = MakeRequest(t, req, http.StatusOK) - - downloadURLs = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, downloadURLs, conaninfoName) - - req = NewRequest(t, "GET", downloadURLs[conaninfoName]) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, contentConaninfo, resp.Body.String()) + req = NewRequest(t, "GET", downloadURLs[download.fileName]) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, download.content, resp.Body.String()) + } }) t.Run("Search", func(t *testing.T) { @@ -605,29 +580,23 @@ func TestPackageConan(t *testing.T) { MakeRequest(t, req, expectedStatusCode) } - t.Run("No Package permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, permission := range []struct { + name string + scope auth_model.AccessTokenScope + expectedAuthStatusCode int + expectedStatusCode int + }{ + {"No Package permission", auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized}, + {"Package Read permission", auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized}, + {"Package Write permission", auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusCreated}, + {"All permission", auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusCreated}, + } { + t.Run(permission.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - testCase(t, auth_model.AccessTokenScopeReadNotification, http.StatusUnauthorized, http.StatusUnauthorized) - }) - - t.Run("Package Read permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusUnauthorized) - }) - - t.Run("Package Write permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeWritePackage, http.StatusOK, http.StatusCreated) - }) - - t.Run("All permission", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - testCase(t, auth_model.AccessTokenScopeAll, http.StatusOK, http.StatusCreated) - }) + testCase(t, permission.scope, permission.expectedAuthStatusCode, permission.expectedStatusCode) + }) + } }) t.Run("CheckCredentials", func(t *testing.T) { @@ -657,19 +626,17 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", recipeURL+"/latest") - resp := MakeRequest(t, req, http.StatusOK) + for _, requestURL := range []string{ + recipeURL + "/latest", + fmt.Sprintf("%s/revisions/%s/packages/%s/latest", recipeURL, revision1, conanPackageReference), + } { + req := NewRequest(t, "GET", requestURL) + resp := MakeRequest(t, req, http.StatusOK) - obj := DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, obj, "revision") - assert.Equal(t, revision1, obj["revision"]) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/revisions/%s/packages/%s/latest", recipeURL, revision1, conanPackageReference)) - resp = MakeRequest(t, req, http.StatusOK) - - obj = DecodeJSON(t, resp, map[string]string{}) - assert.Contains(t, obj, "revision") - assert.Equal(t, revision1, obj["revision"]) + obj := DecodeJSON(t, resp, map[string]string{}) + assert.Contains(t, obj, "revision") + assert.Equal(t, revision1, obj["revision"]) + } }) t.Run("ListRevisions", func(t *testing.T) { @@ -681,9 +648,6 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", recipeURL) - resp := MakeRequest(t, req, http.StatusOK) - type RevisionInfo struct { Revision string `json:"revision"` Time time.Time `json:"time"` @@ -693,24 +657,21 @@ func TestPackageConan(t *testing.T) { Revisions []*RevisionInfo `json:"revisions"` } - list := DecodeJSON(t, resp, &RevisionList{}) - assert.Len(t, list.Revisions, 2) - revs := make([]string, 0, len(list.Revisions)) - for _, rev := range list.Revisions { - revs = append(revs, rev.Revision) - } - assert.ElementsMatch(t, []string{revision1, revision2}, revs) + for _, requestURL := range []string{ + recipeURL, + fmt.Sprintf("%s/%s/packages/%s/revisions", recipeURL, revision1, conanPackageReference), + } { + req := NewRequest(t, "GET", requestURL) + resp := MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/packages/%s/revisions", recipeURL, revision1, conanPackageReference)) - resp = MakeRequest(t, req, http.StatusOK) - - list = DecodeJSON(t, resp, &RevisionList{}) - assert.Len(t, list.Revisions, 2) - revs = make([]string, 0, len(list.Revisions)) - for _, rev := range list.Revisions { - revs = append(revs, rev.Revision) + list := DecodeJSON(t, resp, &RevisionList{}) + assert.Len(t, list.Revisions, 2) + revs := make([]string, 0, len(list.Revisions)) + for _, rev := range list.Revisions { + revs = append(revs, rev.Revision) + } + assert.ElementsMatch(t, []string{revision1, revision2}, revs) } - assert.ElementsMatch(t, []string{revision1, revision2}, revs) }) t.Run("Search", func(t *testing.T) { @@ -753,27 +714,33 @@ func TestPackageConan(t *testing.T) { t.Run("Package", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel1)) - resp := MakeRequest(t, req, http.StatusOK) + for _, requestURL := range []string{ + fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/search", url, name, version1, user1, channel1), + fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/search", url, name, version1, user1, channel1, revision1), + } { + req := NewRequest(t, "GET", requestURL) + resp := MakeRequest(t, req, http.StatusOK) - result := DecodeJSON(t, resp, map[string]*conan_module.Conaninfo{}) + result := DecodeJSON(t, resp, map[string]*conan_module.Conaninfo{}) - assert.Contains(t, result, conanPackageReference) - info := result[conanPackageReference] - assert.NotEmpty(t, info.Settings) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/search", url, name, version1, user1, channel1, revision1)) - resp = MakeRequest(t, req, http.StatusOK) - - result = DecodeJSON(t, resp, map[string]*conan_module.Conaninfo{}) - - assert.Contains(t, result, conanPackageReference) - info = result[conanPackageReference] - assert.NotEmpty(t, info.Settings) + assert.Contains(t, result, conanPackageReference) + info := result[conanPackageReference] + assert.NotEmpty(t, info.Settings) + } }) }) t.Run("Delete", func(t *testing.T) { + deleteConanResource := func(t *testing.T, requestURL string) { + req := NewRequest(t, "DELETE", requestURL). + AddTokenAuth(badToken) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", requestURL). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + } + t.Run("Package", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -793,23 +760,11 @@ func TestPackageConan(t *testing.T) { checkPackageRevisionCount(2) - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s/revisions/%s", url, name, version1, user1, channel1, revision1, conanPackageReference, revision1)) checkPackageRevisionCount(1) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages/%s", url, name, version1, user1, channel1, revision1, conanPackageReference)) checkPackageRevisionCount(0) @@ -817,13 +772,7 @@ func TestPackageConan(t *testing.T) { checkPackageReferenceCount(1) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s/packages", url, name, version1, user1, channel1, revision2)) checkPackageReferenceCount(0) }) @@ -841,23 +790,11 @@ func TestPackageConan(t *testing.T) { checkRecipeRevisionCount(2) - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s/revisions/%s", url, name, version1, user1, channel1, revision1)) checkRecipeRevisionCount(1) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + deleteConanResource(t, fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1)) checkRecipeRevisionCount(0) }) diff --git a/tests/integration/api_packages_conda_test.go b/tests/integration/api_packages_conda_test.go index 23f88abff4b..a301034ec3f 100644 --- a/tests/integration/api_packages_conda_test.go +++ b/tests/integration/api_packages_conda_test.go @@ -62,16 +62,13 @@ func TestPackageConda(t *testing.T) { filename := fmt.Sprintf("%s-%s.tar.bz2", packageName, packageVersion) - req := NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - - req = NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + for _, status := range []int{http.StatusUnauthorized, http.StatusCreated, http.StatusConflict} { + req := NewRequestWithBody(t, "PUT", root+"/"+filename, bytes.NewReader(buf.Bytes())) + if status != http.StatusUnauthorized { + req.AddBasicAuth(user.Name) + } + MakeRequest(t, req, status) + } pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeConda) assert.NoError(t, err) @@ -101,16 +98,13 @@ func TestPackageConda(t *testing.T) { fullName := channel + "/" + packageName filename := fmt.Sprintf("%s-%s.conda", packageName, packageVersion) - req := NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - - req = NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) + for _, status := range []int{http.StatusUnauthorized, http.StatusCreated, http.StatusConflict} { + req := NewRequestWithBody(t, "PUT", root+"/"+channel+"/"+filename, bytes.NewReader(buf.Bytes())) + if status != http.StatusUnauthorized { + req.AddBasicAuth(user.Name) + } + MakeRequest(t, req, status) + } pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeConda) assert.NoError(t, err) @@ -190,84 +184,60 @@ func TestPackageConda(t *testing.T) { Removed map[string]*PackageInfo `json:"removed"` } - req := NewRequest(t, "GET", root+"/noarch/repodata.json") - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - - req = NewRequest(t, "GET", root+"/noarch/repodata.json.bz2") - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) - - req = NewRequest(t, "GET", root+"/noarch/current_repodata.json") - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - - req = NewRequest(t, "GET", root+"/noarch/current_repodata.json.bz2") - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) - - t.Run(".tar.bz2", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeConda, packageName, packageVersion) - assert.NoError(t, err) - - pd, err := packages.GetPackageDescriptor(t.Context(), pv) - assert.NoError(t, err) - - req := NewRequest(t, "GET", root+"/noarch/repodata.json") + for _, c := range []struct { + filename, contentType string + }{ + {"repodata.json", "application/json"}, + {"repodata.json.bz2", "application/x-bzip2"}, + {"current_repodata.json", "application/json"}, + {"current_repodata.json.bz2", "application/x-bzip2"}, + } { + req := NewRequest(t, "GET", root+"/noarch/"+c.filename) resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, c.contentType, resp.Header().Get("Content-Type")) + } - result := DecodeJSON(t, resp, &RepoData{}) + for _, c := range []struct { + extension, name, root string + }{ + {".tar.bz2", packageName, root}, + {".conda", channel + "/" + packageName, root + "/" + channel}, + } { + t.Run(c.extension, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - assert.Equal(t, "noarch", result.Info.Subdir) - assert.Empty(t, result.PackagesConda) - assert.Empty(t, result.Removed) + pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeConda, c.name, packageVersion) + assert.NoError(t, err) - filename := fmt.Sprintf("%s-%s-xxx.tar.bz2", packageName, packageVersion) - assert.Contains(t, result.Packages, filename) - packageInfo := result.Packages[filename] - assert.Equal(t, packageName, packageInfo.Name) - assert.Equal(t, packageVersion, packageInfo.Version) - assert.Equal(t, "noarch", packageInfo.Subdir) - assert.Equal(t, "xxx", packageInfo.Build) - assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) - assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) - assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) - assert.NotNil(t, packageInfo.Dependencies) - assert.Empty(t, packageInfo.Dependencies) - }) + pd, err := packages.GetPackageDescriptor(t.Context(), pv) + assert.NoError(t, err) - t.Run(".conda", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", c.root+"/noarch/repodata.json") + resp := MakeRequest(t, req, http.StatusOK) - pv, err := packages.GetVersionByNameAndVersion(t.Context(), user.ID, packages.TypeConda, channel+"/"+packageName, packageVersion) - assert.NoError(t, err) + result := DecodeJSON(t, resp, &RepoData{}) + packageInfos, otherPackages := result.Packages, result.PackagesConda + if c.extension == ".conda" { + packageInfos, otherPackages = result.PackagesConda, result.Packages + } - pd, err := packages.GetPackageDescriptor(t.Context(), pv) - assert.NoError(t, err) + assert.Equal(t, "noarch", result.Info.Subdir) + assert.Empty(t, otherPackages) + assert.Empty(t, result.Removed) - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/noarch/repodata.json", root, channel)) - resp := MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, &RepoData{}) - - assert.Equal(t, "noarch", result.Info.Subdir) - assert.Empty(t, result.Packages) - assert.Empty(t, result.Removed) - - filename := fmt.Sprintf("%s-%s-xxx.conda", packageName, packageVersion) - assert.Contains(t, result.PackagesConda, filename) - packageInfo := result.PackagesConda[filename] - assert.Equal(t, packageName, packageInfo.Name) - assert.Equal(t, packageVersion, packageInfo.Version) - assert.Equal(t, "noarch", packageInfo.Subdir) - assert.Equal(t, "xxx", packageInfo.Build) - assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) - assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) - assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) - assert.NotNil(t, packageInfo.Dependencies) - assert.Empty(t, packageInfo.Dependencies) - }) + filename := fmt.Sprintf("%s-%s-xxx%s", packageName, packageVersion, c.extension) + assert.Contains(t, packageInfos, filename) + packageInfo := packageInfos[filename] + assert.Equal(t, packageName, packageInfo.Name) + assert.Equal(t, packageVersion, packageInfo.Version) + assert.Equal(t, "noarch", packageInfo.Subdir) + assert.Equal(t, "xxx", packageInfo.Build) + assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) + assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) + assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) + assert.NotNil(t, packageInfo.Dependencies) + assert.Empty(t, packageInfo.Dependencies) + }) + } }) } diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index df59c246692..6d2c02658fa 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -199,17 +199,14 @@ func TestPackageContainer(t *testing.T) { t.Run("DetermineSupport", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + for _, token := range []string{userToken, readToken} { + req := NewRequest(t, "GET", setting.AppURL+"v2"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) + } + req := NewRequest(t, "GET", setting.AppURL+"v2"). - AddTokenAuth(userToken) - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - - req = NewRequest(t, "GET", setting.AppURL+"v2"). - AddTokenAuth(readToken) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - - req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) }) @@ -221,19 +218,13 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Monolithic", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(anonymousToken) - MakeRequest(t, req, http.StatusUnauthorized) + for _, token := range []string{anonymousToken, readToken, badToken} { + req := NewRequest(t, "POST", url+"/blobs/uploads"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnauthorized) + } - req = NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)). + req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/blobs/uploads?digest=%s", url, unknownDigest), bytes.NewReader(blobContent)). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusBadRequest) @@ -259,15 +250,13 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Chunked", func(t *testing.T) { defer tests.PrintCurrentTest(t)() + for _, token := range []string{readToken, badToken} { + req := NewRequest(t, "POST", url+"/blobs/uploads"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnauthorized) + } + req := NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "POST", url+"/blobs/uploads"). - AddTokenAuth(badToken) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusAccepted) @@ -714,41 +703,25 @@ func TestPackageContainer(t *testing.T) { }) t.Run("Delete", func(t *testing.T) { - t.Run("Blob", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, c := range []struct { + name, url string + }{ + {"Blob", fmt.Sprintf("%s/blobs/%s", url, blobDigest)}, + {"ManifestByDigest", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)}, + {"ManifestByTag", fmt.Sprintf("%s/manifests/%s", url, multiTag)}, + } { + t.Run(c.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/blobs/%s", url, blobDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusAccepted) + req := NewRequest(t, "DELETE", c.url). + AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusAccepted) - req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("ManifestByDigest", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusAccepted) - - req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, untaggedManifestDigest)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("ManifestByTag", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", fmt.Sprintf("%s/manifests/%s", url, multiTag)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusAccepted) - - req = NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/%s", url, multiTag)). - AddTokenAuth(userToken) - MakeRequest(t, req, http.StatusNotFound) - }) + req = NewRequest(t, "HEAD", c.url). + AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusNotFound) + }) + } }) }) } diff --git a/tests/integration/api_packages_cran_test.go b/tests/integration/api_packages_cran_test.go index 0615a7a1e9d..8ad38bfceea 100644 --- a/tests/integration/api_packages_cran_test.go +++ b/tests/integration/api_packages_cran_test.go @@ -44,6 +44,24 @@ func TestPackageCran(t *testing.T) { url := fmt.Sprintf("/api/packages/%s/cran", user.Name) + enumerate := func(t *testing.T, indexURL string) { + req := NewRequest(t, "GET", indexURL). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") + + body := resp.Body.String() + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) + + req = NewRequest(t, "GET", indexURL+".gz"). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip") + } + t.Run("Source", func(t *testing.T) { t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -105,21 +123,7 @@ func TestPackageCran(t *testing.T) { t.Run("Enumerate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", url+"/src/contrib/PACKAGES"). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") - - body := resp.Body.String() - assert.Contains(t, body, "Package: "+packageName) - assert.Contains(t, body, "Version: "+packageVersion) - - req = NewRequest(t, "GET", url+"/src/contrib/PACKAGES.gz"). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip") + enumerate(t, url+"/src/contrib/PACKAGES") }) }) @@ -189,21 +193,7 @@ func TestPackageCran(t *testing.T) { req := NewRequest(t, "GET", url+"/bin/windows/contrib/4.1/PACKAGES") MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES"). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") - - body := resp.Body.String() - assert.Contains(t, body, "Package: "+packageName) - assert.Contains(t, body, "Version: "+packageVersion) - - req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES.gz"). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Contains(t, resp.Header().Get("Content-Type"), "application/x-gzip") + enumerate(t, url+"/bin/windows/contrib/4.2/PACKAGES") }) }) diff --git a/tests/integration/api_packages_goproxy_test.go b/tests/integration/api_packages_goproxy_test.go index 615aaed5327..84925ddd715 100644 --- a/tests/integration/api_packages_goproxy_test.go +++ b/tests/integration/api_packages_goproxy_test.go @@ -98,52 +98,43 @@ func TestPackageGo(t *testing.T) { t.Run("Info", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.info", url, packageName, packageVersion)) - resp := MakeRequest(t, req, http.StatusOK) - type Info struct { Version string `json:"Version"` Time time.Time `json:"Time"` } - info := DecodeJSON(t, resp, &Info{}) + for _, infoRequest := range []struct { + path, version string + }{ + {"@v/" + packageVersion + ".info", packageVersion}, + {"@v/latest.info", packageVersion2}, + {"@latest", packageVersion2}, + } { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", url, packageName, infoRequest.path)) + resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, packageVersion, info.Version) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.info", url, packageName)) - resp = MakeRequest(t, req, http.StatusOK) - - info = DecodeJSON(t, resp, &Info{}) - assert.Equal(t, packageVersion2, info.Version) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@latest", url, packageName)) - resp = MakeRequest(t, req, http.StatusOK) - - info = DecodeJSON(t, resp, &Info{}) - assert.Equal(t, packageVersion2, info.Version) + info := DecodeJSON(t, resp, &Info{}) + assert.Equal(t, infoRequest.version, info.Version) + } }) t.Run("GoMod", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.mod", url, packageName, packageVersion)) - resp := MakeRequest(t, req, http.StatusOK) + for _, version := range []string{packageVersion, "latest"} { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.mod", url, packageName, version)) + resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, goModContent, resp.Body.String()) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.mod", url, packageName)) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, goModContent, resp.Body.String()) + assert.Equal(t, goModContent, resp.Body.String()) + } }) t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.zip", url, packageName, packageVersion)) - MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/latest.zip", url, packageName)) - MakeRequest(t, req, http.StatusOK) + for _, version := range []string{packageVersion, "latest"} { + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s/@v/%s.zip", url, packageName, version)) + MakeRequest(t, req, http.StatusOK) + } }) } diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index df25f780f46..e011542bad0 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -47,6 +47,22 @@ func TestPackageMaven(t *testing.T) { assert.NotEmpty(t, h.Get("Last-Modified")) } + checkDownload := func(t *testing.T, name, contentType, content string, downloadCount int64) { + for _, method := range []string{"HEAD", "GET"} { + req := NewRequest(t, method, fmt.Sprintf("%s/%s/%s", root, packageVersion, name)).AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + checkHeaders(t, resp.Header(), contentType, int64(len(content))) + if method == "GET" { + assert.Equal(t, []byte(content), resp.Body.Bytes()) + } + } + + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeMaven) + require.NoError(t, err) + assert.Len(t, pvs, 1) + assert.Equal(t, downloadCount, pvs[0].DownloadCount) + } + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -139,22 +155,7 @@ func TestPackageMaven(t *testing.T) { t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "HEAD", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "application/java-archive", 4) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "application/java-archive", 4) - - assert.Equal(t, []byte("test"), resp.Body.Bytes()) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeMaven) - require.NoError(t, err) - assert.Len(t, pvs, 1) - assert.Equal(t, int64(0), pvs[0].DownloadCount) + checkDownload(t, filename, "application/java-archive", "test", 0) }) t.Run("UploadVerifySHA1", func(t *testing.T) { @@ -219,22 +220,7 @@ func TestPackageMaven(t *testing.T) { t.Run("DownloadPOM", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "HEAD", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "text/xml", int64(len(pomContent))) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.pom", root, packageVersion, filename)).AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - checkHeaders(t, resp.Header(), "text/xml", int64(len(pomContent))) - - assert.Equal(t, []byte(pomContent), resp.Body.Bytes()) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeMaven) - require.NoError(t, err) - assert.Len(t, pvs, 1) - assert.Equal(t, int64(1), pvs[0].DownloadCount) + checkDownload(t, filename+".pom", "text/xml", pomContent, 1) }) t.Run("DownloadChecksums", func(t *testing.T) { diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index f9ccc6a5c4e..a8d60a35106 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -164,210 +164,147 @@ func TestPackageNuGet(t *testing.T) { url := fmt.Sprintf("/api/packages/%s/nuget", user.Name) t.Run("ServiceIndex", func(t *testing.T) { - t.Run("v2", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, version := range []string{"v2", "v3"} { + t.Run(version, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) + privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) - cases := []struct { - Owner string - UseBasicAuth bool - token string - expectedStatus int - }{ - {privateUser.Name, false, "", http.StatusOK}, - {privateUser.Name, true, "", http.StatusOK}, - {privateUser.Name, false, writeToken, http.StatusOK}, - {privateUser.Name, false, readToken, http.StatusOK}, - {privateUser.Name, false, badToken, http.StatusOK}, - {user.Name, false, "", http.StatusOK}, - {user.Name, true, "", http.StatusOK}, - {user.Name, false, writeToken, http.StatusOK}, - {user.Name, false, readToken, http.StatusOK}, - {user.Name, false, badToken, http.StatusOK}, - } + cases := []struct { + Owner string + UseBasicAuth bool + token string + expectedStatus int + }{ + {privateUser.Name, false, "", http.StatusOK}, + {privateUser.Name, true, "", http.StatusOK}, + {privateUser.Name, false, writeToken, http.StatusOK}, + {privateUser.Name, false, readToken, http.StatusOK}, + {privateUser.Name, false, badToken, http.StatusOK}, + {user.Name, false, "", http.StatusOK}, + {user.Name, true, "", http.StatusOK}, + {user.Name, false, writeToken, http.StatusOK}, + {user.Name, false, readToken, http.StatusOK}, + {user.Name, false, badToken, http.StatusOK}, + } - for _, c := range cases { - t.Run(c.Owner, func(t *testing.T) { - url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) + for _, c := range cases { + t.Run(c.Owner, func(t *testing.T) { + url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - req := NewRequest(t, "GET", url) - if c.UseBasicAuth { - req.AddBasicAuth(user.Name) - } else if c.token != "" { - addNuGetAPIKeyHeader(req, c.token) - } - resp := MakeRequest(t, req, c.expectedStatus) - if c.expectedStatus != http.StatusOK { - return - } - - var result nuget.ServiceIndexResponseV2 - decodeXML(t, resp, &result) - - assert.Equal(t, setting.AppURL+url[1:], result.Base) - assert.Equal(t, "Packages", result.Workspace.Collection.Href) - }) - } - }) - - t.Run("v3", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate}) - - cases := []struct { - Owner string - UseBasicAuth bool - token string - expectedStatus int - }{ - {privateUser.Name, false, "", http.StatusOK}, - {privateUser.Name, true, "", http.StatusOK}, - {privateUser.Name, false, writeToken, http.StatusOK}, - {privateUser.Name, false, readToken, http.StatusOK}, - {privateUser.Name, false, badToken, http.StatusOK}, - {user.Name, false, "", http.StatusOK}, - {user.Name, true, "", http.StatusOK}, - {user.Name, false, writeToken, http.StatusOK}, - {user.Name, false, readToken, http.StatusOK}, - {user.Name, false, badToken, http.StatusOK}, - } - - for _, c := range cases { - t.Run(c.Owner, func(t *testing.T) { - url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - - req := NewRequest(t, "GET", url+"/index.json") - if c.UseBasicAuth { - req.AddBasicAuth(user.Name) - } else if c.token != "" { - addNuGetAPIKeyHeader(req, c.token) - } - resp := MakeRequest(t, req, c.expectedStatus) - - if c.expectedStatus != http.StatusOK { - return - } - - result := DecodeJSON(t, resp, &nuget.ServiceIndexResponseV3{}) - - assert.Equal(t, "3.0.0", result.Version) - assert.NotEmpty(t, result.Resources) - - root := setting.AppURL + url[1:] - for _, r := range result.Resources { - switch r.Type { - case "SearchQueryService": - fallthrough - case "SearchQueryService/3.0.0-beta": - fallthrough - case "SearchQueryService/3.0.0-rc": - assert.Equal(t, root+"/query", r.ID) - case "RegistrationsBaseUrl": - fallthrough - case "RegistrationsBaseUrl/3.0.0-beta": - fallthrough - case "RegistrationsBaseUrl/3.0.0-rc": - assert.Equal(t, root+"/registration", r.ID) - case "PackageBaseAddress/3.0.0": - assert.Equal(t, root+"/package", r.ID) - case "PackagePublish/2.0.0": - assert.Equal(t, root, r.ID) + endpoint := url + if version == "v3" { + endpoint += "/index.json" } - } - }) - } - }) + req := NewRequest(t, "GET", endpoint) + if c.UseBasicAuth { + req.AddBasicAuth(user.Name) + } else if c.token != "" { + addNuGetAPIKeyHeader(req, c.token) + } + resp := MakeRequest(t, req, c.expectedStatus) + if c.expectedStatus != http.StatusOK { + return + } + + if version == "v2" { + var result nuget.ServiceIndexResponseV2 + decodeXML(t, resp, &result) + + assert.Equal(t, setting.AppURL+url[1:], result.Base) + assert.Equal(t, "Packages", result.Workspace.Collection.Href) + } else { + result := DecodeJSON(t, resp, &nuget.ServiceIndexResponseV3{}) + + assert.Equal(t, "3.0.0", result.Version) + assert.NotEmpty(t, result.Resources) + + root := setting.AppURL + url[1:] + for _, r := range result.Resources { + switch r.Type { + case "SearchQueryService": + fallthrough + case "SearchQueryService/3.0.0-beta": + fallthrough + case "SearchQueryService/3.0.0-rc": + assert.Equal(t, root+"/query", r.ID) + case "RegistrationsBaseUrl": + fallthrough + case "RegistrationsBaseUrl/3.0.0-beta": + fallthrough + case "RegistrationsBaseUrl/3.0.0-rc": + assert.Equal(t, root+"/registration", r.ID) + case "PackageBaseAddress/3.0.0": + assert.Equal(t, root+"/package", r.ID) + case "PackagePublish/2.0.0": + assert.Equal(t, root, r.ID) + } + } + } + }) + } + }) + } }) t.Run("Upload", func(t *testing.T) { t.Run("DependencyPackage", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // create with username/password - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + for _, useToken := range []bool{false, true} { + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) + if useToken { + // create with token + req.AddTokenAuth(writeToken) + } else { + // create with username/password + req.AddBasicAuth(user.Name) + } + MakeRequest(t, req, http.StatusCreated) - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) - assert.NoError(t, err) - assert.Len(t, pvs, 1, "Should have one version") + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) + assert.NoError(t, err) + assert.Len(t, pvs, 1, "Should have one version") - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) + pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec") - for _, pf := range pfs { - switch pf.Name { - case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): - assert.True(t, pf.IsLead) + pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec") + for _, pf := range pfs { + switch pf.Name { + case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): + assert.True(t, pf.IsLead) - pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(len(content)), pb.Size) - case packageName + ".nuspec": - assert.False(t, pf.IsLead) - default: - assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) + pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(len(content)), pb.Size) + case packageName + ".nuspec": + assert.False(t, pf.IsLead) + default: + assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) + } + } + + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusConflict) + + if !useToken { + // delete the package + assert.NoError(t, packageService.DeletePackageVersionAndReferences(t.Context(), pvs[0])) + + // create failure with token without write access + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). + AddTokenAuth(readToken) + MakeRequest(t, req, http.StatusUnauthorized) } } - - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) - - // delete the package - assert.NoError(t, packageService.DeletePackageVersionAndReferences(t.Context(), pvs[0])) - - // create failure with token without write access - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddTokenAuth(readToken) - MakeRequest(t, req, http.StatusUnauthorized) - - // create with token - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddTokenAuth(writeToken) - MakeRequest(t, req, http.StatusCreated) - - pvs, err = packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeNuGet) - assert.NoError(t, err) - assert.Len(t, pvs, 1, "Should have one version") - - pd, err = packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.IsType(t, &nuget_module.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) - - pfs, err = packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 2, "Should have 2 files: nuget and nuspec") - for _, pf := range pfs { - switch pf.Name { - case fmt.Sprintf("%s.%s.nupkg", packageName, packageVersion): - assert.True(t, pf.IsLead) - - pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(len(content)), pb.Size) - case packageName + ".nuspec": - assert.False(t, pf.IsLead) - default: - assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) - } - } - - req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusConflict) }) t.Run("SymbolPackage", func(t *testing.T) { @@ -561,11 +498,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) } t.Run("v2", func(t *testing.T) { - t.Run("Search()", func(t *testing.T) { + testSearch := func(t *testing.T, endpoint, query string) { defer tests.PrintCurrentTest(t)() for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + req := NewRequest(t, "GET", fmt.Sprintf("%s/%s?%s&$skip=%d&$top=%d", url, endpoint, fmt.Sprintf(query, c.Query), c.Skip, c.Take)). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -575,59 +512,27 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/$count?%s&$skip=%d&$top=%d", url, endpoint, fmt.Sprintf(query, c.Query), c.Skip, c.Take)). AddBasicAuth(user.Name) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) } + } + + t.Run("Search()", func(t *testing.T) { + testSearch(t, "Search()", "searchTerm='%s'") }) t.Run("Packages()", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - var result FeedResponse - decodeXML(t, resp, &result) - - assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) - assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) - } + testSearch(t, "Packages()", "$filter=substringof('%s',tolower(Id))") }) t.Run("Packages()", func(t *testing.T) { defer tests.PrintCurrentTest(t)() t.Run("substringof", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusOK) - - var result FeedResponse - decodeXML(t, resp, &result) - - assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) - assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - - req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). - AddBasicAuth(user.Name) - resp = MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) - } + testSearch(t, "Packages()", "$filter=substringof('%s',tolower(Id))") }) t.Run("IdEq", func(t *testing.T) { diff --git a/tests/integration/api_packages_pub_test.go b/tests/integration/api_packages_pub_test.go index 072a36978d9..314109d6cd4 100644 --- a/tests/integration/api_packages_pub_test.go +++ b/tests/integration/api_packages_pub_test.go @@ -117,19 +117,19 @@ description: ` + packageDescription, uploadFile(t, result.URL, buildPackage(packageVersionLatest), http.StatusNoContent) }) + type VersionMetadata struct { + Version string `json:"version"` + ArchiveURL string `json:"archive_url"` + Published time.Time `json:"published"` + Pubspec any `json:"pubspec,omitempty"` + } + t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s/%s", root, packageName, packageVersion)) resp := MakeRequest(t, req, http.StatusOK) - type VersionMetadata struct { - Version string `json:"version"` - ArchiveURL string `json:"archive_url"` - Published time.Time `json:"published"` - Pubspec any `json:"pubspec,omitempty"` - } - result := DecodeJSON(t, resp, &VersionMetadata{}) assert.Equal(t, packageVersion, result.Version) @@ -147,13 +147,6 @@ description: ` + packageDescription, req := NewRequest(t, "GET", fmt.Sprintf("%s/api/packages/%s", root, packageName)) resp := MakeRequest(t, req, http.StatusOK) - type VersionMetadata struct { - Version string `json:"version"` - ArchiveURL string `json:"archive_url"` - Published time.Time `json:"published"` - Pubspec any `json:"pubspec,omitempty"` - } - type PackageVersions struct { Name string `json:"name"` Latest *VersionMetadata `json:"latest"` diff --git a/tests/integration/api_packages_pypi_test.go b/tests/integration/api_packages_pypi_test.go index a24d6c96eb1..a05f906d208 100644 --- a/tests/integration/api_packages_pypi_test.go +++ b/tests/integration/api_packages_pypi_test.go @@ -74,66 +74,55 @@ func TestPackagePyPI(t *testing.T) { uploadHelper(t, body, writer.FormDataContentType(), expectedStatus) } - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, upload := range []struct { + name string + filename string + fileCount int + }{ + {"Upload", "test.whl", 1}, + {"UploadAddFile", "test.tar.gz", 2}, + } { + t.Run(upload.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - filename := "test.whl" - uploadFile(t, filename, content, http.StatusCreated) + uploadFile(t, upload.filename, content, http.StatusCreated) - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypePyPI) - assert.NoError(t, err) - assert.Len(t, pvs, 1) + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypePyPI) + assert.NoError(t, err) + assert.Len(t, pvs, 1) - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.Nil(t, pd.SemVer) - metadata, ok := pd.Metadata.(*pypi.Metadata) - require.True(t, ok) - assert.Equal(t, projectURL, metadata.ProjectURL) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) + pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + if upload.fileCount == 1 { + metadata, ok := pd.Metadata.(*pypi.Metadata) + require.True(t, ok) + assert.Equal(t, projectURL, metadata.ProjectURL) + } else { + assert.IsType(t, &pypi.Metadata{}, pd.Metadata) + } + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 1) - assert.Equal(t, filename, pfs[0].Name) - assert.True(t, pfs[0].IsLead) + pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) + assert.NoError(t, err) + assert.Len(t, pfs, upload.fileCount) - pb, err := packages.GetBlobByID(t.Context(), pfs[0].BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(4), pb.Size) - }) + var pf *packages.PackageFile + if upload.fileCount == 1 { + pf = pfs[0] + } else { + pf, err = packages.GetFileForVersionByName(t.Context(), pvs[0].ID, upload.filename, packages.EmptyFileKey) + assert.NoError(t, err) + } + assert.Equal(t, upload.filename, pf.Name) + assert.True(t, pf.IsLead) - t.Run("UploadAddFile", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - filename := "test.tar.gz" - uploadFile(t, filename, content, http.StatusCreated) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypePyPI) - assert.NoError(t, err) - assert.Len(t, pvs, 1) - - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.Nil(t, pd.SemVer) - assert.IsType(t, &pypi.Metadata{}, pd.Metadata) - assert.Equal(t, packageName, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) - - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 2) - - pf, err := packages.GetFileForVersionByName(t.Context(), pvs[0].ID, filename, packages.EmptyFileKey) - assert.NoError(t, err) - assert.Equal(t, filename, pf.Name) - assert.True(t, pf.IsLead) - - pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) - assert.NoError(t, err) - assert.Equal(t, int64(4), pb.Size) - }) + pb, err := packages.GetBlobByID(t.Context(), pf.BlobID) + assert.NoError(t, err) + assert.Equal(t, int64(4), pb.Size) + }) + } t.Run("UploadHashMismatch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 639fb0e37e5..cf7e9ef3553 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -467,21 +467,42 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, }, } - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - updates := []*rpm_module.Update{&advisory} + postAdvisories := func(t *testing.T, url string, updates []*rpm_module.Update, expectedStatus int) { body, err := json.Marshal(updates) assert.NoError(t, err) - req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)). + req := NewRequestWithBody(t, "POST", url, bytes.NewReader(body)). AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + MakeRequest(t, req, expectedStatus) + } + + getUpdates := func(t *testing.T) updateInfo { + req := NewRequest(t, "GET", groupURL+"/repodata/updateinfo.xml.gz") + resp := MakeRequest(t, req, http.StatusOK) + + var result updateInfo + decodeGzipXML(t, resp, &result) + return result + } + + findUpdate := func(updates []*rpm_module.Update, id string) *rpm_module.Update { + for _, update := range updates { + if update.ID == id { + return update + } + } + return nil + } + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + postAdvisories(t, errataURL, []*rpm_module.Update{&advisory}, http.StatusOK) url := groupURL + "/repodata" // Check repomd.xml contains updateinfo - req = NewRequest(t, "GET", url+"/repomd.xml") + req := NewRequest(t, "GET", url+"/repomd.xml") resp := MakeRequest(t, req, http.StatusOK) type testRepoData struct { @@ -490,7 +511,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var repomd struct { Data []*testRepoData `xml:"data"` } - err = xml.NewDecoder(resp.Body).Decode(&repomd) + err := xml.NewDecoder(resp.Body).Decode(&repomd) require.NoError(t, err) found := slices.IndexFunc(repomd.Data, func(s *testRepoData) bool { @@ -499,11 +520,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, assert.True(t, found, "updateinfo not found in repomd.xml") // Now check updateinfo.xml.gz - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp = MakeRequest(t, req, http.StatusOK) - - var result updateInfo - decodeGzipXML(t, resp, &result) + result := getUpdates(t) assert.Equal(t, "http://linux.duke.edu/metadata/updateinfo", result.Xmlns) assert.Len(t, result.Updates, 1) @@ -559,21 +576,10 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, MakeRequest(t, req, http.StatusOK) // Verify it was stored correctly (skipping nulls) - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) - - var result updateInfo - decodeGzipXML(t, resp, &result) + result := getUpdates(t) // We need to find the new advisory CVE-2023-5678 - var newAdvisory *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-5678" { - newAdvisory = u - break - } - } + newAdvisory := findUpdate(result.Updates, "CVE-2023-5678") assert.NotNil(t, newAdvisory) assert.Len(t, newAdvisory.PkgList, 1) assert.Equal(t, "el9", newAdvisory.PkgList[0].Short) @@ -585,13 +591,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, defer tests.PrintCurrentTest(t)() badURL := fmt.Sprintf("%s/package/%s/non-existent-version/errata", groupURL, packageName) - updates := []*rpm_module.Update{&advisory} - body, err := json.Marshal(updates) - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", badURL, bytes.NewReader(body)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNotFound) + postAdvisories(t, badURL, []*rpm_module.Update{&advisory}, http.StatusNotFound) }) t.Run("MergeAdvisories", func(t *testing.T) { @@ -615,29 +615,12 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, }, } - updates := []*rpm_module.Update{&advisory2} - body, err := json.Marshal(updates) - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + postAdvisories(t, errataURL, []*rpm_module.Update{&advisory2}, http.StatusOK) // Check updateinfo.xml.gz again - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) + result := getUpdates(t) - var result updateInfo - decodeGzipXML(t, resp, &result) - - var targetUpdate *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-1234" { - targetUpdate = u - break - } - } + targetUpdate := findUpdate(result.Updates, "CVE-2023-1234") assert.NotNil(t, targetUpdate) // Verify that package lists are merged into the same collection assert.Len(t, targetUpdate.PkgList, 1) @@ -651,44 +634,15 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, // Upload a third advisory with the same ID but a different collection advisory3 := advisory advisory3.PkgList = []*rpm_module.Collection{ - { - Short: "el8", - Packages: []*rpm_module.UpdatePackage{ - { - Arch: packageArchitecture, - Name: packageName, - Release: "1", - Src: "gitea-test-1.0.2-1.src.rpm", - Version: "1.0.2", - Filename: fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), - }, - }, - }, + {Short: "el8", Packages: advisory.PkgList[0].Packages}, } - updates := []*rpm_module.Update{&advisory3} - body, err := json.Marshal(updates) - assert.NoError(t, err) - - req := NewRequestWithBody(t, "POST", errataURL, bytes.NewReader(body)). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + postAdvisories(t, errataURL, []*rpm_module.Update{&advisory3}, http.StatusOK) // Check updateinfo.xml.gz again - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) + result := getUpdates(t) - var result updateInfo - decodeGzipXML(t, resp, &result) - - var targetUpdate *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-1234" { - targetUpdate = u - break - } - } + targetUpdate := findUpdate(result.Updates, "CVE-2023-1234") assert.NotNil(t, targetUpdate) // Verify that we now have 2 collections assert.Len(t, targetUpdate.PkgList, 2) @@ -716,20 +670,9 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, MakeRequest(t, req, http.StatusOK) // Check updateinfo.xml.gz - url := groupURL + "/repodata" - req = NewRequest(t, "GET", url+"/updateinfo.xml.gz") - resp := MakeRequest(t, req, http.StatusOK) + result := getUpdates(t) - var result updateInfo - decodeGzipXML(t, resp, &result) - - var targetUpdate *rpm_module.Update - for _, u := range result.Updates { - if u.ID == "CVE-2023-1234" { - targetUpdate = u - break - } - } + targetUpdate := findUpdate(result.Updates, "CVE-2023-1234") assert.NotNil(t, targetUpdate) assert.Len(t, targetUpdate.PkgList, 2) diff --git a/tests/integration/api_packages_swift_test.go b/tests/integration/api_packages_swift_test.go index a6bb92402ec..07412db7168 100644 --- a/tests/integration/api_packages_swift_test.go +++ b/tests/integration/api_packages_swift_test.go @@ -64,19 +64,14 @@ func TestPackageSwift(t *testing.T) { t.Run("CheckLogin", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequestWithBody(t, "POST", url, strings.NewReader("")) - MakeRequest(t, req, http.StatusUnauthorized) + for _, endpoint := range []string{url, url + "/login"} { + req := NewRequestWithBody(t, "POST", endpoint, strings.NewReader("")) + MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithBody(t, "POST", url, strings.NewReader("")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) - - req = NewRequestWithBody(t, "POST", url+"/login", strings.NewReader("")) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequestWithBody(t, "POST", url+"/login", strings.NewReader("")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + req = NewRequestWithBody(t, "POST", endpoint, strings.NewReader("")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) + } }) t.Run("CheckAcceptMediaType", func(t *testing.T) { @@ -108,171 +103,122 @@ func TestPackageSwift(t *testing.T) { assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) }) - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, upload := range []struct { + name, version string + formField bool + }{ + {"Upload", packageVersion, false}, + {"UploadMultipart", packageVersion2Beta, true}, + } { + t.Run(upload.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { - var body bytes.Buffer - mpw := multipart.NewWriter(&body) + uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { + var body bytes.Buffer + mpw := multipart.NewWriter(&body) - part, _ := mpw.CreateFormFile("source-archive", "source-archive.zip") - io.Copy(part, sr) + if upload.formField { + // Read the source archive content + sourceContent, err := io.ReadAll(sr) + assert.NoError(t, err) + mpw.WriteField("source-archive", string(sourceContent)) + } else { + part, _ := mpw.CreateFormFile("source-archive", "source-archive.zip") + io.Copy(part, sr) + } - if metadata != "" { - mpw.WriteField("metadata", metadata) + if metadata != "" { + mpw.WriteField("metadata", metadata) + } + + mpw.Close() + + req := NewRequestWithBody(t, "PUT", url, &body). + SetHeader("Content-Type", mpw.FormDataContentType()). + SetHeader("Accept", swift_router.AcceptJSON). + AddBasicAuth(user.Name) + MakeRequest(t, req, expectedStatus) } - mpw.Close() + if !upload.formField { + for _, triple := range []string{"/sc_ope/package/1.0.0", "/scope/pack~age/1.0.0", "/scope/package/1_0.0"} { + req := NewRequestWithBody(t, "PUT", url+triple, bytes.NewReader([]byte{})). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusBadRequest) - req := NewRequestWithBody(t, "PUT", url, &body). - SetHeader("Content-Type", mpw.FormDataContentType()). - SetHeader("Accept", swift_router.AcceptJSON). - AddBasicAuth(user.Name) - MakeRequest(t, req, expectedStatus) - } + assert.Equal(t, "1", resp.Header().Get("Content-Version")) + assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) + } + } - for _, triple := range []string{"/sc_ope/package/1.0.0", "/scope/pack~age/1.0.0", "/scope/package/1_0.0"} { - req := NewRequestWithBody(t, "PUT", url+triple, bytes.NewReader([]byte{})). - AddBasicAuth(user.Name) - resp := MakeRequest(t, req, http.StatusBadRequest) + uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, upload.version) - assert.Equal(t, "1", resp.Header().Get("Content-Version")) - assert.Equal(t, "application/problem+json", resp.Header().Get("Content-Type")) - } + req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) + MakeRequest(t, req, http.StatusUnauthorized) - uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion) + // Test with metadata as form field + uploadPackage( + t, + uploadURL, + http.StatusCreated, + test.WriteZipArchive(map[string]string{ + "Package.swift": contentManifest1, + "Package@swift-5.6.swift": contentManifest2, + }), + makePackageMetadataJSON(upload.version), + ) - req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) - MakeRequest(t, req, http.StatusUnauthorized) + if upload.formField { + stableURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2) + uploadPackage(t, stableURL, http.StatusCreated, test.WriteZipArchive(map[string]string{"Package.swift": contentManifest1}), "") + } - uploadPackage( - t, - uploadURL, - http.StatusCreated, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - "Package@swift-5.6.swift": contentManifest2, - }), - makePackageMetadataJSON(packageVersion), - ) - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift) - assert.NoError(t, err) - assert.Len(t, pvs, 1) - - pd, err := packages.GetPackageDescriptor(t.Context(), pvs[0]) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.Equal(t, packageID, pd.Package.Name) - assert.Equal(t, packageVersion, pd.Version.Version) - metadata, ok := pd.Metadata.(*swift_module.Metadata) - require.True(t, ok) - assert.Equal(t, packageDescription, metadata.Description) - assert.Len(t, metadata.Manifests, 2) - assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) - assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) - assert.Len(t, pd.VersionProperties, 3) - assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) - - pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[0].ID) - assert.NoError(t, err) - assert.Len(t, pfs, 1) - assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion), pfs[0].Name) - assert.True(t, pfs[0].IsLead) - - uploadPackage( - t, - uploadURL, - http.StatusConflict, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - }), - "", - ) - }) - - t.Run("UploadMultipart", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) { - var body bytes.Buffer - mpw := multipart.NewWriter(&body) - - // Read the source archive content - sourceContent, err := io.ReadAll(sr) + pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift) assert.NoError(t, err) - mpw.WriteField("source-archive", string(sourceContent)) - - if metadata != "" { - mpw.WriteField("metadata", metadata) + versionIndex := 0 + if upload.formField { + require.Len(t, pvs, 3) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests + versionIndex = 1 + } else { + assert.Len(t, pvs, 1) } - mpw.Close() + pd, err := packages.GetPackageDescriptor(t.Context(), pvs[versionIndex]) + assert.NoError(t, err) + assert.NotNil(t, pd.SemVer) + assert.Equal(t, packageID, pd.Package.Name) + assert.Equal(t, upload.version, pd.Version.Version) + metadata, ok := pd.Metadata.(*swift_module.Metadata) + require.True(t, ok) + assert.Equal(t, packageDescription, metadata.Description) + assert.Len(t, metadata.Manifests, 2) + assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) + assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) + assert.Len(t, pd.VersionProperties, 3) + assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) - req := NewRequestWithBody(t, "PUT", url, &body). - SetHeader("Content-Type", mpw.FormDataContentType()). - SetHeader("Accept", swift_router.AcceptJSON). - AddBasicAuth(user.Name) - MakeRequest(t, req, expectedStatus) - } + pfs, err := packages.GetFilesByVersionID(t.Context(), pvs[versionIndex].ID) + assert.NoError(t, err) + assert.Len(t, pfs, 1) + assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, upload.version), pfs[0].Name) + assert.True(t, pfs[0].IsLead) - uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2Beta) + uploadPackage( + t, + uploadURL, + http.StatusConflict, + test.WriteZipArchive(map[string]string{ + "Package.swift": contentManifest1, + }), + "", + ) - req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) - MakeRequest(t, req, http.StatusUnauthorized) - - // Test with metadata as form field - uploadPackage( - t, - uploadURL, - http.StatusCreated, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - "Package@swift-5.6.swift": contentManifest2, - }), - makePackageMetadataJSON(packageVersion2Beta), - ) - - stableURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2) - uploadPackage(t, stableURL, http.StatusCreated, test.WriteZipArchive(map[string]string{"Package.swift": contentManifest1}), "") - - pvs, err := packages.GetVersionsByPackageType(t.Context(), user.ID, packages.TypeSwift) - assert.NoError(t, err) - require.Len(t, pvs, 3) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests - thisPackageVersion := pvs[1] - pd, err := packages.GetPackageDescriptor(t.Context(), thisPackageVersion) - assert.NoError(t, err) - assert.NotNil(t, pd.SemVer) - assert.Equal(t, packageID, pd.Package.Name) - assert.Equal(t, packageVersion2Beta, pd.Version.Version) - metadata, ok := pd.Metadata.(*swift_module.Metadata) - require.True(t, ok) - assert.Equal(t, packageDescription, metadata.Description) - assert.Len(t, metadata.Manifests, 2) - assert.Equal(t, contentManifest1, metadata.Manifests[""].Content) - assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content) - assert.Len(t, pd.VersionProperties, 3) - assert.Equal(t, packageRepositoryURL1, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL)) - - pfs, err := packages.GetFilesByVersionID(t.Context(), thisPackageVersion.ID) - assert.NoError(t, err) - assert.Len(t, pfs, 1) - assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2Beta), pfs[0].Name) - assert.True(t, pfs[0].IsLead) - - uploadPackage( - t, - uploadURL, - http.StatusConflict, - test.WriteZipArchive(map[string]string{ - "Package.swift": contentManifest1, - }), - "", - ) - - invalidURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, "1.0.6-") - uploadPackage(t, invalidURL, http.StatusBadRequest, bytes.NewReader(nil), "") - }) + if upload.formField { + invalidURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, "1.0.6-") + uploadPackage(t, invalidURL, http.StatusBadRequest, bytes.NewReader(nil), "") + } + }) + } t.Run("Download", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 5585ff0cb5a..3008fc67668 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -110,49 +110,40 @@ func TestPackageAPI(t *testing.T) { assert.Equal(t, user.Name, p.Creator.UserName) }) - t.Run("DeleteEntirePackage", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, c := range []struct { + name string + packageName string + suffix string + }{ + {"DeleteEntirePackage", "test-package-entire-delete", ""}, + {"DeletePackageVersion", "test-package-version-delete", "/1.0.1"}, + } { + t.Run(c.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - packageName := "test-package-entire-delete" - for _, version := range []string{"1.0.1", "1.0.2"} { - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version) - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - } + for _, version := range []string{"1.0.1", "1.0.2"} { + url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, c.packageName, version) + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + } - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)). - AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusNoContent) + url := fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, c.packageName) + c.suffix + req := NewRequest(t, "DELETE", url). + AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)). - AddTokenAuth(tokenReadPackage) - MakeRequest(t, req, http.StatusNotFound) - }) + req = NewRequest(t, "GET", url). + AddTokenAuth(tokenReadPackage) + MakeRequest(t, req, http.StatusNotFound) - t.Run("DeletePackageVersion", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - packageName := "test-package-version-delete" - for _, version := range []string{"1.0.1", "1.0.2"} { - url := fmt.Sprintf("/api/packages/%s/generic/%s/%s/file.bin", user.Name, packageName, version) - req := NewRequestWithBody(t, "PUT", url, bytes.NewReader([]byte{1})). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) - } - - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)). - AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.1", user.Name, packageName)). - AddTokenAuth(tokenReadPackage) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.2", user.Name, packageName)). - AddTokenAuth(tokenReadPackage) - MakeRequest(t, req, http.StatusOK) - }) + if c.suffix != "" { + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/1.0.2", user.Name, c.packageName)). + AddTokenAuth(tokenReadPackage) + MakeRequest(t, req, http.StatusOK) + } + }) + } t.Run("ListPackageVersions", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -317,6 +308,20 @@ func TestPackageAccess(t *testing.T) { ExpectedStatus int } + unauthorizedTargets := []Target{ + {admin, http.StatusUnauthorized}, + {inactive, http.StatusUnauthorized}, + {user, http.StatusUnauthorized}, + {limitedUser, http.StatusUnauthorized}, + {privateUser, http.StatusUnauthorized}, + {privateOrgMember, http.StatusUnauthorized}, + {limitedOrgMember, http.StatusUnauthorized}, + {publicOrgMember, http.StatusUnauthorized}, + {privateOrgNoMember, http.StatusUnauthorized}, + {limitedOrgNoMember, http.StatusUnauthorized}, + {publicOrgNoMember, http.StatusUnauthorized}, + } + t.Run("Upload", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -345,36 +350,12 @@ func TestPackageAccess(t *testing.T) { { // Without credentials no upload should be possible Doer: nil, Filename: "nil", - Targets: []Target{ - {admin, http.StatusUnauthorized}, - {inactive, http.StatusUnauthorized}, - {user, http.StatusUnauthorized}, - {limitedUser, http.StatusUnauthorized}, - {privateUser, http.StatusUnauthorized}, - {privateOrgMember, http.StatusUnauthorized}, - {limitedOrgMember, http.StatusUnauthorized}, - {publicOrgMember, http.StatusUnauthorized}, - {privateOrgNoMember, http.StatusUnauthorized}, - {limitedOrgNoMember, http.StatusUnauthorized}, - {publicOrgNoMember, http.StatusUnauthorized}, - }, + Targets: unauthorizedTargets, }, { // Inactive users can't upload anywhere Doer: inactive, Filename: "inactive", - Targets: []Target{ - {admin, http.StatusUnauthorized}, - {inactive, http.StatusUnauthorized}, - {user, http.StatusUnauthorized}, - {limitedUser, http.StatusUnauthorized}, - {privateUser, http.StatusUnauthorized}, - {privateOrgMember, http.StatusUnauthorized}, - {limitedOrgMember, http.StatusUnauthorized}, - {publicOrgMember, http.StatusUnauthorized}, - {privateOrgNoMember, http.StatusUnauthorized}, - {limitedOrgNoMember, http.StatusUnauthorized}, - {publicOrgNoMember, http.StatusUnauthorized}, - }, + Targets: unauthorizedTargets, }, { // Normal users can upload to self and orgs in which they are members and have package write access Doer: user, @@ -445,20 +426,8 @@ func TestPackageAccess(t *testing.T) { }, }, { // Inactive users have no access - Doer: inactive, - Targets: []Target{ - {admin, http.StatusUnauthorized}, - {inactive, http.StatusUnauthorized}, - {user, http.StatusUnauthorized}, - {limitedUser, http.StatusUnauthorized}, - {privateUser, http.StatusUnauthorized}, - {privateOrgMember, http.StatusUnauthorized}, - {limitedOrgMember, http.StatusUnauthorized}, - {publicOrgMember, http.StatusUnauthorized}, - {privateOrgNoMember, http.StatusUnauthorized}, - {limitedOrgNoMember, http.StatusUnauthorized}, - {publicOrgNoMember, http.StatusUnauthorized}, - }, + Doer: inactive, + Targets: unauthorizedTargets, }, { // Normal users can access self, public or limited users/orgs and private orgs in which they are members Doer: user, diff --git a/tests/integration/api_private_serv_test.go b/tests/integration/api_private_serv_test.go index 30abf963598..296bc9b011c 100644 --- a/tests/integration/api_private_serv_test.go +++ b/tests/integration/api_private_serv_test.go @@ -44,44 +44,38 @@ func TestAPIPrivateServ(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) defer cancel() - // Can push to a repo we own - results, extra := private.ServCommand(ctx, 1, "user2", "repo1", perm.AccessModeWrite, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.Empty(t, results.UserExtDoerData) - assert.Equal(t, int64(1), results.PublicKeyID) - assert.Equal(t, "user2", results.UserName) - assert.Equal(t, int64(2), results.UserID) - assert.Equal(t, "user2", results.OwnerName) - assert.Equal(t, "repo1", results.RepoName) - assert.Equal(t, int64(1), results.RepoID) - - // Cannot push to a private repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_private_1", perm.AccessModeWrite, "git-upload-pack", "") - assert.Error(t, extra.Error) - assert.Empty(t, results) - - // Cannot pull from a private repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") - assert.Error(t, extra.Error) - assert.Empty(t, results) - - // Can pull from a public repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeRead, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.Empty(t, results.UserExtDoerData) - assert.Equal(t, int64(1), results.PublicKeyID) - assert.Equal(t, "user2", results.UserName) - assert.Equal(t, int64(2), results.UserID) - assert.Equal(t, "user15", results.OwnerName) - assert.Equal(t, "big_test_public_1", results.RepoName) - assert.Equal(t, int64(17), results.RepoID) - - // Cannot push to a public repo we're not associated with - results, extra = private.ServCommand(ctx, 1, "user15", "big_test_public_1", perm.AccessModeWrite, "git-upload-pack", "") - assert.Error(t, extra.Error) - assert.Empty(t, results) + for _, testCase := range []struct { + ownerName, repoName string + mode perm.AccessMode + repoID int64 + }{ + // Can push to a repo we own + {"user2", "repo1", perm.AccessModeWrite, 1}, + // Cannot push to a private repo we're not associated with + {"user15", "big_test_private_1", perm.AccessModeWrite, 0}, + // Cannot pull from a private repo we're not associated with + {"user15", "big_test_private_1", perm.AccessModeRead, 0}, + // Can pull from a public repo we're not associated with + {"user15", "big_test_public_1", perm.AccessModeRead, 17}, + // Cannot push to a public repo we're not associated with + {"user15", "big_test_public_1", perm.AccessModeWrite, 0}, + } { + results, extra := private.ServCommand(ctx, 1, testCase.ownerName, testCase.repoName, testCase.mode, "git-upload-pack", "") + if testCase.repoID == 0 { + assert.Error(t, extra.Error) + assert.Empty(t, results) + } else { + assert.NoError(t, extra.Error) + assert.False(t, results.IsWiki) + assert.Empty(t, results.UserExtDoerData) + assert.Equal(t, int64(1), results.PublicKeyID) + assert.Equal(t, "user2", results.UserName) + assert.Equal(t, int64(2), results.UserID) + assert.Equal(t, testCase.ownerName, results.OwnerName) + assert.Equal(t, testCase.repoName, results.RepoName) + assert.Equal(t, testCase.repoID, results.RepoID) + } + } // Add reading deploy key testContent := "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGXEEzWmm1dxb+57RoK5KVCL0w2eNv9cqJX2AGGVlkFsVDhOXHzsadS3LTK4VlEbbrDMJdoti9yM8vclA8IeRacAAAAEc3NoOg== nocomment" @@ -90,7 +84,7 @@ func TestAPIPrivateServ(t *testing.T) { // Can pull from repo we're a deploy-key for deployKeyUser := user.NewDeployKeyUser() - results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") + results, extra := private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_1", perm.AccessModeRead, "git-upload-pack", "") assert.NoError(t, extra.Error) assert.False(t, results.IsWiki) assert.NotEmpty(t, results.UserExtDoerData) @@ -126,28 +120,22 @@ func TestAPIPrivateServ(t *testing.T) { assert.Error(t, extra.Error) assert.Empty(t, results) - // Can pull from repo we're a writing deploy key for - results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeRead, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.NotEmpty(t, results.UserExtDoerData) - assert.Equal(t, deployKey.KeyID, results.PublicKeyID) - assert.Equal(t, deployKeyUser.Name, results.UserName) - assert.Equal(t, deployKeyUser.ID, results.UserID) - assert.Equal(t, "user15", results.OwnerName) - assert.Equal(t, "big_test_private_2", results.RepoName) - assert.Equal(t, int64(20), results.RepoID) - - // Can push to repo we're a writing deploy key for - results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", perm.AccessModeWrite, "git-upload-pack", "") - assert.NoError(t, extra.Error) - assert.False(t, results.IsWiki) - assert.NotEmpty(t, results.UserExtDoerData) - assert.Equal(t, deployKey.KeyID, results.PublicKeyID) - assert.Equal(t, deployKeyUser.Name, results.UserName) - assert.Equal(t, deployKeyUser.ID, results.UserID) - assert.Equal(t, "user15", results.OwnerName) - assert.Equal(t, "big_test_private_2", results.RepoName) - assert.Equal(t, int64(20), results.RepoID) + for _, mode := range []perm.AccessMode{ + // Can pull from repo we're a writing deploy key for + perm.AccessModeRead, + // Can push to repo we're a writing deploy key for + perm.AccessModeWrite, + } { + results, extra = private.ServCommand(ctx, deployKey.KeyID, "user15", "big_test_private_2", mode, "git-upload-pack", "") + assert.NoError(t, extra.Error) + assert.False(t, results.IsWiki) + assert.NotEmpty(t, results.UserExtDoerData) + assert.Equal(t, deployKey.KeyID, results.PublicKeyID) + assert.Equal(t, deployKeyUser.Name, results.UserName) + assert.Equal(t, deployKeyUser.ID, results.UserID) + assert.Equal(t, "user15", results.OwnerName) + assert.Equal(t, "big_test_private_2", results.RepoName) + assert.Equal(t, int64(20), results.RepoID) + } }) } diff --git a/tests/integration/api_public_only_test.go b/tests/integration/api_public_only_test.go index 3a24d2b4d20..16a79d696ce 100644 --- a/tests/integration/api_public_only_test.go +++ b/tests/integration/api_public_only_test.go @@ -23,28 +23,20 @@ func TestAPIUserReposPublicOnly(t *testing.T) { defer tests.PrepareTestEnv(t)() token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/user/repos"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var repos []api.Repository - DecodeJSON(t, resp, &repos) - assert.NotEmpty(t, repos) - for _, repo := range repos { - assert.False(t, repo.Private) + for index, url := range []string{"/api/v1/user/repos", "/api/v1/users/user2/repos"} { + req := NewRequest(t, "GET", url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &repos) + assert.NotEmpty(t, repos) + for _, repo := range repos { + assert.False(t, repo.Private) + } + assert.NotContains(t, repoNames(repos), "user2/repo2") + if index == 1 { + assert.Equal(t, strconv.Itoa(len(repos)), resp.Header().Get("X-Total-Count")) + } } - assert.NotContains(t, repoNames(repos), "user2/repo2") - - req = NewRequest(t, "GET", "/api/v1/users/user2/repos"). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &repos) - assert.NotEmpty(t, repos) - for _, repo := range repos { - assert.False(t, repo.Private) - } - assert.NotContains(t, repoNames(repos), "user2/repo2") - assert.Equal(t, strconv.Itoa(len(repos)), resp.Header().Get("X-Total-Count")) } func repoNames(repos []api.Repository) []string { @@ -55,77 +47,68 @@ func repoNames(repos []api.Repository) []string { return names } -func TestAPIRepoByIDPublicOnly(t *testing.T) { +func TestAPIPublicOnlyReadEndpoints(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/repositories/1"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + t.Run("RepoByID", func(t *testing.T) { + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/repositories/1"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", "/api/v1/repositories/2"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} + req = NewRequest(t, "GET", "/api/v1/repositories/2"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) -func TestAPIActivityFeedsPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ActivityFeeds", func(t *testing.T) { + var activities []api.Activity + for _, testCase := range []struct { + url string + scope auth_model.AccessTokenScope + tokenOwners []string + }{ + {"/api/v1/users/user2/activities/feeds", auth_model.AccessTokenScopeReadUser, []string{"user2", "user1"}}, + {"/api/v1/orgs/org3/activities/feeds", auth_model.AccessTokenScopeReadOrganization, []string{"user2"}}, + } { + token := getUserToken(t, "user2", testCase.scope) + req := NewRequest(t, "GET", testCase.url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &activities) + assert.NotEmpty(t, activities) - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser) - req := NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var activities []api.Activity - DecodeJSON(t, resp, &activities) - assert.NotEmpty(t, activities) + for _, tokenOwner := range testCase.tokenOwners { + publicToken := getUserToken(t, tokenOwner, testCase.scope, auth_model.AccessTokenScopePublicOnly) + req = NewRequest(t, "GET", testCase.url).AddTokenAuth(publicToken) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &activities) + assertPublicActivitiesOnly(t, activities) + } + } + }) - for _, tokenOwner := range []string{"user2", "user1"} { - publicToken := getUserToken(t, tokenOwner, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) - req = NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds"). - AddTokenAuth(publicToken) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &activities) - assertPublicActivitiesOnly(t, activities) - } + t.Run("OrgMembership", func(t *testing.T) { + // user2 is a member of the private org private_org35 + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - orgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization) - req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds"). - AddTokenAuth(orgToken) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &activities) - assert.NotEmpty(t, activities) + // a full org-scoped token can read the membership permissions + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) - publicOrgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) - req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds"). - AddTokenAuth(publicOrgToken) - resp = MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &activities) - assertPublicActivitiesOnly(t, activities) -} + // a public-only token must not disclose permissions for a private org + publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) + req = NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(publicToken) + MakeRequest(t, req, http.StatusNotFound) -func TestAPIOrgMembershipPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user2 is a member of the private org private_org35 - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - - // a full org-scoped token can read the membership permissions - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - - // a public-only token must not disclose permissions for a private org - publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly) - req = NewRequestf(t, "GET", "/api/v1/users/user2/orgs/%s/permissions", org.Name).AddTokenAuth(publicToken) - MakeRequest(t, req, http.StatusNotFound) - - var members, publicMembers []*api.User - DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(token), http.StatusOK), &members) - DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(publicToken), http.StatusOK), &publicMembers) - assert.Len(t, members, 3) - assert.Len(t, publicMembers, 2) - MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(token), http.StatusNoContent) - MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(publicToken), http.StatusSeeOther) + var members, publicMembers []*api.User + DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(token), http.StatusOK), &members) + DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members").AddTokenAuth(publicToken), http.StatusOK), &publicMembers) + assert.Len(t, members, 3) + assert.Len(t, publicMembers, 2) + MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(token), http.StatusNoContent) + MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/members/user4").AddTokenAuth(publicToken), http.StatusSeeOther) + }) } func TestAPITeamReposPublicOnly(t *testing.T) { diff --git a/tests/integration/api_pull_commits_test.go b/tests/integration/api_pull_commits_test.go deleted file mode 100644 index 3c326046228..00000000000 --- a/tests/integration/api_pull_commits_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - issues_model "gitea.dev/models/issues" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" - api "gitea.dev/modules/structs" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAPIPullCommits(t *testing.T) { - defer tests.PrepareTestEnv(t)() - pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) - assert.NoError(t, pr.LoadIssue(t.Context())) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pr.HeadRepoID}) - - req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pr.Index) - resp := MakeRequest(t, req, http.StatusOK) - - commits := DecodeJSON(t, resp, []*api.Commit{}) - - require.Len(t, commits, 2) - - assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", commits[0].SHA) - assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", commits[1].SHA) - - assert.NotEmpty(t, commits[0].Files) - assert.NotEmpty(t, commits[1].Files) - assert.NotNil(t, commits[0].RepoCommit.Verification) - assert.NotNil(t, commits[1].RepoCommit.Verification) -} - -// TODO add tests for already merged PR and closed PR diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go index fa82c909565..6ab2fb4546e 100644 --- a/tests/integration/api_pull_review_test.go +++ b/tests/integration/api_pull_review_test.go @@ -30,6 +30,28 @@ import ( func TestAPIPullReview(t *testing.T) { defer tests.PrepareTestEnv(t)() + + // TODO add tests for already merged PR and closed PR + t.Run("Commits", func(t *testing.T) { + pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + assert.NoError(t, pr.LoadIssue(t.Context())) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pr.HeadRepoID}) + + req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/commits", repo.OwnerName, repo.Name, pr.Index) + resp := MakeRequest(t, req, http.StatusOK) + + commits := DecodeJSON(t, resp, []*api.Commit{}) + + require.Len(t, commits, 2) + + assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", commits[0].SHA) + assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", commits[1].SHA) + + assert.NotEmpty(t, commits[0].Files) + assert.NotEmpty(t, commits[1].Files) + assert.NotNil(t, commits[0].RepoCommit.Verification) + assert.NotNil(t, commits[1].RepoCommit.Verification) + }) t.Run("General", testAPIPullReviewGeneral) t.Run("CommentReply", testAPIPullReviewCommentReply) } @@ -273,46 +295,44 @@ func TestAPIPullReviewRequest(t *testing.T) { pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3}) assert.NoError(t, pullIssue.LoadAttributes(t.Context())) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID}) + requestReview := func(method string, repo *repo_model.Repository, index int64, token string, options *api.PullReviewRequestOptions, expectedStatus int) { + req := NewRequestWithJSON(t, method, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, index), options).AddTokenAuth(token) + MakeRequest(t, req, expectedStatus) + } // Test add Review Request session := loginUser(t, "user2") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com", "user8"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) // poster of pr can't be reviewer - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"user1"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + }, http.StatusUnprocessableEntity) // test user not exist - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"testOther"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + }, http.StatusNotFound) // Test Remove Review Request session2 := loginUser(t, "user4") token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo, pullIssue.Index, token2, &api.PullReviewRequestOptions{ Reviewers: []string{"user4"}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // doer is not admin - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo, pullIssue.Index, token2, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) + }, http.StatusUnprocessableEntity) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo, pullIssue.Index, token, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // a collaborator can add/remove a review request pullIssue21 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 21}) @@ -320,42 +340,36 @@ func TestAPIPullReviewRequest(t *testing.T) { pull21Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue21.RepoID}) // repo60 user38Session := loginUser(t, "user38") user38Token := getTokenForLoggedInUser(t, user38Session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, pull21Repo, pullIssue21.Index, user38Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com"}, - }).AddTokenAuth(user38Token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, pull21Repo, pullIssue21.Index, user38Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com"}, - }).AddTokenAuth(user38Token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // the poster of the PR can add/remove a review request user39Session := loginUser(t, "user39") user39Token := getTokenForLoggedInUser(t, user39Session, auth_model.AccessTokenScopeWriteRepository) - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, pull21Repo, pullIssue21.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(user39Token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, pull21Repo, pullIssue21.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user8"}, - }).AddTokenAuth(user39Token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // user with read permission on pull requests unit can add/remove a review request pullIssue22 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 22}) assert.NoError(t, pullIssue22.LoadAttributes(t.Context())) pull22Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue22.RepoID}) // repo61 - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, pull22Repo, pullIssue22.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user38"}, - }).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) // user39 is from a team with read permission on pull requests unit - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, pull22Repo, pullIssue22.Index, user39Token, &api.PullReviewRequestOptions{ Reviewers: []string{"user38"}, - }).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // user39 is from a team with read permission on pull requests unit // Test team review request pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12}) @@ -363,37 +377,29 @@ func TestAPIPullReviewRequest(t *testing.T) { repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID}) // Test add Team Review Request - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"team1", "owners"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + }, http.StatusCreated) // Test add Team Review Request to not allowned - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"test_team"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) + }, http.StatusUnprocessableEntity) // Test add Team Review Request to not exist - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"not_exist_team"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + }, http.StatusNotFound) // Test Remove team Review Request - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{ + requestReview(http.MethodDelete, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{ TeamReviewers: []string{"team1"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + }, http.StatusNoContent) // empty request test - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) + requestReview(http.MethodPost, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{}, http.StatusCreated) - req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + requestReview(http.MethodDelete, repo3, pullIssue12.Index, token, &api.PullReviewRequestOptions{}, http.StatusNoContent) } func TestAPIPullReviewCommentResolveEndpoints(t *testing.T) { @@ -484,27 +490,20 @@ func TestAPIPullReviewStayDismissed(t *testing.T) { token8 := getTokenForLoggedInUser(t, session8, auth_model.AccessTokenScopeWriteRepository) // user2 request user8 - req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ - Reviewers: []string{user8.LoginName}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - reviewsCountCheck(t, + for _, name := range []string{ "check we have only one review request", - pullIssue.ID, user8.ID, 0, 1, 1, false) - - // user2 request user8 again, it is expected to be ignored - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ - Reviewers: []string{user8.LoginName}, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - reviewsCountCheck(t, + // user2 request user8 again, it is expected to be ignored "check we have only one review request, even after re-request it again", - pullIssue.ID, user8.ID, 0, 1, 1, false) + } { + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{ + Reviewers: []string{user8.LoginName}, + }).AddTokenAuth(token2) + MakeRequest(t, req, http.StatusCreated) + reviewsCountCheck(t, name, pullIssue.ID, user8.ID, 0, 1, 1, false) + } // user8 reviews it as accept - req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ + req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ Event: "APPROVED", Body: "lgtm", }).AddTokenAuth(token8) diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index b2464d10a20..d7ddced4ba7 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -144,6 +144,30 @@ func TestAPIViewPulls(t *testing.T) { })) } + t.Run("APIViewPullsByBaseHead", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name). + AddTokenAuth(ctx.Token) + resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + + pull := DecodeJSON(t, resp, &api.PullRequest{}) + assert.EqualValues(t, 3, pull.Index) + assert.EqualValues(t, 2, pull.ID) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name). + AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("APICommitPullRequest", func(t *testing.T) { + mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3" + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, http.StatusOK) + + invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234" + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) + ctx.Session.MakeRequest(t, req, http.StatusNotFound) + }) + t.Run("HeadRepoHiddenWhenInaccessible", func(t *testing.T) { session := loginUser(t, "user31") forkToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) @@ -173,26 +197,6 @@ func TestAPIViewPulls(t *testing.T) { }) } -func TestAPIViewPullsByBaseHead(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch2", owner.Name, repo.Name). - AddTokenAuth(ctx.Token) - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) - - pull := DecodeJSON(t, resp, &api.PullRequest{}) - assert.EqualValues(t, 3, pull.Index) - assert.EqualValues(t, 2, pull.ID) - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/master/branch-not-exist", owner.Name, repo.Name). - AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, req, http.StatusNotFound) -} - // TestAPIMergePullWIP ensures that we can't merge a WIP pull request func TestAPIMergePullWIP(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -611,22 +615,6 @@ func doAPIGetPullFiles(ctx APITestContext, pr *api.PullRequest, callback func(*t } } -func TestAPICommitPullRequest(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) - - mergedCommitSHA := "1a8823cd1a9549fde083f992f6b9b87a7ab74fb3" - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, mergedCommitSHA).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, req, http.StatusOK) - - invalidCommitSHA := "abcd1234abcd1234abcd1234abcd1234abcd1234" - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/commits/%s/pull", owner.Name, repo.Name, invalidCommitSHA).AddTokenAuth(ctx.Token) - ctx.Session.MakeRequest(t, req, http.StatusNotFound) -} - func TestAPIViewPullFilesWithHeadRepoDeleted(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index fb6f04ada24..51f909f1746 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -33,6 +33,30 @@ import ( func TestAPIReleaseRead(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("CreateProtectedTagRelease", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) + writer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, writer.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() + + commit, err := gitRepo.GetBranchCommit(t.Context(), "master") + assert.NoError(t, err) + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/releases", repo.OwnerName, repo.Name), &api.CreateReleaseOption{ + TagName: "v0.0.1", + Title: "v0.0.1", + IsDraft: false, + IsPrerelease: false, + Target: commit.ID.String(), + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) t.Run("DraftReleaseAttachmentAccess", testAPIDraftReleaseAttachmentAccess) t.Run("ListReleasesWithWriteToken", testAPIListReleasesWithWriteToken) t.Run("ListReleasesWithReadToken", testAPIListReleasesWithReadToken) @@ -225,76 +249,43 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { assert.Equal(t, rel.Note, newRelease.Note) } -func TestAPICreateProtectedTagRelease(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func TestAPICreateRelease(t *testing.T) { + for _, scenario := range []string{"InvalidTarget", "DefaultBranch", "ExistingTag"} { + t.Run(scenario, func(t *testing.T) { + if scenario == "DefaultBranch" { + defer tests.PrintCurrentTest(t)() + } else { + defer tests.PrepareTestEnv(t)() + } - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - writer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - session := loginUser(t, writer.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, owner.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() + if scenario == "ExistingTag" { + gitRepo, err := git.OpenRepository(t.Context(), repo) + assert.NoError(t, err) + defer gitRepo.Close() - commit, err := gitRepo.GetBranchCommit(t.Context(), "master") - assert.NoError(t, err) + err = gitRepo.CreateTag(t.Context(), "v0.0.1", "master") + assert.NoError(t, err) + } - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/releases", repo.OwnerName, repo.Name), &api.CreateReleaseOption{ - TagName: "v0.0.1", - Title: "v0.0.1", - IsDraft: false, - IsPrerelease: false, - Target: commit.ID.String(), - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) -} + if scenario == "InvalidTarget" { + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name) + req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ + TagName: "i-point-to-an-invalid-target", + Title: "Invalid Target", + Target: "invalid-target", + }).AddTokenAuth(token) -func TestAPICreateReleaseToDefaultBranch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") -} - -func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - gitRepo, err := git.OpenRepository(t.Context(), repo) - assert.NoError(t, err) - defer gitRepo.Close() - - err = gitRepo.CreateTag(t.Context(), "v0.0.1", "master") - assert.NoError(t, err) - - createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") -} - -func TestAPICreateReleaseGivenInvalidTarget(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name) - req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ - TagName: "i-point-to-an-invalid-target", - Title: "Invalid Target", - Target: "invalid-target", - }).AddTokenAuth(token) - - MakeRequest(t, req, http.StatusNotFound) + MakeRequest(t, req, http.StatusNotFound) + } else { + createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") + } + }) + } } func testAPIGetLatestRelease(t *testing.T) { diff --git a/tests/integration/api_repo_avatar_test.go b/tests/integration/api_repo_avatar_test.go index 431e1bee20e..8c43629a9d6 100644 --- a/tests/integration/api_repo_avatar_test.go +++ b/tests/integration/api_repo_avatar_test.go @@ -29,48 +29,33 @@ func TestAPIUpdateRepoAvatar(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository) - // Test what happens if you use a valid image + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) + avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) assert.NoError(t, err) if err != nil { assert.FailNow(t, "Unable to open avatar.png") } - opts := api.UpdateRepoAvatarOption{ - Image: base64.StdEncoding.EncodeToString(avatar), + for _, avatarCase := range []struct { + image string + status int + }{ + // Test what happens if you use a valid image + {base64.StdEncoding.EncodeToString(avatar), http.StatusNoContent}, + // Test what happens if you don't have a valid Base64 string + {"Invalid", http.StatusBadRequest}, + // Test what happens if you use a file that is not an image + {base64.StdEncoding.EncodeToString([]byte("This is not an image")), http.StatusInternalServerError}, + } { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &api.UpdateRepoAvatarOption{Image: avatarCase.image}). + AddTokenAuth(token) + MakeRequest(t, req, avatarCase.status) } - - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - // Test what happens if you don't have a valid Base64 string - opts = api.UpdateRepoAvatarOption{ - Image: "Invalid", - } - - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) - - // Test what happens if you use a file that is not an image - opts = api.UpdateRepoAvatarOption{ - Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")), - } - - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name), &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusInternalServerError) -} - -func TestAPIDeleteRepoAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/avatar", repo.OwnerName, repo.Name)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) } diff --git a/tests/integration/api_repo_branch_test.go b/tests/integration/api_repo_branch_test.go index 864e351c8d0..2980647c782 100644 --- a/tests/integration/api_repo_branch_test.go +++ b/tests/integration/api_repo_branch_test.go @@ -18,130 +18,127 @@ import ( "gitea.dev/modules/json" "gitea.dev/modules/setting" api "gitea.dev/modules/structs" - "gitea.dev/tests" "github.com/stretchr/testify/assert" ) -func TestAPIRepoBranchesPlain(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - session := loginUser(t, user1.LowerName) +func TestAPIRepoBranches(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _url *url.URL) { + t.Run("Plain", func(t *testing.T) { + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user1.LowerName) - // public-only token cannot see a private repo - publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository) - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo - MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + // public-only token cannot see a private repo + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository) + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo + MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) - bs, err := io.ReadAll(resp.Body) - assert.NoError(t, err) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) - var branches []*api.Branch - assert.NoError(t, json.Unmarshal(bs, &branches)) - assert.Len(t, branches, 2) - assert.Equal(t, "test_branch", branches[0].Name) - assert.Equal(t, "master", branches[1].Name) + var branches []*api.Branch + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 2) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) - link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) - MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) + MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - var branch api.Branch - assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.Equal(t, "test_branch", branch.Name) + resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch)) + assert.Equal(t, "test_branch", branch.Name) - MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) - req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) - resp = MakeRequest(t, req, http.StatusCreated) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - var branch2 api.Branch - assert.NoError(t, json.Unmarshal(bs, &branch2)) - assert.Equal(t, "test_branch2", branch2.Name) - assert.Equal(t, branch.Commit.ID, branch2.Commit.ID) + req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) + req.Header.Add("Content-Type", "application/json") + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + resp = MakeRequest(t, req, http.StatusCreated) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch2 api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch2)) + assert.Equal(t, "test_branch2", branch2.Name) + assert.Equal(t, branch.Commit.ID, branch2.Commit.ID) - resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) + resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) - branches = []*api.Branch{} - assert.NoError(t, json.Unmarshal(bs, &branches)) - assert.Len(t, branches, 3) - assert.Equal(t, "test_branch", branches[0].Name) - assert.Equal(t, "test_branch2", branches[1].Name) - assert.Equal(t, "master", branches[2].Name) + branches = []*api.Branch{} + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 3) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "test_branch2", branches[1].Name) + assert.Equal(t, "master", branches[2].Name) - link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) - MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) + link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound) - MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent) - assert.NoError(t, err) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent) + assert.NoError(t, err) + }) + + t.Run("Mirror", func(t *testing.T) { + repo5 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user1.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo5.Name)) // a mirror repo + resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + + var branches []*api.Branch + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 2) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) + + link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo5.Name)) + resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch)) + assert.Equal(t, "test_branch", branch.Name) + + req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) + req.Header.Add("Content-Type", "application/json") + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + resp = MakeRequest(t, req, http.StatusForbidden) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) + + resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) + }) + + t.Run("Search", func(t *testing.T) { + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository) + + // "test" matches "test_branch" but not "master" + resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK) + branches := DecodeJSON(t, resp, []api.Branch{}) + assert.Len(t, branches, 1) + assert.Equal(t, "test_branch", branches[0].Name) + + // no match returns empty list + resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK) + branches = DecodeJSON(t, resp, []api.Branch{}) + assert.Empty(t, branches) + }) }) } - -func TestAPIRepoBranchesMirror(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo5 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - session := loginUser(t, user1.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo5.Name)) // a mirror repo - resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) - bs, err := io.ReadAll(resp.Body) - assert.NoError(t, err) - - var branches []*api.Branch - assert.NoError(t, json.Unmarshal(bs, &branches)) - assert.Len(t, branches, 2) - assert.Equal(t, "test_branch", branches[0].Name) - assert.Equal(t, "master", branches[1].Name) - - link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo5.Name)) - resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - var branch api.Branch - assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.Equal(t, "test_branch", branch.Name) - - req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) - req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) - resp = MakeRequest(t, req, http.StatusForbidden) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) - - resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden) - bs, err = io.ReadAll(resp.Body) - assert.NoError(t, err) - assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs)) -} - -func TestAPIRepoBranchesSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository) - - // "test" matches "test_branch" but not "master" - resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK) - branches := DecodeJSON(t, resp, []api.Branch{}) - assert.Len(t, branches, 1) - assert.Equal(t, "test_branch", branches[0].Name) - - // no match returns empty list - resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK) - branches = DecodeJSON(t, resp, []api.Branch{}) - assert.Empty(t, branches) -} diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index 00afa3ea479..8bd821a8f9a 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -8,14 +8,17 @@ import ( "testing" auth_model "gitea.dev/models/auth" + "gitea.dev/models/organization" "gitea.dev/models/perm" repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" api "gitea.dev/modules/structs" + repo_service "gitea.dev/services/repository" "gitea.dev/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAPIRepoCollaboratorPermission(t *testing.T) { @@ -41,41 +44,27 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { assert.Equal(t, api.AccessLevelNameOwner, repoPermission.Permission) }) - t.Run("CollaboratorWithReadAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead)) + for _, testCase := range []struct { + name string + mode perm.AccessMode + permission api.AccessLevelName + }{ + {"Read", perm.AccessModeRead, api.AccessLevelNameRead}, + {"Write", perm.AccessModeWrite, api.AccessLevelNameWrite}, + {"Admin", perm.AccessModeAdmin, api.AccessLevelNameAdmin}, + } { + t.Run("CollaboratorWith"+testCase.name+"Access", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWith"+testCase.name+"Access", doAPIAddCollaborator(testCtx, user4.Name, testCase.mode)) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). + AddTokenAuth(testCtx.Token) + resp := MakeRequest(t, req, http.StatusOK) - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) + repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) - }) - - t.Run("CollaboratorWithWriteAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite)) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) - - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - - assert.Equal(t, api.AccessLevelNameWrite, repoPermission.Permission) - }) - - t.Run("CollaboratorWithAdminAccess", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin)) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user4.Name). - AddTokenAuth(testCtx.Token) - resp := MakeRequest(t, req, http.StatusOK) - - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - - assert.Equal(t, api.AccessLevelNameAdmin, repoPermission.Permission) - }) + assert.Equal(t, testCase.permission, repoPermission.Permission) + }) + } t.Run("CollaboratorNotFound", func(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, "non-existent-user"). @@ -89,47 +78,36 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { doAPIAddCollaborator(ctx, user34.Name, perm.AccessModeAdmin)(t) }) - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) + for _, readOwnPermission := range []bool{true, false} { + t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { + t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) + _session := loginUser(t, user5.Name) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). + AddTokenAuth(_testCtx.Token) + resp := _session.MakeRequest(t, req, http.StatusOK) - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) + repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) + assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) - t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { - session := loginUser(t, user5.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + if readOwnPermission { + t.Run("CollaboratorCanReadOwnPermission", func(t *testing.T) { + session := loginUser(t, user5.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) - repoCollPerm := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) + repoCollPerm := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - assert.Equal(t, api.AccessLevelNameRead, repoCollPerm.Permission) + assert.Equal(t, api.AccessLevelNameRead, repoCollPerm.Permission) + }) + } }) - }) - - t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) { - t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) - - _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission", repo2Owner.Name, repo2.Name, user5.Name). - AddTokenAuth(_testCtx.Token) - resp := _session.MakeRequest(t, req, http.StatusOK) - - repoPermission := DecodeJSON(t, resp, &api.RepoCollaboratorPermission{}) - - assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) - }) + } t.Run("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) { t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin)) @@ -147,3 +125,36 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { assert.Equal(t, api.AccessLevelNameRead, repoPermission.Permission) }) } + +func TestAPIRepositoryDelete(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("AdminNoPermToDeleteRepo", func(t *testing.T) { + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) + teams, err := organization.GetUserOrgTeams(t.Context(), org.ID, doer.ID) + require.NoError(t, err) + require.Len(t, teams, 1) + + team := teams[0] + assert.Equal(t, perm.AccessModeAdmin, team.AccessMode) + assert.True(t, team.CanCreateOrgRepo) + token := getUserToken(t, doer.Name, auth_model.AccessTokenScopeWriteRepository) + + unrelatedRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "unrelated-admin-team"}) + require.NoError(t, err) + + targetRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "target-admin-team"}) + require.NoError(t, err) + require.NoError(t, repo_service.TeamAddRepository(t.Context(), team, targetRepo)) + + req := NewRequest(t, "DELETE", "/api/v1/repos/"+unrelatedRepo.FullName()).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: unrelatedRepo.ID}) + + req = NewRequest(t, "DELETE", "/api/v1/repos/"+targetRepo.FullName()).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &repo_model.Repository{ID: targetRepo.ID}) + }) +} diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index 57f943c5adf..c44f9608b66 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -143,30 +143,23 @@ func TestAPIDownloadCompareDiffOrPatch(t *testing.T) { assert.True(t, strings.HasPrefix(body, "From "), "patch output should start with a format-patch header, got: %q", body[:min(40, len(body))]) }) - t.Run("CommitToCommitDiff", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f?output=diff").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) - - t.Run("BranchToCommitDiff", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - + for _, testCase := range []struct { + name string + url string + }{ + {"CommitToCommitDiff", "/api/v1/repos/user2/repo20/compare/808038d2f71b0ab02099...c8e31bc7688741a5287f?output=diff"}, // 8babce96... is the head of remove-files-b; pairing it with add-csv guarantees a non-empty diff. - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv...8babce967f21b9dfa6987f943b91093dac58a4f0?output=diff").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) + {"BranchToCommitDiff", "/api/v1/repos/user2/repo20/compare/add-csv...8babce967f21b9dfa6987f943b91093dac58a4f0?output=diff"}, + {"TwoDotSeparator", "/api/v1/repos/user2/repo20/compare/add-csv..remove-files-b?output=diff"}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("TwoDotSeparator", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo20/compare/add-csv..remove-files-b?output=diff").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) + req := NewRequest(t, "GET", testCase.url).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "diff --git ") + }) + } t.Run("SlashedBranchName", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_repo_delete_test.go b/tests/integration/api_repo_delete_test.go deleted file mode 100644 index ae566c42804..00000000000 --- a/tests/integration/api_repo_delete_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/organization" - "gitea.dev/models/perm" - repo_model "gitea.dev/models/repo" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - repo_service "gitea.dev/services/repository" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAPIRepositoryDelete(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - t.Run("AdminNoPermToDeleteRepo", func(t *testing.T) { - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - teams, err := organization.GetUserOrgTeams(t.Context(), org.ID, doer.ID) - require.NoError(t, err) - require.Len(t, teams, 1) - - team := teams[0] - assert.Equal(t, perm.AccessModeAdmin, team.AccessMode) - assert.True(t, team.CanCreateOrgRepo) - token := getUserToken(t, doer.Name, auth_model.AccessTokenScopeWriteRepository) - - unrelatedRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "unrelated-admin-team"}) - require.NoError(t, err) - - targetRepo, err := repo_service.CreateRepository(t.Context(), owner, org, repo_service.CreateRepoOptions{Name: "target-admin-team"}) - require.NoError(t, err) - require.NoError(t, repo_service.TeamAddRepository(t.Context(), team, targetRepo)) - - req := NewRequest(t, "DELETE", "/api/v1/repos/"+unrelatedRepo.FullName()).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: unrelatedRepo.ID}) - - req = NewRequest(t, "DELETE", "/api/v1/repos/"+targetRepo.FullName()).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &repo_model.Repository{ID: targetRepo.ID}) - }) -} diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 631a9248a3d..47bf8fa811b 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -136,11 +136,10 @@ func TestAPICreateFile(t *testing.T) { fileID := 0 // Get user2's token - session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + session2 := loginUser(t, user2.Name) + token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) // Get user4's token - session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + token4 := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) // Test creating a file in repo1 which user2 owns, try both with branch and empty branch for _, branch := range [...]string{ @@ -222,54 +221,42 @@ func TestAPICreateFile(t *testing.T) { apiError := DecodeJSON(t, resp, &context.APIError{}) assert.Equal(t, expectedAPIError, apiError) - // Test creating a file in repo1 by user4 who does not have write access - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" where user2 is a collaborator - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" with no user token - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - createFileOptions = getCreateFileOptions() - fileID++ - treePath = fmt.Sprintf("new/file%d.txt", fileID) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + for _, testCase := range []struct { + owner *user_model.User + repo *repo_model.Repository + token string + expectedStatus int + }{ + // Test creating a file in repo1 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } { + createFileOptions = getCreateFileOptions() + fileID++ + treePath = fmt.Sprintf("new/file%d.txt", fileID) + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCase.owner.Name, testCase.repo.Name, treePath), &createFileOptions) + if testCase.token != "" { + req.AddTokenAuth(testCase.token) + } + MakeRequest(t, req, testCase.expectedStatus) + } // Test creating a file in an empty repository - doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t) + doAPICreateRepository(APITestContext{ + Session: session2, + Token: token2, + Username: "user2", + Reponame: "empty-repo", + }, true)(t) createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 293977f6bf4..350e7997770 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -110,56 +110,45 @@ func TestAPIDeleteFile(t *testing.T) { AddTokenAuth(token2) MakeRequest(t, req, http.StatusUnprocessableEntity) - // Test creating a file in repo16 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &deleteFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &deleteFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &deleteFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, treePath) - deleteFileOptions = getDeleteFileOptions() - req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &deleteFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + testCases := []struct { + owner *user_model.User + repo *repo_model.Repository + token string + expectedStatus int + }{ + // Test creating a file in repo16 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusOK}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusOK}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } + permissionFiles := map[*repo_model.Repository]map[string]string{} + for i, testCase := range testCases { + if permissionFiles[testCase.repo] == nil { + permissionFiles[testCase.repo] = map[string]string{} + } + permissionFiles[testCase.repo][fmt.Sprintf("delete/file%d.txt", fileID+i+1)] = "This is a NEW file" + } + for _, testCase := range testCases { + if permissionFiles[testCase.repo] != nil { + createFileInBranch(testCase.owner, testCase.repo, createFileInBranchOptions{}, permissionFiles[testCase.repo]) + delete(permissionFiles, testCase.repo) + } + fileID++ + treePath = fmt.Sprintf("delete/file%d.txt", fileID) + deleteFileOptions = getDeleteFileOptions() + req = NewRequestWithJSON(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCase.owner.Name, testCase.repo.Name, treePath), &deleteFileOptions) + if testCase.token != "" { + req.AddTokenAuth(testCase.token) + } + MakeRequest(t, req, testCase.expectedStatus) + } }) } diff --git a/tests/integration/api_repo_file_get_test.go b/tests/integration/api_repo_file_get_test.go index a785cd853d5..e2a4260b3b2 100644 --- a/tests/integration/api_repo_file_get_test.go +++ b/tests/integration/api_repo_file_get_test.go @@ -9,25 +9,22 @@ import ( "testing" auth_model "gitea.dev/models/auth" - "gitea.dev/tests" "github.com/stretchr/testify/assert" ) func TestAPIGetRawFileOrLFS(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // Test with raw file - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) - - req = NewRequest(t, "GET", "/api/v1/repos/user2/repo2/media/test.xml").AddTokenAuth(getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)) - resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) - - // Test with LFS onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Test with raw file + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) + + req = NewRequest(t, "GET", "/api/v1/repos/user2/repo2/media/test.xml").AddTokenAuth(getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository)) + resp = MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) + + // Test with LFS createLFSTestRepository(t, "repo-lfs-test") httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository) t.Run("repo-lfs-test", func(t *testing.T) { diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index a4b6cde6893..534d3b7a960 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -235,56 +235,45 @@ func TestAPIUpdateFile(t *testing.T) { apiError := DecodeJSON(t, resp, &context.APIError{}) assert.Equal(t, expectedAPIError, apiError) - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo16, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(org3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusOK) - - // Test using org repo "org3/repo3" with no user token - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(org3, repo3, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &updateFileOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - treePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, treePath) - updateFileOptions = getUpdateFileOptions() - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &updateFileOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + testCases := []struct { + owner *user_model.User + repo *repo_model.Repository + token string + expectedStatus int + }{ + // Test creating a file in repo1 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusOK}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusOK}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } + permissionFiles := map[*repo_model.Repository]map[string]string{} + for i, testCase := range testCases { + if permissionFiles[testCase.repo] == nil { + permissionFiles[testCase.repo] = map[string]string{} + } + permissionFiles[testCase.repo][fmt.Sprintf("update/file%d.txt", fileID+i+1)] = "This is a NEW file" + } + for _, testCase := range testCases { + if permissionFiles[testCase.repo] != nil { + createFileInBranch(testCase.owner, testCase.repo, createFileInBranchOptions{}, permissionFiles[testCase.repo]) + delete(permissionFiles, testCase.repo) + } + fileID++ + treePath = fmt.Sprintf("update/file%d.txt", fileID) + updateFileOptions = getUpdateFileOptions() + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCase.owner.Name, testCase.repo.Name, treePath), &updateFileOptions) + if testCase.token != "" { + req.AddTokenAuth(testCase.token) + } + MakeRequest(t, req, testCase.expectedStatus) + } }) } diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index 4b3b5ccf7b3..f4d846c3510 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -128,135 +128,87 @@ func TestAPIChangeFiles(t *testing.T) { }) } - // Test changing files in a new branch + for _, branch := range []struct { + name string + forcePush bool + protected bool + status int + expectedError string + }{ + // Test changing files in a new branch + {"new_branch", false, false, http.StatusCreated, ""}, + // Test fails creating a file in a branch that already exists without force + {"develop", false, false, http.StatusUnprocessableEntity, `"message":"branch already exists [name: develop]"`}, + // Test succeeds creating a file in a branch that already exists with force + {"develop", true, false, http.StatusCreated, ""}, + // Test fails creating a file in a branch that already exists with force and branch protection enabled + {"develop", true, true, http.StatusForbidden, `"message":"Branch develop is protected from force push"`}, + } { + if branch.protected { + protectionReq := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections", &api.BranchProtection{ + RuleName: "develop", + BranchName: "develop", + Priority: 1, + EnablePush: true, + EnableForcePush: false, + }).AddTokenAuth(token2) + MakeRequest(t, protectionReq, http.StatusCreated) + } + changeFilesOptions := getChangeFilesOptions() + changeFilesOptions.BranchName = repo1.DefaultBranch + changeFilesOptions.NewBranchName = branch.name + changeFilesOptions.ForcePush = branch.forcePush + fileID++ + createTreePath := fmt.Sprintf("new/file%d.txt", fileID) + updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) + deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) + changeFilesOptions.Files[0].Path = createTreePath + changeFilesOptions.Files[1].Path = updateTreePath + changeFilesOptions.Files[2].Path = deleteTreePath + createFile(user2, repo1, updateTreePath) + createFile(user2, repo1, deleteTreePath) + url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) + req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, branch.status) + if branch.expectedError != "" { + assert.Contains(t, resp.Body.String(), branch.expectedError) + continue + } + filesResponse := DecodeJSON(t, resp, &api.FilesResponse{}) + expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" + expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/%s/new/file%d.txt", branch.name, fileID) + expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/%s/new/file%d.txt", branch.name, fileID) + expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" + expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/%s/update/file%d.txt", branch.name, fileID) + expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/%s/update/file%d.txt", branch.name, fileID) + assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) + assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) + assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) + assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) + assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) + assert.Nil(t, filesResponse.Files[2]) + assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) + } + + // Test updating a file and renaming it changeFilesOptions := getChangeFilesOptions() changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "new_branch" fileID++ - createTreePath := fmt.Sprintf("new/file%d.txt", fileID) updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) + changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} + changeFilesOptions.Files[0].FromPath = updateTreePath + changeFilesOptions.Files[0].Path = "rename/" + updateTreePath url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions). AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) filesResponse := DecodeJSON(t, resp, &api.FilesResponse{}) - expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" - expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) - expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" - expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) - expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) - assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) - assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) - assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) - assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) - assert.Nil(t, filesResponse.Files[2]) - assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) - - // Test fails creating a file in a branch that already exists without force - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "develop" - changeFilesOptions.ForcePush = false - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusUnprocessableEntity) - assert.Contains(t, resp.Body.String(), `"message":"branch already exists [name: develop]"`) - - // Test succeeds creating a file in a branch that already exists with force - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "develop" - changeFilesOptions.ForcePush = true - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusCreated) - filesResponse = DecodeJSON(t, resp, &api.FilesResponse{}) - expectedCreateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/develop/new/file%d.txt", fileID) - expectedCreateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/develop/new/file%d.txt", fileID) - expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/develop/update/file%d.txt", fileID) - expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/develop/update/file%d.txt", fileID) - assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) - assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) - assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) - assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) - assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) - assert.Nil(t, filesResponse.Files[2]) - assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) - - // Test fails creating a file in a branch that already exists with force and branch protection enabled - protectionReq := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections", &api.BranchProtection{ - RuleName: "develop", - BranchName: "develop", - Priority: 1, - EnablePush: true, - EnableForcePush: false, - }).AddTokenAuth(token2) - MakeRequest(t, protectionReq, http.StatusCreated) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - changeFilesOptions.NewBranchName = "develop" - changeFilesOptions.ForcePush = true - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusForbidden) - assert.Contains(t, resp.Body.String(), `"message":"Branch develop is protected from force push"`) - - // Test updating a file and renaming it - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.BranchName = repo1.DefaultBranch - fileID++ - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - createFile(user2, repo1, updateTreePath) - changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} - changeFilesOptions.Files[0].FromPath = updateTreePath - changeFilesOptions.Files[0].Path = "rename/" + updateTreePath - req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). - AddTokenAuth(token2) - resp = MakeRequest(t, req, http.StatusCreated) - filesResponse = DecodeJSON(t, resp, &api.FilesResponse{}) - expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136" - expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) - expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) + expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) + expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) assert.Equal(t, expectedUpdateSHA, filesResponse.Files[0].SHA) assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) @@ -266,9 +218,9 @@ func TestAPIChangeFiles(t *testing.T) { changeFilesOptions.Message = "" changeFilesOptions.BranchName = repo1.DefaultBranch fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) + createTreePath := fmt.Sprintf("new/file%d.txt", fileID) updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) + deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) changeFilesOptions.Files[0].Path = createTreePath changeFilesOptions.Files[1].Path = updateTreePath changeFilesOptions.Files[2].Path = deleteTreePath @@ -300,92 +252,40 @@ func TestAPIChangeFiles(t *testing.T) { apiError := DecodeJSON(t, resp, &context.APIError{}) assert.Equal(t, expectedAPIError, apiError) - // Test creating a file in repo1 by user4 who does not have write access - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, updateTreePath) - createFile(user2, repo16, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusNotFound) - - // Tests a repo with no token given so will fail - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, updateTreePath) - createFile(user2, repo16, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using access token for a private repo that the user of the token owns - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo16, updateTreePath) - createFile(user2, repo16, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" where user2 is a collaborator - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, updateTreePath) - createFile(org3, repo3, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusCreated) - - // Test using org repo "org3/repo3" with no user token - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(org3, repo3, updateTreePath) - createFile(org3, repo3, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions) - MakeRequest(t, req, http.StatusNotFound) - - // Test using repo "user2/repo1" where user4 is a NOT collaborator - fileID++ - createTreePath = fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - changeFilesOptions = getChangeFilesOptions() - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) + for _, permission := range []struct { + owner *user_model.User + repo *repo_model.Repository + token string + status int + }{ + // Test creating a file in repo1 by user4 who does not have write access + {user2, repo16, token4, http.StatusNotFound}, + // Tests a repo with no token given so will fail + {user2, repo16, "", http.StatusNotFound}, + // Test using access token for a private repo that the user of the token owns + {user2, repo16, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" where user2 is a collaborator + {org3, repo3, token2, http.StatusCreated}, + // Test using org repo "org3/repo3" with no user token + {org3, repo3, "", http.StatusNotFound}, + // Test using repo "user2/repo1" where user4 is a NOT collaborator + {user2, repo1, token4, http.StatusForbidden}, + } { + fileID++ + createTreePath = fmt.Sprintf("new/file%d.txt", fileID) + updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) + deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) + createFile(permission.owner, permission.repo, updateTreePath) + createFile(permission.owner, permission.repo, deleteTreePath) + changeFilesOptions = getChangeFilesOptions() + changeFilesOptions.Files[0].Path = createTreePath + changeFilesOptions.Files[1].Path = updateTreePath + changeFilesOptions.Files[2].Path = deleteTreePath + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", permission.owner.Name, permission.repo.Name), &changeFilesOptions) + if permission.token != "" { + req = req.AddTokenAuth(permission.token) + } + MakeRequest(t, req, permission.status) + } }) } diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 887a7366845..efb8e840caf 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -60,11 +60,6 @@ func TestAPIReposGitBlobs(t *testing.T) { AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) - // Test using org repo "org3/repo3" where user2 is a collaborator - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3.Name, repo3ReadmeSHA). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - // Test using org repo "org3/repo3" with no user token req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", org3.Name, repo3ReadmeSHA, repo3.Name) MakeRequest(t, req, http.StatusNotFound) diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index 7905e6acb1e..368420b3adb 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -75,45 +75,56 @@ func TestAPIReposGitCommitList(t *testing.T) { session := loginUser(t, user.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - // Test getting commits (Page 1) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo20/commits?not=master&sha=remove-files-a", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + for _, testCase := range []struct { + name, path, total string + commits []string + files [][]string + }{ + // Test getting commits (Page 1) + { + "Page1", "repo20/commits?not=master&sha=remove-files-a", "2", + []string{"cfe3b3c1fd36fba04f9183287b106497e1afe986", "c8e31bc7688741a5287fcde4fbb8fc129ca07027"}, + [][]string{{"link_hi", "test.csv"}, {"test.csv"}}, + }, + // Test getting commits (Page 1) + { + "NotMaster", "repo16/commits", "3", + []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, + [][]string{{"readme.md"}, {"readme.md"}, {"readme.md"}}, + }, + // Test getting commits (Page=1, Branch=good-sign) + { + "DifferentBranch", "repo16/commits?sha=good-sign", "", + []string{"f27c2b2b03dcab38beaf89b0ab4ff61f6de63441"}, + [][]string{{"readme.md"}}, + }, + { + "FileHistory", "repo16/commits?path=readme.md&sha=good-sign", "1", + []string{"f27c2b2b03dcab38beaf89b0ab4ff61f6de63441"}, + [][]string{{"readme.md"}}, + }, + { + "FileHistoryNotOnMaster", "repo20/commits?path=test.csv&sha=add-csv¬=master", "1", + []string{"c8e31bc7688741a5287fcde4fbb8fc129ca07027"}, + [][]string{{"test.csv"}}, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s", user.Name, testCase.path). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + apiData := DecodeJSON(t, resp, []api.Commit{}) - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 2) - assert.Equal(t, "cfe3b3c1fd36fba04f9183287b106497e1afe986", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"link_hi", "test.csv"}, apiData[0].Files) - assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[1].CommitMeta.SHA) - compareCommitFiles(t, []string{"test.csv"}, apiData[1].Files) - - assert.Equal(t, "2", resp.Header().Get("X-Total")) -} - -func TestAPIReposGitCommitListNotMaster(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // Test getting commits (Page 1) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 3) - assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[1].Files) - assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[2].Files) - - assert.Equal(t, "3", resp.Header().Get("X-Total")) + assert.Len(t, apiData, len(testCase.commits)) + for i, sha := range testCase.commits { + assert.Equal(t, sha, apiData[i].CommitMeta.SHA) + compareCommitFiles(t, testCase.files[i], apiData[i].Files) + } + if testCase.total != "" { + assert.Equal(t, testCase.total, resp.Header().Get("X-Total")) + } + }) + } } func TestAPIReposGitCommitListPage2Empty(t *testing.T) { @@ -133,25 +144,6 @@ func TestAPIReposGitCommitListPage2Empty(t *testing.T) { assert.Empty(t, apiData) } -func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // Test getting commits (Page=1, Branch=good-sign) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?sha=good-sign", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 1) - assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) -} - func TestAPIReposGitCommitListWithoutSelectFields(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -197,46 +189,6 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { resp.Body.String()) } -func TestGetFileHistory(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?path=readme.md&sha=good-sign", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 1) - assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - - assert.Equal(t, "1", resp.Header().Get("X-Total")) -} - -func TestGetFileHistoryNotOnMaster(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo20/commits?path=test.csv&sha=add-csv¬=master", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, []api.Commit{}) - - assert.Len(t, apiData, 1) - assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[0].CommitMeta.SHA) - compareCommitFiles(t, []string{"test.csv"}, apiData[0].Files) - - assert.Equal(t, "1", resp.Header().Get("X-Total")) -} - func TestGetFileHistoryEmptyDateRange(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/api_repo_git_hook_test.go b/tests/integration/api_repo_git_hook_test.go index 01a17c7b08b..d5e232d2b4c 100644 --- a/tests/integration/api_repo_git_hook_test.go +++ b/tests/integration/api_repo_git_hook_test.go @@ -28,61 +28,51 @@ func TestAPIGitHooks(t *testing.T) { echo "TestGitHookScript" ` - t.Run("ListGitHooks", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + // user1 is an admin user + adminSession := loginUser(t, "user1") + adminReadToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeReadRepository) + adminWriteToken := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeWriteRepository) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID}) + ownerSession := loginUser(t, owner1.Name) + ownerReadToken := getTokenForLoggedInUser(t, ownerSession, auth_model.AccessTokenScopeReadRepository) + ownerWriteToken := getTokenForLoggedInUser(t, ownerSession, auth_model.AccessTokenScopeWriteRepository) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + for _, testCase := range []struct { + name string + repoID int64 + }{ + {"ListGitHooks", 37}, + {"NoGitHooks", 1}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiGitHooks := DecodeJSON(t, resp, []*api.GitHook{}) - assert.Len(t, apiGitHooks, 3) - for _, apiGitHook := range apiGitHooks { - if apiGitHook.Name == "pre-receive" { - assert.True(t, apiGitHook.IsActive) - assert.Equal(t, testHookContent, apiGitHook.Content) - } else { - assert.False(t, apiGitHook.IsActive) - assert.Empty(t, apiGitHook.Content) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: testCase.repoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). + AddTokenAuth(adminReadToken) + resp := MakeRequest(t, req, http.StatusOK) + apiGitHooks := DecodeJSON(t, resp, []*api.GitHook{}) + assert.Len(t, apiGitHooks, 3) + for _, apiGitHook := range apiGitHooks { + if testCase.repoID == 37 && apiGitHook.Name == "pre-receive" { + assert.True(t, apiGitHook.IsActive) + assert.Equal(t, testHookContent, apiGitHook.Content) + } else { + assert.False(t, apiGitHook.IsActive) + assert.Empty(t, apiGitHook.Content) + } } - } - }) - - t.Run("NoGitHooks", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - apiGitHooks := DecodeJSON(t, resp, []*api.GitHook{}) - assert.Len(t, apiGitHooks, 3) - for _, apiGitHook := range apiGitHooks { - assert.False(t, apiGitHook.IsActive) - assert.Empty(t, apiGitHook.Content) - } - }) + }) + } t.Run("ListGitHooksNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner.Name, repo.Name). - AddTokenAuth(token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git", owner1.Name, repo1.Name). + AddTokenAuth(ownerReadToken) MakeRequest(t, req, http.StatusForbidden) }) @@ -92,11 +82,8 @@ echo "TestGitHookScript" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminReadToken) resp := MakeRequest(t, req, http.StatusOK) apiGitHook := DecodeJSON(t, resp, &api.GitHook{}) assert.True(t, apiGitHook.IsActive) @@ -105,13 +92,8 @@ echo "TestGitHookScript" t.Run("GetGitHookNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner1.Name, repo1.Name). + AddTokenAuth(ownerReadToken) MakeRequest(t, req, http.StatusForbidden) }) @@ -121,22 +103,18 @@ echo "TestGitHookScript" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name) req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ Content: testHookContent, - }).AddTokenAuth(token) + }).AddTokenAuth(adminWriteToken) resp := MakeRequest(t, req, http.StatusOK) apiGitHook := DecodeJSON(t, resp, &api.GitHook{}) assert.True(t, apiGitHook.IsActive) assert.Equal(t, testHookContent, apiGitHook.Content) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminWriteToken) resp = MakeRequest(t, req, http.StatusOK) apiGitHook2 := DecodeJSON(t, resp, &api.GitHook{}) assert.True(t, apiGitHook2.IsActive) @@ -146,15 +124,10 @@ echo "TestGitHookScript" t.Run("EditGitHookNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name) + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive", owner1.Name, repo1.Name) req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ Content: testHookContent, - }).AddTokenAuth(token) + }).AddTokenAuth(ownerWriteToken) MakeRequest(t, req, http.StatusForbidden) }) @@ -164,16 +137,12 @@ echo "TestGitHookScript" repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 37}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - // user1 is an admin user - session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminWriteToken) MakeRequest(t, req, http.StatusNoContent) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + AddTokenAuth(adminWriteToken) resp := MakeRequest(t, req, http.StatusOK) apiGitHook2 := DecodeJSON(t, resp, &api.GitHook{}) assert.False(t, apiGitHook2.IsActive) @@ -183,13 +152,8 @@ echo "TestGitHookScript" t.Run("DeleteGitHookNoAccess", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner.Name, repo.Name). - AddTokenAuth(token) + req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive", owner1.Name, repo1.Name). + AddTokenAuth(ownerWriteToken) MakeRequest(t, req, http.StatusForbidden) }) } diff --git a/tests/integration/api_repo_git_notes_test.go b/tests/integration/api_repo_git_notes_test.go deleted file mode 100644 index 8cb531e792c..00000000000 --- a/tests/integration/api_repo_git_notes_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "net/url" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - api "gitea.dev/modules/structs" - - "github.com/stretchr/testify/assert" -) - -func TestAPIReposGitNotes(t *testing.T) { - onGiteaRun(t, func(*testing.T, *url.URL) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - // check invalid requests - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) - - // check valid request - req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d", user.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiData := DecodeJSON(t, resp, &api.Note{}) - assert.Equal(t, "This is a test note\n", apiData.Message) - assert.NotEmpty(t, apiData.Commit.Files) - assert.NotNil(t, apiData.Commit.RepoCommit.Verification) - }) -} diff --git a/tests/integration/api_repo_git_ref_test.go b/tests/integration/api_repo_git_ref_test.go deleted file mode 100644 index fc43c4eac69..00000000000 --- a/tests/integration/api_repo_git_ref_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - "gitea.dev/tests" -) - -func TestAPIReposGitRefs(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - - for _, ref := range [...]string{ - "refs/heads/master", // Branch - "refs/tags/v1.1", // Tag - } { - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/%s", user.Name, ref). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - } - // Test getting all refs - req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) - // Test getting non-existent refs - req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs/heads/unknown", user.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) -} diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index 50f20fb3613..0df44637eca 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -6,6 +6,7 @@ package integration import ( "fmt" "net/http" + "net/url" "strings" "testing" @@ -22,53 +23,106 @@ import ( ) func TestAPIGitTags(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + onGiteaRun(t, func(t *testing.T, _url *url.URL) { + t.Run("Notes", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - // Set up git config for the tagger - _ = git.ManagedConfigSet(t.Context(), repo, "user.name", user.Name) - _ = git.ManagedConfigSet(t.Context(), repo, "user.email", user.Email) + // check invalid requests + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) - gitRepo, _ := git.OpenRepository(t.Context(), repo) - defer gitRepo.Close() + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/..", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusUnprocessableEntity) - commit, _ := gitRepo.GetBranchCommit(t.Context(), "master") - lTagName := "lightweightTag" - _ = gitRepo.CreateTag(t.Context(), lTagName, commit.ID.String()) + // check valid request + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/65f1bf27bc3bf70f64657658635e66094edbcb4d", user.Name). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - aTagName := "annotatedTag" - aTagMessage := strings.Repeat("very long " /*10bytes*/, 20*1024) + "annotated message" - _ = gitRepo.CreateAnnotatedTag(t.Context(), aTagName, aTagMessage, commit.ID.String()) - aTag, _ := gitRepo.GetTag(t.Context(), aTagName) + apiData := DecodeJSON(t, resp, &api.Note{}) + assert.Equal(t, "This is a test note\n", apiData.Message) + assert.NotEmpty(t, apiData.Commit.Files) + assert.NotNil(t, apiData.Commit.RepoCommit.Verification) + }) - // SHOULD work for annotated tags - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - tag := DecodeJSON(t, resp, &api.AnnotatedTag{}) - assert.Equal(t, aTagName, tag.Tag) - assert.Equal(t, aTag.ID.String(), tag.SHA) - assert.Equal(t, commit.ID.String(), tag.Object.SHA) - assert.Equal(t, aTagMessage+"\n", tag.Message) - assert.Equal(t, user.Name, tag.Tagger.Name) - assert.Equal(t, user.Email, tag.Tagger.Email) - assert.Equal(t, repo.APIURL()+"/git/tags/"+aTag.ID.String(), tag.URL) + t.Run("Refs", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - defer test.MockVariableValue(&git.MaxGitObjectSize, 1024)() - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - tag = DecodeJSON(t, resp, &api.AnnotatedTag{}) - assert.True(t, strings.HasPrefix(aTagMessage, tag.Message)) - assert.Less(t, len(tag.Message), len(aTagMessage)) - assert.Less(t, len(tag.Message), 1024) + for _, ref := range [...]string{ + "refs/heads/master", // Branch + "refs/tags/v1.1", // Tag + } { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/%s", user.Name, ref). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + } + // Test getting all refs + req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + // Test getting non-existent refs + req = NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/refs/heads/unknown", user.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) - // Should NOT work for lightweight tags - badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()). - AddTokenAuth(token) - MakeRequest(t, badReq, http.StatusBadRequest) + t.Run("Tags", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + // Login as User2. + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + // Set up git config for the tagger + _ = git.ManagedConfigSet(t.Context(), repo, "user.name", user.Name) + _ = git.ManagedConfigSet(t.Context(), repo, "user.email", user.Email) + + gitRepo, _ := git.OpenRepository(t.Context(), repo) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(t.Context(), "master") + lTagName := "lightweightTag" + _ = gitRepo.CreateTag(t.Context(), lTagName, commit.ID.String()) + + aTagName := "annotatedTag" + aTagMessage := strings.Repeat("very long " /*10bytes*/, 20*1024) + "annotated message" + _ = gitRepo.CreateAnnotatedTag(t.Context(), aTagName, aTagMessage, commit.ID.String()) + aTag, _ := gitRepo.GetTag(t.Context(), aTagName) + + // SHOULD work for annotated tags + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + tag := DecodeJSON(t, resp, &api.AnnotatedTag{}) + assert.Equal(t, aTagName, tag.Tag) + assert.Equal(t, aTag.ID.String(), tag.SHA) + assert.Equal(t, commit.ID.String(), tag.Object.SHA) + assert.Equal(t, aTagMessage+"\n", tag.Message) + assert.Equal(t, user.Name, tag.Tagger.Name) + assert.Equal(t, user.Email, tag.Tagger.Email) + assert.Equal(t, repo.APIURL()+"/git/tags/"+aTag.ID.String(), tag.URL) + + defer test.MockVariableValue(&git.MaxGitObjectSize, 1024)() + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, aTag.ID.String()).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + tag = DecodeJSON(t, resp, &api.AnnotatedTag{}) + assert.True(t, strings.HasPrefix(aTagMessage, tag.Message)) + assert.Less(t, len(tag.Message), len(aTagMessage)) + assert.Less(t, len(tag.Message), 1024) + + // Should NOT work for lightweight tags + badReq := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/tags/%s", user.Name, repo.Name, commit.ID.String()). + AddTokenAuth(token) + MakeRequest(t, badReq, http.StatusBadRequest) + }) + }) } func TestAPIDeleteTagByName(t *testing.T) { diff --git a/tests/integration/api_repo_lfs_locks_test.go b/tests/integration/api_repo_lfs_locks_test.go index f1a38334c1e..2c36092ea06 100644 --- a/tests/integration/api_repo_lfs_locks_test.go +++ b/tests/integration/api_repo_lfs_locks_test.go @@ -21,156 +21,162 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPILFSLocksNotStarted(t *testing.T) { +func TestAPILFSLocks(t *testing.T) { defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, false)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/verify", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/10/unlock", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) -} + t.Run("NotStarted", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, false)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) -func TestAPILFSLocksNotLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks/10/unlock", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + }) - req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) - req.Header.Set("Accept", lfs.MediaType) - resp := MakeRequest(t, req, http.StatusUnauthorized) - lfsLockError := DecodeJSON(t, resp, &api.LFSLockError{}) - assert.Equal(t, "You must have pull access to list locks", lfsLockError.Message) -} + t.Run("NotLogin", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) -func TestAPILFSLocksLogged(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3 - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // in org 3 + req := NewRequestf(t, "GET", "/%s/%s.git/info/lfs/locks", user.Name, repo.Name) + req.Header.Set("Accept", lfs.MediaType) + resp := MakeRequest(t, req, http.StatusUnauthorized) + lfsLockError := DecodeJSON(t, resp, &api.LFSLockError{}) + assert.Equal(t, "You must have pull access to list locks", lfsLockError.Message) + }) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // own by org 3 + t.Run("Logged", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // in org 3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // in org 3 - tests := []struct { - user *user_model.User - repo *repo_model.Repository - path string - httpResult int - addTime []int - }{ - {user: user2, repo: repo1, path: "foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{0}}, - {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusCreated, addTime: []int{0}}, - {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusConflict}, - {user: user2, repo: repo1, path: "Foo/BaR.zip", httpResult: http.StatusConflict}, - {user: user2, repo: repo1, path: "Foo/Test/../subFOlder/../Relative/../BaR.zip", httpResult: http.StatusConflict}, - {user: user4, repo: repo1, path: "FoO/BaR.zip", httpResult: http.StatusUnauthorized}, - {user: user4, repo: repo1, path: "path/test-user4", httpResult: http.StatusUnauthorized}, - {user: user2, repo: repo1, path: "patH/Test-user4", httpResult: http.StatusCreated, addTime: []int{0}}, - {user: user2, repo: repo1, path: "some/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/path", httpResult: http.StatusCreated, addTime: []int{0}}, + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // own by org 3 - {user: user2, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{1, 2}}, - {user: user4, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusConflict}, - {user: user4, repo: repo3, path: "test/foo/bar.bin", httpResult: http.StatusCreated, addTime: []int{1, 2}}, - } + tests := []struct { + user *user_model.User + repo *repo_model.Repository + path string + httpResult int + addTime []int + }{ + {user: user2, repo: repo1, path: "foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "path/test", httpResult: http.StatusConflict}, + {user: user2, repo: repo1, path: "Foo/BaR.zip", httpResult: http.StatusConflict}, + {user: user2, repo: repo1, path: "Foo/Test/../subFOlder/../Relative/../BaR.zip", httpResult: http.StatusConflict}, + {user: user4, repo: repo1, path: "FoO/BaR.zip", httpResult: http.StatusUnauthorized}, + {user: user4, repo: repo1, path: "path/test-user4", httpResult: http.StatusUnauthorized}, + {user: user2, repo: repo1, path: "patH/Test-user4", httpResult: http.StatusCreated, addTime: []int{0}}, + {user: user2, repo: repo1, path: "some/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/long/path", httpResult: http.StatusCreated, addTime: []int{0}}, - resultsTests := []struct { - user *user_model.User - repo *repo_model.Repository - totalCount int - oursCount int - theirsCount int - locksOwners []*user_model.User - locksTimes []time.Time - }{ - {user: user2, repo: repo1, totalCount: 4, oursCount: 4, theirsCount: 0, locksOwners: []*user_model.User{user2, user2, user2, user2}, locksTimes: []time.Time{}}, - {user: user2, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, - {user: user4, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, - } + {user: user2, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusCreated, addTime: []int{1, 2}}, + {user: user4, repo: repo3, path: "test/foo/bar.zip", httpResult: http.StatusConflict}, + {user: user4, repo: repo3, path: "test/foo/bar.bin", httpResult: http.StatusCreated, addTime: []int{1, 2}}, + } - deleteTests := []struct { - user *user_model.User - repo *repo_model.Repository - lockID string - }{} + resultsTests := []struct { + user *user_model.User + repo *repo_model.Repository + totalCount int + oursCount int + theirsCount int + locksOwners []*user_model.User + locksTimes []time.Time + }{ + {user: user2, repo: repo1, totalCount: 4, oursCount: 4, theirsCount: 0, locksOwners: []*user_model.User{user2, user2, user2, user2}, locksTimes: []time.Time{}}, + {user: user2, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, + {user: user4, repo: repo3, totalCount: 2, oursCount: 1, theirsCount: 1, locksOwners: []*user_model.User{user2, user4}, locksTimes: []time.Time{}}, + } - // create locks - for _, test := range tests { - session := loginUser(t, test.user.Name) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) - req.Header.Set("Accept", lfs.AcceptHeader) - req.Header.Set("Content-Type", lfs.MediaType) - resp := session.MakeRequest(t, req, test.httpResult) - if len(test.addTime) > 0 { - lfsLock := DecodeJSON(t, resp, &api.LFSLockResponse{}) - assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name) - assert.Equal(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second - for _, id := range test.addTime { - resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) + deleteTests := []struct { + user *user_model.User + repo *repo_model.Repository + lockID string + }{} + + sessions := map[string]*TestSession{ + user2.Name: loginUser(t, user2.Name), + user4.Name: loginUser(t, user4.Name), + } + + // create locks + for _, test := range tests { + session := sessions[test.user.Name] + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks", test.repo.FullName()), map[string]string{"path": test.path}) + req.Header.Set("Accept", lfs.AcceptHeader) + req.Header.Set("Content-Type", lfs.MediaType) + resp := session.MakeRequest(t, req, test.httpResult) + if len(test.addTime) > 0 { + lfsLock := DecodeJSON(t, resp, &api.LFSLockResponse{}) + assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name) + assert.Equal(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + for _, id := range test.addTime { + resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) + } } } - } - // check creation - for _, test := range resultsTests { - session := loginUser(t, test.user.Name) - req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) - req.Header.Set("Accept", lfs.AcceptHeader) - resp := session.MakeRequest(t, req, http.StatusOK) - lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) - assert.Len(t, lfsLocks.Locks, test.totalCount) - for i, lock := range lfsLocks.Locks { - assert.Equal(t, test.locksOwners[i].Name, lock.Owner.Name) - assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) - assert.Equal(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + // check creation + for _, test := range resultsTests { + session := sessions[test.user.Name] + req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) + req.Header.Set("Accept", lfs.AcceptHeader) + resp := session.MakeRequest(t, req, http.StatusOK) + lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) + assert.Len(t, lfsLocks.Locks, test.totalCount) + for i, lock := range lfsLocks.Locks { + assert.Equal(t, test.locksOwners[i].Name, lock.Owner.Name) + assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) + assert.Equal(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + } + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) + req.Header.Set("Accept", lfs.AcceptHeader) + req.Header.Set("Content-Type", lfs.MediaType) + resp = session.MakeRequest(t, req, http.StatusOK) + lfsLocksVerify := DecodeJSON(t, resp, &api.LFSLockListVerify{}) + assert.Len(t, lfsLocksVerify.Ours, test.oursCount) + assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) + for _, lock := range lfsLocksVerify.Ours { + assert.Equal(t, test.user.Name, lock.Owner.Name) + deleteTests = append(deleteTests, struct { + user *user_model.User + repo *repo_model.Repository + lockID string + }{test.user, test.repo, lock.ID}) + } + for _, lock := range lfsLocksVerify.Theirs { + assert.NotEqual(t, test.user.DisplayName(), lock.Owner.Name) + } } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) - req.Header.Set("Accept", lfs.AcceptHeader) - req.Header.Set("Content-Type", lfs.MediaType) - resp = session.MakeRequest(t, req, http.StatusOK) - lfsLocksVerify := DecodeJSON(t, resp, &api.LFSLockListVerify{}) - assert.Len(t, lfsLocksVerify.Ours, test.oursCount) - assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) - for _, lock := range lfsLocksVerify.Ours { - assert.Equal(t, test.user.Name, lock.Owner.Name) - deleteTests = append(deleteTests, struct { - user *user_model.User - repo *repo_model.Repository - lockID string - }{test.user, test.repo, lock.ID}) + // remove all locks + for _, test := range deleteTests { + session := sessions[test.user.Name] + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{}) + req.Header.Set("Accept", lfs.AcceptHeader) + req.Header.Set("Content-Type", lfs.MediaType) + resp := session.MakeRequest(t, req, http.StatusOK) + lfsLockRep := DecodeJSON(t, resp, &api.LFSLockResponse{}) + assert.Equal(t, test.lockID, lfsLockRep.Lock.ID) + assert.Equal(t, test.user.Name, lfsLockRep.Lock.Owner.Name) } - for _, lock := range lfsLocksVerify.Theirs { - assert.NotEqual(t, test.user.DisplayName(), lock.Owner.Name) + + // check that we don't have any lock + for _, test := range resultsTests { + session := sessions[test.user.Name] + req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) + req.Header.Set("Accept", lfs.AcceptHeader) + resp := session.MakeRequest(t, req, http.StatusOK) + lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) + assert.Empty(t, lfsLocks.Locks) } - } - - // remove all locks - for _, test := range deleteTests { - session := loginUser(t, test.user.Name) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/%s/unlock", test.repo.FullName(), test.lockID), map[string]string{}) - req.Header.Set("Accept", lfs.AcceptHeader) - req.Header.Set("Content-Type", lfs.MediaType) - resp := session.MakeRequest(t, req, http.StatusOK) - lfsLockRep := DecodeJSON(t, resp, &api.LFSLockResponse{}) - assert.Equal(t, test.lockID, lfsLockRep.Lock.ID) - assert.Equal(t, test.user.Name, lfsLockRep.Lock.Owner.Name) - } - - // check that we don't have any lock - for _, test := range resultsTests { - session := loginUser(t, test.user.Name) - req := NewRequestf(t, "GET", "/%s.git/info/lfs/locks", test.repo.FullName()) - req.Header.Set("Accept", lfs.AcceptHeader) - resp := session.MakeRequest(t, req, http.StatusOK) - lfsLocks := DecodeJSON(t, resp, &api.LFSLockList{}) - assert.Empty(t, lfsLocks.Locks) - } + }) } diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index dea02814dfb..629a57fc1be 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -25,38 +25,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestAPILFSNotStarted(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, false)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "PUT", "/%s/%s.git/info/lfs/objects/oid/10", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid/name", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) - req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestAPILFSMediaType(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) - MakeRequest(t, req, http.StatusUnsupportedMediaType) - req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) - MakeRequest(t, req, http.StatusUnsupportedMediaType) -} - func createLFSTestRepository(t *testing.T, repoName string) *repo_model.Repository { t.Helper() ctx := NewAPITestContext(t, "user2", repoName, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) @@ -68,428 +36,415 @@ func createLFSTestRepository(t *testing.T, repoName string) *repo_model.Reposito return repo } -func TestAPILFSBatch(t *testing.T) { +func TestAPILFS(t *testing.T) { defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - repo := createLFSTestRepository(t, "lfs-batch-repo") + t.Run("NotStarted", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, false)() - oid := storeObjectInRepo(t, repo.ID, "dummy1") - defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - session := loginUser(t, "user2") - - newRequest := func(t testing.TB, br *lfs.BatchRequest) *RequestWrapper { - return NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br). - SetHeader("Accept", lfs.AcceptHeader). - SetHeader("Content-Type", lfs.MediaType) - } - decodeResponse := func(t *testing.T, b *bytes.Buffer) *lfs.BatchResponse { - var br lfs.BatchResponse - - assert.NoError(t, json.Unmarshal(b.Bytes(), &br)) - return &br - } - - t.Run("InvalidJsonRequest", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, nil) - - session.MakeRequest(t, req, http.StatusBadRequest) + req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "PUT", "/%s/%s.git/info/lfs/objects/oid/10", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid/name", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusNotFound) }) - t.Run("InvalidOperation", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("MediaType", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() - req := newRequest(t, &lfs.BatchRequest{ - Operation: "dummy", + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name) + MakeRequest(t, req, http.StatusUnsupportedMediaType) + req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name) + MakeRequest(t, req, http.StatusUnsupportedMediaType) + }) + + t.Run("Batch", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + + repo := createLFSTestRepository(t, "lfs-batch-repo") + + oid := storeObjectInRepo(t, repo.ID, "dummy1") + defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) + + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, br *lfs.BatchRequest) *RequestWrapper { + return NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br). + SetHeader("Accept", lfs.AcceptHeader). + SetHeader("Content-Type", lfs.MediaType) + } + makeRequest := func(t *testing.T, request *lfs.BatchRequest) *lfs.BatchResponse { + resp := session.MakeRequest(t, newRequest(t, request), http.StatusOK) + var br lfs.BatchResponse + + assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &br)) + return &br + } + + t.Run("InvalidJsonRequest", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, nil) + + session.MakeRequest(t, req, http.StatusBadRequest) }) - session.MakeRequest(t, req, http.StatusBadRequest) - }) - - t.Run("InvalidPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: "dummy"}, - {Oid: oid, Size: -1}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 2) - assert.Equal(t, "dummy", br.Objects[0].Oid) - assert.Equal(t, oid, br.Objects[1].Oid) - assert.Equal(t, int64(0), br.Objects[0].Size) - assert.Equal(t, int64(-1), br.Objects[1].Size) - assert.NotNil(t, br.Objects[0].Error) - assert.NotNil(t, br.Objects[1].Error) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[1].Error.Code) - assert.Equal(t, "Oid or size are invalid", br.Objects[0].Error.Message) - assert.Equal(t, "Oid or size are invalid", br.Objects[1].Error.Message) - }) - - t.Run("PointerSizeMismatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: oid, Size: 1}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) - assert.Equal(t, "Object "+oid+" is not 1 bytes", br.Objects[0].Error.Message) - }) - - t.Run("Download", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("PointerNotInStore", func(t *testing.T) { + t.Run("InvalidOperation", func(t *testing.T) { defer tests.PrintCurrentTest(t)() req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, - }, + Operation: "dummy", }) - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + session.MakeRequest(t, req, http.StatusBadRequest) }) - t.Run("MetaNotFound", func(t *testing.T) { + t.Run("InvalidPointer", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: "dummy"}, + {Oid: oid, Size: -1}, + }, + }) + assert.Len(t, br.Objects, 2) + assert.Equal(t, "dummy", br.Objects[0].Oid) + assert.Equal(t, oid, br.Objects[1].Oid) + assert.Equal(t, int64(0), br.Objects[0].Size) + assert.Equal(t, int64(-1), br.Objects[1].Size) + assert.NotNil(t, br.Objects[0].Error) + assert.NotNil(t, br.Objects[1].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[1].Error.Code) + assert.Equal(t, "Oid or size are invalid", br.Objects[0].Error.Message) + assert.Equal(t, "Oid or size are invalid", br.Objects[1].Error.Message) + }) + + t.Run("PointerSizeMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 1}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, "Object "+oid+" is not 1 bytes", br.Objects[0].Error.Message) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("PointerNotInStore", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + }) + + t.Run("MetaNotFound", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.False(t, exist) + err = contentStore.Put(p, bytes.NewReader([]byte("dummy0"))) + assert.NoError(t, err) + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{p}, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) + }) + + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "download", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Contains(t, br.Objects[0].Actions, "download") + l := br.Objects[0].Actions["download"] + assert.NotNil(t, l) + assert.NotEmpty(t, l.Href) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("FileTooBig", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.LFS.MaxFileSize, 2)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.NotNil(t, br.Objects[0].Error) + assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) + assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message) + }) + + t.Run("CrossRepoObjectRequiresUpload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // An object whose bytes already exist in the store but which is not + // linked to this repo must not be silently linked, even when the + // caller can access it in another repo. Auto-linking let a deploy key + // (whose token carries the repo owner's identity) exfiltrate objects + // across repos without proving possession. The client must upload. + p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.True(t, exist) + + // The object is linked to another repo owned by the same user. + repo2 := createLFSTestRepository(t, "lfs-batch2-repo") + storeObjectInRepo(t, repo2.ID, "dummy0") + + meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.Nil(t, meta) + assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{p}, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + // The client is told to upload instead of the object being linked. + assert.Contains(t, br.Objects[0].Actions, "upload") + + // No meta object may have been created for this repo. + meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.Nil(t, meta) + assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + + // Cleanup + err = contentStore.ObjectStorage.Delete(p.RelativePath()) + assert.NoError(t, err) + }) + + t.Run("AlreadyExists", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: oid, Size: 6}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Empty(t, br.Objects[0].Actions) + }) + + t.Run("NewFile", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + br := makeRequest(t, &lfs.BatchRequest{ + Operation: "upload", + Objects: []lfs.Pointer{ + {Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0153", Size: 1}, + }, + }) + assert.Len(t, br.Objects, 1) + assert.Nil(t, br.Objects[0].Error) + assert.Contains(t, br.Objects[0].Actions, "upload") + ul := br.Objects[0].Actions["upload"] + assert.NotNil(t, ul) + assert.NotEmpty(t, ul.Href) + assert.Equal(t, "chunked", ul.Header["Transfer-Encoding"], "git-lfs client needs Transfer-Encoding to do chunked transfer") + assert.Contains(t, br.Objects[0].Actions, "verify") + vl := br.Objects[0].Actions["verify"] + assert.NotNil(t, vl) + assert.NotEmpty(t, vl.Href) + }) + }) + }) + + t.Run("Upload", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() + + repo := createLFSTestRepository(t, "lfs-upload-repo") + session := loginUser(t, "user2") + + newRequest := func(t testing.TB, p lfs.Pointer, content string) *RequestWrapper { + reqUrl := fmt.Sprintf("/user2/lfs-upload-repo.git/info/lfs/objects/%s/%d", p.Oid, p.Size) + return NewRequestWithBody(t, "PUT", reqUrl, strings.NewReader(content)) + } + + t.Run("InvalidPointer", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := newRequest(t, lfs.Pointer{Oid: "dummy"}, "") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("AlreadyExistsInStore", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4", Size: 6} contentStore := lfs.NewContentStore() exist, err := contentStore.Exists(p) assert.NoError(t, err) assert.False(t, exist) - err = contentStore.Put(p, bytes.NewReader([]byte("dummy0"))) + err = contentStore.Put(p, bytes.NewReader([]byte("dummy5"))) assert.NoError(t, err) - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{p}, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code) - }) - - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "download", - Objects: []lfs.Pointer{ - {Oid: oid, Size: 6}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - assert.Contains(t, br.Objects[0].Actions, "download") - l := br.Objects[0].Actions["download"] - assert.NotNil(t, l) - assert.NotEmpty(t, l.Href) - }) - }) - - t.Run("Upload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - t.Run("FileTooBig", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer test.MockVariableValue(&setting.LFS.MaxFileSize, 2)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{ - {Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.NotNil(t, br.Objects[0].Error) - assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code) - assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message) - }) - - t.Run("CrossRepoObjectRequiresUpload", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - // An object whose bytes already exist in the store but which is not - // linked to this repo must not be silently linked, even when the - // caller can access it in another repo. Auto-linking let a deploy key - // (whose token carries the repo owner's identity) exfiltrate objects - // across repos without proving possession. The client must upload. - p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6} - - contentStore := lfs.NewContentStore() - exist, err := contentStore.Exists(p) - assert.NoError(t, err) - assert.True(t, exist) - - // The object is linked to another repo owned by the same user. - repo2 := createLFSTestRepository(t, "lfs-batch2-repo") - storeObjectInRepo(t, repo2.ID, "dummy0") - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) assert.Nil(t, meta) assert.Equal(t, git_model.ErrLFSObjectNotExist, err) - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{p}, + t.Run("InvalidAccess", func(t *testing.T) { + req := newRequest(t, p, "invalid") + session.MakeRequest(t, req, http.StatusUnprocessableEntity) }) - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - // The client is told to upload instead of the object being linked. - assert.Contains(t, br.Objects[0].Actions, "upload") + t.Run("ValidAccess", func(t *testing.T) { + req := newRequest(t, p, "dummy5") - // No meta object may have been created for this repo. - meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) - assert.Nil(t, meta) - assert.Equal(t, git_model.ErrLFSObjectNotExist, err) + session.MakeRequest(t, req, http.StatusOK) + meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + }) // Cleanup err = contentStore.ObjectStorage.Delete(p.RelativePath()) assert.NoError(t, err) }) - t.Run("AlreadyExists", func(t *testing.T) { + t.Run("MetaAlreadyExists", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{ - {Oid: oid, Size: 6}, - }, - }) - - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - assert.Empty(t, br.Objects[0].Actions) + oid := storeObjectInRepo(t, repo.ID, "123456") + req := newRequest(t, lfs.Pointer{Oid: oid, Size: 6}, "") + session.MakeRequest(t, req, http.StatusOK) }) - t.Run("NewFile", func(t *testing.T) { + t.Run("HashMismatch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := newRequest(t, &lfs.BatchRequest{ - Operation: "upload", - Objects: []lfs.Pointer{ - {Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0153", Size: 1}, - }, - }) + req := newRequest(t, lfs.Pointer{Oid: "2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a", Size: 1}, "a") - resp := session.MakeRequest(t, req, http.StatusOK) - br := decodeResponse(t, resp.Body) - assert.Len(t, br.Objects, 1) - assert.Nil(t, br.Objects[0].Error) - assert.Contains(t, br.Objects[0].Actions, "upload") - ul := br.Objects[0].Actions["upload"] - assert.NotNil(t, ul) - assert.NotEmpty(t, ul.Href) - assert.Equal(t, "chunked", ul.Header["Transfer-Encoding"], "git-lfs client needs Transfer-Encoding to do chunked transfer") - assert.Contains(t, br.Objects[0].Actions, "verify") - vl := br.Objects[0].Actions["verify"] - assert.NotNil(t, vl) - assert.NotEmpty(t, vl.Href) - }) - }) -} - -func TestAPILFSUpload(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - - repo := createLFSTestRepository(t, "lfs-upload-repo") - session := loginUser(t, "user2") - - newRequest := func(t testing.TB, p lfs.Pointer, content string) *RequestWrapper { - reqUrl := fmt.Sprintf("/user2/lfs-upload-repo.git/info/lfs/objects/%s/%d", p.Oid, p.Size) - return NewRequestWithBody(t, "PUT", reqUrl, strings.NewReader(content)) - } - - t.Run("InvalidPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, lfs.Pointer{Oid: "dummy"}, "") - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("AlreadyExistsInStore", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - p := lfs.Pointer{Oid: "83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4", Size: 6} - - contentStore := lfs.NewContentStore() - exist, err := contentStore.Exists(p) - assert.NoError(t, err) - assert.False(t, exist) - err = contentStore.Put(p, bytes.NewReader([]byte("dummy5"))) - assert.NoError(t, err) - - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) - assert.Nil(t, meta) - assert.Equal(t, git_model.ErrLFSObjectNotExist, err) - - t.Run("InvalidAccess", func(t *testing.T) { - req := newRequest(t, p, "invalid") session.MakeRequest(t, req, http.StatusUnprocessableEntity) }) - t.Run("ValidAccess", func(t *testing.T) { - req := newRequest(t, p, "dummy5") + t.Run("SizeMismatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - session.MakeRequest(t, req, http.StatusOK) - meta, err = git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + req := newRequest(t, lfs.Pointer{Oid: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", Size: 2}, "a") + + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("ConcurrentFailureKeepsExistingMeta", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + pointer, err := lfs.GeneratePointer(strings.NewReader("any-content")) + assert.NoError(t, err) + + // mock a record in database: it should not happen in real world (no existing file in the store) + // !!for testing purpose only!! to verify a failed upload should not remove a valid record, + _, err = git_model.NewLFSMetaObject(t.Context(), repo.ID, pointer) + assert.NoError(t, err) + + // make an invalid request, the existing lfs record should not be removed + req := newRequest(t, lfs.Pointer{Oid: pointer.Oid, Size: 1}, "invalid content") + session.MakeRequest(t, req, http.StatusUnprocessableEntity) + meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, pointer.Oid) assert.NoError(t, err) assert.NotNil(t, meta) }) - // Cleanup - err = contentStore.ObjectStorage.Delete(p.RelativePath()) - assert.NoError(t, err) + t.Run("Success", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + p := lfs.Pointer{Oid: "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d", Size: 5} + + req := newRequest(t, p, "gitea") + + session.MakeRequest(t, req, http.StatusOK) + + contentStore := lfs.NewContentStore() + exist, err := contentStore.Exists(p) + assert.NoError(t, err) + assert.True(t, exist) + + meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) + assert.NoError(t, err) + assert.NotNil(t, meta) + }) }) - t.Run("MetaAlreadyExists", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - oid := storeObjectInRepo(t, repo.ID, "123456") - req := newRequest(t, lfs.Pointer{Oid: oid, Size: 6}, "") - session.MakeRequest(t, req, http.StatusOK) - }) + t.Run("Verify", func(t *testing.T) { + defer test.MockVariableValue(&setting.LFS.StartServer, true)() - t.Run("HashMismatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + repo := createLFSTestRepository(t, "lfs-verify-repo") + oid := storeObjectInRepo(t, repo.ID, "dummy3") + defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) - req := newRequest(t, lfs.Pointer{Oid: "2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a", Size: 1}, "a") + session := loginUser(t, "user2") - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) + for _, testCase := range []struct { + name string + ptr *lfs.Pointer + status int + }{ + {"InvalidJsonRequest", nil, http.StatusUnprocessableEntity}, + {"InvalidPointer", &lfs.Pointer{}, http.StatusUnprocessableEntity}, + {"PointerNotExisting", &lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}, http.StatusNotFound}, + {"Success", &lfs.Pointer{Oid: oid, Size: 6}, http.StatusOK}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - t.Run("SizeMismatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, lfs.Pointer{Oid: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", Size: 2}, "a") - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("ConcurrentFailureKeepsExistingMeta", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - pointer, err := lfs.GeneratePointer(strings.NewReader("any-content")) - assert.NoError(t, err) - - // mock a record in database: it should not happen in real world (no existing file in the store) - // !!for testing purpose only!! to verify a failed upload should not remove a valid record, - _, err = git_model.NewLFSMetaObject(t.Context(), repo.ID, pointer) - assert.NoError(t, err) - - // make an invalid request, the existing lfs record should not be removed - req := newRequest(t, lfs.Pointer{Oid: pointer.Oid, Size: 1}, "invalid content") - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, pointer.Oid) - assert.NoError(t, err) - assert.NotNil(t, meta) - }) - - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - p := lfs.Pointer{Oid: "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d", Size: 5} - - req := newRequest(t, p, "gitea") - - session.MakeRequest(t, req, http.StatusOK) - - contentStore := lfs.NewContentStore() - exist, err := contentStore.Exists(p) - assert.NoError(t, err) - assert.True(t, exist) - - meta, err := git_model.GetLFSMetaObjectByOid(t.Context(), repo.ID, p.Oid) - assert.NoError(t, err) - assert.NotNil(t, meta) - }) -} - -func TestAPILFSVerify(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.LFS.StartServer, true)() - - repo := createLFSTestRepository(t, "lfs-verify-repo") - oid := storeObjectInRepo(t, repo.ID, "dummy3") - defer git_model.RemoveLFSMetaObjectByOid(t.Context(), repo.ID, oid) - - session := loginUser(t, "user2") - - newRequest := func(t testing.TB, p *lfs.Pointer) *RequestWrapper { - return NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", p). - SetHeader("Accept", lfs.AcceptHeader). - SetHeader("Content-Type", lfs.MediaType) - } - - t.Run("InvalidJsonRequest", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, nil) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("InvalidPointer", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.Pointer{}) - - session.MakeRequest(t, req, http.StatusUnprocessableEntity) - }) - - t.Run("PointerNotExisting", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6}) - - session.MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("Success", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := newRequest(t, &lfs.Pointer{Oid: oid, Size: 6}) - - session.MakeRequest(t, req, http.StatusOK) + req := NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", testCase.ptr). + SetHeader("Accept", lfs.AcceptHeader). + SetHeader("Content-Type", lfs.MediaType) + session.MakeRequest(t, req, testCase.status) + }) + } }) } diff --git a/tests/integration/api_repo_secrets_test.go b/tests/integration/api_repo_secrets_test.go index 580b4736b10..b3987da25c2 100644 --- a/tests/integration/api_repo_secrets_test.go +++ b/tests/integration/api_repo_secrets_test.go @@ -30,75 +30,38 @@ func TestAPIRepoSecrets(t *testing.T) { MakeRequest(t, req, http.StatusOK) }) - t.Run("Create", func(t *testing.T) { - cases := []struct { - Name string - ExpectedStatus int - }{ - { - Name: "", - ExpectedStatus: http.StatusMethodNotAllowed, - }, - { - Name: "-", - ExpectedStatus: http.StatusBadRequest, - }, - { - Name: "_", - ExpectedStatus: http.StatusCreated, - }, - { - Name: "secret", - ExpectedStatus: http.StatusCreated, - }, - { - Name: "2secret", - ExpectedStatus: http.StatusBadRequest, - }, - { - Name: "GITEA_secret", - ExpectedStatus: http.StatusBadRequest, - }, - { - Name: "GITHUB_secret", - ExpectedStatus: http.StatusBadRequest, - }, - } - - for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ - Data: "data", - }).AddTokenAuth(token) - MakeRequest(t, req, c.ExpectedStatus) - } - }) - - t.Run("CreateWithDescription", func(t *testing.T) { - cases := []struct { - Name string - Description string - ExpectedStatus int - }{ - { - Name: "no_description", - Description: "", - ExpectedStatus: http.StatusCreated, - }, - { - Name: "description", - Description: "some description", - ExpectedStatus: http.StatusCreated, - }, - } - - for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ - Data: "data", - Description: c.Description, - }).AddTokenAuth(token) - MakeRequest(t, req, c.ExpectedStatus) - } - }) + type secretCase struct { + Name, Description string + ExpectedStatus int + } + for _, group := range []struct { + name string + cases []secretCase + }{ + {"Create", []secretCase{ + {"", "", http.StatusMethodNotAllowed}, + {"-", "", http.StatusBadRequest}, + {"_", "", http.StatusCreated}, + {"secret", "", http.StatusCreated}, + {"2secret", "", http.StatusBadRequest}, + {"GITEA_secret", "", http.StatusBadRequest}, + {"GITHUB_secret", "", http.StatusBadRequest}, + }}, + {"CreateWithDescription", []secretCase{ + {"no_description", "", http.StatusCreated}, + {"description", "some description", http.StatusCreated}, + }}, + } { + t.Run(group.name, func(t *testing.T) { + for _, c := range group.cases { + req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/%s", repo.FullName(), c.Name), api.CreateOrUpdateSecretOption{ + Data: "data", + Description: c.Description, + }).AddTokenAuth(token) + MakeRequest(t, req, c.ExpectedStatus) + } + }) + } t.Run("Update", func(t *testing.T) { name := "update_secret" diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 79859a28190..b723901fc5f 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -27,358 +27,419 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPIUserRepos(t *testing.T) { +func TestAPIRepoFixtureReads(t *testing.T) { defer tests.PrepareTestEnv(t)() - userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - targetUser := userOwner - testCases := []struct { - name string - doer *user_model.User - count int - privateCount int - }{ - {name: "Anonymous", doer: nil, count: 7}, - {name: "UserOwner", doer: userOwner, count: 14, privateCount: 7}, - {name: "UserOther", doer: userOther, count: 7}, - } + t.Run("UserRepos", func(t *testing.T) { + userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + targetUser := userOwner - for _, tc := range testCases { - var token string - if tc.doer != nil { - session := loginUser(t, tc.doer.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + testCases := []struct { + name string + doer *user_model.User + count int + privateCount int + }{ + {name: "Anonymous", doer: nil, count: 7}, + {name: "UserOwner", doer: userOwner, count: 14, privateCount: 7}, + {name: "UserOther", doer: userOther, count: 7}, } - t.Run(tc.name, func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", targetUser.Name).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - apiRepos := DecodeJSON(t, resp, []*api.Repository{}) - assert.Len(t, apiRepos, tc.count) - privateCount := 0 - for _, repo := range apiRepos { - assert.Equal(t, targetUser.ID, repo.Owner.ID) - if repo.Private { - privateCount++ - } + for _, tc := range testCases { + var token string + if tc.doer != nil { + session := loginUser(t, tc.doer.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) } - assert.Equal(t, tc.privateCount, privateCount) - }) - } -} -func TestAPISearchRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - const keyword = "test" + t.Run(tc.name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", targetUser.Name).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword) - resp := MakeRequest(t, req, http.StatusOK) + apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + assert.Len(t, apiRepos, tc.count) + privateCount := 0 + for _, repo := range apiRepos { + assert.Equal(t, targetUser.ID, repo.Owner.ID) + if repo.Private { + privateCount++ + } + } + assert.Equal(t, tc.privateCount, privateCount) + }) + } + }) - body := DecodeJSON(t, resp, &api.SearchResults{}) - assert.NotEmpty(t, body.Data) - for _, repo := range body.Data { - assert.Contains(t, repo.Name, keyword) - assert.False(t, repo.Private) - } + t.Run("SearchRepo", func(t *testing.T) { + const keyword = "test" - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) - orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) + req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword) + resp := MakeRequest(t, req, http.StatusOK) - defer test.MockVariableValue(&setting.API.DefaultPagingNum, 10)() + body := DecodeJSON(t, resp, &api.SearchResults{}) + assert.NotEmpty(t, body.Data) + for _, repo := range body.Data { + assert.Contains(t, repo.Name, keyword) + assert.False(t, repo.Private) + } - // Map of expected results, where key is user for login - type expectedResults map[*user_model.User]struct { - count int - repoOwnerID int64 - repoName string - includesPrivate bool - } + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}) + orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) - testCases := []struct { - name, requestURL string - expectedResults - }{ - { - name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ - nil: {count: 36}, - user: {count: 36}, - user2: {count: 36}, + defer test.MockVariableValue(&setting.API.DefaultPagingNum, 10)() + + // Map of expected results, where key is user for login + type expectedResults map[*user_model.User]struct { + count int + repoOwnerID int64 + repoName string + includesPrivate bool + } + + testCases := []struct { + name, requestURL string + expectedResults + }{ + { + name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{ + nil: {count: 36}, + user: {count: 36}, + user2: {count: 36}, + }, }, - }, - { - name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ - nil: {count: 10}, - user: {count: 10}, - user2: {count: 10}, + { + name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{ + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, }, - }, - { - name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{ - nil: {count: 10}, - user: {count: 10}, - user2: {count: 10}, + { + name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{ + nil: {count: 10}, + user: {count: 10}, + user2: {count: 10}, + }, }, - }, - { - name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{ - nil: {count: 7, repoName: "big_test_"}, - user: {count: 7, repoName: "big_test_"}, - user2: {count: 7, repoName: "big_test_"}, + { + name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{ + nil: {count: 7, repoName: "big_test_"}, + user: {count: 7, repoName: "big_test_"}, + user2: {count: 7, repoName: "big_test_"}, + }, }, - }, - { - name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{ - user2: {count: 2, repoName: "big_test_"}, + { + name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{ + user2: {count: 2, repoName: "big_test_"}, + }, }, - }, - { - name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{ - nil: {count: 5}, - user: {count: 9, includesPrivate: true}, - user2: {count: 6, includesPrivate: true}, + { + name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{ + nil: {count: 5}, + user: {count: 9, includesPrivate: true}, + user2: {count: 6, includesPrivate: true}, + }, }, - }, - { - name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 2, includesPrivate: true}, - user2: {count: 2, includesPrivate: true}, - user4: {count: 1}, + { + name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 2, includesPrivate: true}, + user2: {count: 2, includesPrivate: true}, + user4: {count: 1}, + }, }, - }, - { - name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{ - nil: {count: 1}, + { + name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 4, includesPrivate: true}, + user2: {count: 3, includesPrivate: true}, + org3: {count: 4, includesPrivate: true}, + }, + }, + { + name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ + nil: {count: 1, repoOwnerID: orgUser.ID}, + user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, + user2: {count: 1, repoOwnerID: orgUser.ID}, + }, + }, + {name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{ + nil: {count: 3}, user: {count: 4, includesPrivate: true}, - user2: {count: 3, includesPrivate: true}, - org3: {count: 4, includesPrivate: true}, - }, - }, - { - name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{ - nil: {count: 1, repoOwnerID: orgUser.ID}, - user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true}, - user2: {count: 1, repoOwnerID: orgUser.ID}, - }, - }, - {name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{ - nil: {count: 3}, - user: {count: 4, includesPrivate: true}, - user4: {count: 7, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{ - nil: {count: 0}, - user: {count: 1, includesPrivate: true}, - user4: {count: 1, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 1}, - user4: {count: 2, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 1}, - user4: {count: 2, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{ - nil: {count: 2}, - user: {count: 2}, - user4: {count: 4, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{ - nil: {count: 1}, - user: {count: 1}, - user4: {count: 2, includesPrivate: true}, - }}, - {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{ - nil: {count: 0}, - user: {count: 1, includesPrivate: true}, - user4: {count: 1, includesPrivate: true}, - }}, - } - - repoCache := cache.NewEphemeralCache(time.Hour) - getRepo := func(t *testing.T, id int64) *repo_model.Repository { - r, _ := cache.GetWithEphemeralCache(t.Context(), repoCache, "cache-group-test", fmt.Sprintf("repo:%d", id), func(context.Context, string) (*repo_model.Repository, error) { - return unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: id}), nil - }) - return r - } - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - for userToLogin, expected := range testCase.expectedResults { - var testName string - var userID int64 - var token string - if userToLogin != nil && userToLogin.ID > 0 { - testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) - session := loginUser(t, userToLogin.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - userID = userToLogin.ID - } else { - testName = "AnonymousUser" - _ = emptyTestSession(t) - } - - t.Run(testName, func(t *testing.T) { - request := NewRequest(t, "GET", testCase.requestURL). - AddTokenAuth(token) - response := MakeRequest(t, request, http.StatusOK) - - body := DecodeJSON(t, response, &api.SearchResults{}) - - repoNames := make([]string, 0, len(body.Data)) - for _, repo := range body.Data { - repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private)) - } - assert.Len(t, repoNames, expected.count) - for _, repo := range body.Data { - r := getRepo(t, repo.ID) - hasAccess, err := access_model.HasAnyUnitAccess(t.Context(), userID, r) - assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err) - assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName) - - assert.NotEmpty(t, repo.Name) - assert.Equal(t, repo.Name, r.Name) - - if len(expected.repoName) > 0 { - assert.Contains(t, repo.Name, expected.repoName) - } - - if expected.repoOwnerID > 0 { - assert.Equal(t, expected.repoOwnerID, repo.Owner.ID) - } - - if !expected.includesPrivate { - assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName) - } - } - }) - } - }) - } -} - -func TestAPIViewRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") - resp := MakeRequest(t, req, http.StatusOK) - repo := DecodeJSON(t, resp, &api.Repository{}) - assert.EqualValues(t, 1, repo.ID) - assert.Equal(t, "repo1", repo.Name) - assert.Equal(t, 2, repo.Releases) - assert.Equal(t, 1, repo.OpenIssues) - assert.Equal(t, 3, repo.OpenPulls) - - req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") - resp = MakeRequest(t, req, http.StatusOK) - repo = DecodeJSON(t, resp, &api.Repository{}) - assert.EqualValues(t, 10, repo.ID) - assert.Equal(t, "repo10", repo.Name) - assert.Equal(t, 1, repo.OpenPulls) - assert.Equal(t, 1, repo.Forks) - - req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") - resp = MakeRequest(t, req, http.StatusOK) - repo = DecodeJSON(t, resp, &api.Repository{}) - assert.EqualValues(t, 4, repo.ID) - assert.Equal(t, "repo4", repo.Name) - assert.Equal(t, 1, repo.Stars) -} - -func TestAPIOrgRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - - // org3 is an Org. Try to log in as different doers to list its repos. - targetOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - - testCases := []struct { - name string - doer *user_model.User - count int - privateCount int - }{ - {name: "Anonymous", doer: nil, count: 1}, - {name: "UserNormal", doer: userOwner, count: 3, privateCount: 2}, - {name: "UserAdmin", doer: userAdmin, count: 3, privateCount: 2}, - {name: "UserOther", doer: userOther, count: 1}, - } - - for _, tc := range testCases { - var token string - if tc.doer != nil { - session := loginUser(t, tc.doer.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + user4: {count: 7, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{ + nil: {count: 0}, + user: {count: 1, includesPrivate: true}, + user4: {count: 1, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{ + nil: {count: 2}, + user: {count: 2}, + user4: {count: 4, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{ + nil: {count: 1}, + user: {count: 1}, + user4: {count: 2, includesPrivate: true}, + }}, + {name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{ + nil: {count: 0}, + user: {count: 1, includesPrivate: true}, + user4: {count: 1, includesPrivate: true}, + }}, } - t.Run(tc.name, func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", targetOrg.Name).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + tokens := make(map[int64]string) + repoCache := cache.NewEphemeralCache(time.Hour) + getRepo := func(t *testing.T, id int64) *repo_model.Repository { + r, _ := cache.GetWithEphemeralCache(t.Context(), repoCache, "cache-group-test", fmt.Sprintf("repo:%d", id), func(context.Context, string) (*repo_model.Repository, error) { + return unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: id}), nil + }) + return r + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + for userToLogin, expected := range testCase.expectedResults { + var testName string + var userID int64 + var token string + if userToLogin != nil && userToLogin.ID > 0 { + testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) + userID = userToLogin.ID + token = tokens[userID] + if token == "" { + token = getTokenForLoggedInUser(t, loginUser(t, userToLogin.Name), auth_model.AccessTokenScopeReadRepository) + tokens[userID] = token + } + } else { + testName = "AnonymousUser" + _ = emptyTestSession(t) + } - apiRepos := DecodeJSON(t, resp, []*api.Repository{}) - assert.Len(t, apiRepos, tc.count) - privateCount := 0 - for _, repo := range apiRepos { - assert.Equal(t, targetOrg.ID, repo.Owner.ID) - if repo.Private { - privateCount++ + t.Run(testName, func(t *testing.T) { + request := NewRequest(t, "GET", testCase.requestURL). + AddTokenAuth(token) + response := MakeRequest(t, request, http.StatusOK) + + body := DecodeJSON(t, response, &api.SearchResults{}) + + repoNames := make([]string, 0, len(body.Data)) + for _, repo := range body.Data { + repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private)) + } + assert.Len(t, repoNames, expected.count) + for _, repo := range body.Data { + r := getRepo(t, repo.ID) + hasAccess, err := access_model.HasAnyUnitAccess(t.Context(), userID, r) + assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err) + assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName) + + assert.NotEmpty(t, repo.Name) + assert.Equal(t, repo.Name, r.Name) + + if len(expected.repoName) > 0 { + assert.Contains(t, repo.Name, expected.repoName) + } + + if expected.repoOwnerID > 0 { + assert.Equal(t, expected.repoOwnerID, repo.Owner.ID) + } + + if !expected.includesPrivate { + assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName) + } + } + }) } + }) + } + }) + + t.Run("ViewRepo", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + repo := DecodeJSON(t, resp, &api.Repository{}) + assert.EqualValues(t, 1, repo.ID) + assert.Equal(t, "repo1", repo.Name) + assert.Equal(t, 2, repo.Releases) + assert.Equal(t, 1, repo.OpenIssues) + assert.Equal(t, 3, repo.OpenPulls) + + req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") + resp = MakeRequest(t, req, http.StatusOK) + repo = DecodeJSON(t, resp, &api.Repository{}) + assert.EqualValues(t, 10, repo.ID) + assert.Equal(t, "repo10", repo.Name) + assert.Equal(t, 1, repo.OpenPulls) + assert.Equal(t, 1, repo.Forks) + + req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") + resp = MakeRequest(t, req, http.StatusOK) + repo = DecodeJSON(t, resp, &api.Repository{}) + assert.EqualValues(t, 4, repo.ID) + assert.Equal(t, "repo4", repo.Name) + assert.Equal(t, 1, repo.Stars) + }) + + t.Run("OrgRepos", func(t *testing.T) { + userOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + userOther := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + + // org3 is an Org. Try to log in as different doers to list its repos. + targetOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + + testCases := []struct { + name string + doer *user_model.User + count int + privateCount int + }{ + {name: "Anonymous", doer: nil, count: 1}, + {name: "UserNormal", doer: userOwner, count: 3, privateCount: 2}, + {name: "UserAdmin", doer: userAdmin, count: 3, privateCount: 2}, + {name: "UserOther", doer: userOther, count: 1}, + } + + for _, tc := range testCases { + var token string + if tc.doer != nil { + session := loginUser(t, tc.doer.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) } - assert.Equal(t, tc.privateCount, privateCount) - }) - } -} -// See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories. -func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID}) + t.Run(tc.name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", targetOrg.Name).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - // Disable code repository unit. - var units []unit_model.Type - units = append(units, unit_model.TypeCode) + apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + assert.Len(t, apiRepos, tc.count) + privateCount := 0 + for _, repo := range apiRepos { + assert.Equal(t, targetOrg.ID, repo.Owner.ID) + if repo.Private { + privateCount++ + } + } + assert.Equal(t, tc.privateCount, privateCount) + }) + } + }) - err := repo_service.UpdateRepositoryUnits(t.Context(), repo21, nil, units) - assert.NoError(t, err, "should have been able to delete code repository unit") + t.Run("GetRepoByIDUnauthorized", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + req := NewRequest(t, "GET", "/api/v1/repositories/2"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + }) - assert.False(t, repo21.UnitEnabled(t.Context(), unit_model.TypeCode)) + // mirror-sync must fail with "400 (Bad Request)" when an attempt is made to + // sync a non-mirror repository. + t.Run("MirrorSyncNonMirrorRepo", func(t *testing.T) { + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") + resp := MakeRequest(t, req, http.StatusOK) + repo := DecodeJSON(t, resp, &api.Repository{}) + assert.False(t, repo.Mirror) - req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name). - AddTokenAuth(token) + req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync"). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusBadRequest) + errRespJSON := DecodeJSON(t, resp, map[string]string{}) + assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) + }) - resp := MakeRequest(t, req, http.StatusOK) - apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + t.Run("RepoGetReviewers", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - var repoNames []string - for _, r := range apiRepos { - repoNames = append(repoNames, r.Name) - } + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + reviewers := DecodeJSON(t, resp, []*api.User{}) + if assert.Len(t, reviewers, 1) { + assert.ElementsMatch(t, []int64{2}, []int64{reviewers[0].ID}) + } + }) - assert.Contains(t, repoNames, repo21.Name) -} + t.Run("RepoGetAssignees", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) -func TestAPIGetRepoByIDUnauthorized(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - req := NewRequest(t, "GET", "/api/v1/repositories/2"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + assignees := DecodeJSON(t, resp, []*api.User{}) + assert.Len(t, assignees, 1) + + assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, assignee.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + + nonAssignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, nonAssignee.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, "org3"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + }) + + // See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories. + t.Run("OrgReposWithCodeUnitDisabled", func(t *testing.T) { + repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID}) + + // Disable code repository unit. + var units []unit_model.Type + units = append(units, unit_model.TypeCode) + + err := repo_service.UpdateRepositoryUnits(t.Context(), repo21, nil, units) + assert.NoError(t, err, "should have been able to delete code repository unit") + + assert.False(t, repo21.UnitEnabled(t.Context(), unit_model.TypeCode)) + + session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name). + AddTokenAuth(token) + + resp := MakeRequest(t, req, http.StatusOK) + apiRepos := DecodeJSON(t, resp, []*api.Repository{}) + + var repoNames []string + for _, r := range apiRepos { + repoNames = append(repoNames, r.Name) + } + + assert.Contains(t, repoNames, repo21.Name) + }) } func TestAPIRepoMigrate(t *testing.T) { @@ -460,26 +521,6 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { }) } -// mirror-sync must fail with "400 (Bad Request)" when an attempt is made to -// sync a non-mirror repository. -func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") - resp := MakeRequest(t, req, http.StatusOK) - repo := DecodeJSON(t, resp, &api.Repository{}) - assert.False(t, repo.Mirror) - - req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync"). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusBadRequest) - errRespJSON := DecodeJSON(t, resp, map[string]string{}) - assert.Equal(t, "Repository is not a mirror", errRespJSON["message"]) -} - func testAPIOrgCreateRepo(t *testing.T, session *TestSession, orgName, repoName string, status int) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) @@ -687,85 +728,28 @@ func TestAPIGenerateRepo(t *testing.T) { templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) - assertGeneratedRepoIsUsable := func(t *testing.T, ownerName string, repo *api.Repository) { - t.Helper() - assert.NotEmpty(t, repo.DefaultBranch) + for _, ownerName := range []string{ + // user + user.Name, + // org + "org3", + } { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: ownerName, + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + apiRepo := DecodeJSON(t, resp, &api.Repository{}) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ownerName, repo.Name, repo.DefaultBranch).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "new-repo", apiRepo.Name) + assert.NotEmpty(t, apiRepo.DefaultBranch) + + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ownerName, apiRepo.Name, apiRepo.DefaultBranch).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) branch := DecodeJSON(t, resp, &api.Branch{}) - assert.Equal(t, repo.DefaultBranch, branch.Name) - } - - // user - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ - Owner: user.Name, - Name: "new-repo", - Description: "test generate repo", - Private: false, - GitContent: true, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - apiRepo := DecodeJSON(t, resp, &api.Repository{}) - - assert.Equal(t, "new-repo", apiRepo.Name) - assertGeneratedRepoIsUsable(t, user.Name, apiRepo) - - // org - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ - Owner: "org3", - Name: "new-repo", - Description: "test generate repo", - Private: false, - GitContent: true, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - apiRepo = DecodeJSON(t, resp, &api.Repository{}) - - assert.Equal(t, "new-repo", apiRepo.Name) - assertGeneratedRepoIsUsable(t, "org3", apiRepo) -} - -func TestAPIRepoGetReviewers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - reviewers := DecodeJSON(t, resp, []*api.User{}) - if assert.Len(t, reviewers, 1) { - assert.ElementsMatch(t, []int64{2}, []int64{reviewers[0].ID}) + assert.Equal(t, apiRepo.DefaultBranch, branch.Name) } } - -func TestAPIRepoGetAssignees(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - assignees := DecodeJSON(t, resp, []*api.User{}) - assert.Len(t, assignees, 1) - - assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, assignee.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - nonAssignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, nonAssignee.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees/%s", user.Name, repo.Name, "org3"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) -} diff --git a/tests/integration/api_repo_topic_test.go b/tests/integration/api_repo_topic_test.go index bd45f0d440d..b1fa976507b 100644 --- a/tests/integration/api_repo_topic_test.go +++ b/tests/integration/api_repo_topic_test.go @@ -19,197 +19,168 @@ import ( "github.com/stretchr/testify/assert" ) -func TestAPITopicSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - searchURL, _ := url.Parse("/api/v1/topics/search") - - // search all topics - res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics := DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 6) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // pagination search topics first page - query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} - - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 4) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // pagination search topics second page - query = url.Values{"page": []string{"2"}, "limit": []string{"4"}} - - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 2) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // add keyword search - query = url.Values{"page": []string{"1"}, "limit": []string{"4"}} - query.Add("q", "topic") - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 2) - - query.Set("q", "database") - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - if assert.Len(t, topics.TopicNames, 1) { - assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.Equal(t, "database", topics.TopicNames[0].Name) - assert.Equal(t, 1, topics.TopicNames[0].RepoCount) - } -} - func TestAPIRepoTopic(t *testing.T) { defer tests.PrepareTestEnv(t)() - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of repo2 - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of repo3 - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // write access to repo 3 - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - // Get user2's token - token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeWriteRepository) + t.Run("Search", func(t *testing.T) { + searchURL, _ := url.Parse("/api/v1/topics/search") - // Test read topics using login - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name)). - AddTokenAuth(token2) - res := MakeRequest(t, req, http.StatusOK) - topics := DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) + for _, testCase := range []struct { + query url.Values + count int + total string + }{ + // search all topics + {nil, 6, "6"}, + // pagination search topics first page + {url.Values{"page": {"1"}, "limit": {"4"}}, 4, "6"}, + // pagination search topics second page + {url.Values{"page": {"2"}, "limit": {"4"}}, 2, "6"}, + // add keyword search + {url.Values{"page": {"1"}, "limit": {"4"}, "q": {"topic"}}, 2, ""}, + {url.Values{"page": {"1"}, "limit": {"4"}, "q": {"database"}}, 1, ""}, + } { + searchURL.RawQuery = testCase.query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + topics := DecodeJSON(t, res, &struct { + TopicNames []*api.TopicResponse `json:"topics"` + }{}) + validLength := assert.Len(t, topics.TopicNames, testCase.count) + if testCase.total != "" { + assert.Equal(t, testCase.total, res.Header().Get("x-total-count")) + } + if testCase.query.Get("q") == "database" && validLength { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) + } + } + }) - // Test delete a topic - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + t.Run("Archived", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 30}) // owner of the archived repo51 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 51}) + assert.True(t, repo.IsArchived) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository) - // Test add an existing topic - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Golang"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + // writing topics on an archived repo must be rejected, matching the web UI + req := NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusLocked) - // Test add a topic - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "topicName3"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusLocked) - url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) + req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name), + &api.RepoTopicOptions{Topics: []string{"archivedtopic"}}).AddTokenAuth(token) + MakeRequest(t, req, http.StatusLocked) - // Test read topics using token - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) + // reading topics stays allowed on an archived repo + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name)). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) - // Test replace topics - newTopics := []string{" windows ", " ", "MAC "} - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + t.Run("Repository", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of repo2 + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of repo3 + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // write access to repo 3 + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - // Test replace topics with something invalid - newTopics = []string{"topicname1", "topicname2", "topicname!"} - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.ElementsMatch(t, []string{"windows", "mac"}, topics.TopicNames) + // Get user2's token + token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeWriteRepository) - // Test with some topics multiple times, less than 25 unique - newTopics = []string{"t1", "t2", "t1", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "t13", "t14", "t15", "t16", "17", "t18", "t19", "t20", "t21", "t22", "t23", "t24", "t25"} - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", url). - AddTokenAuth(token2) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.Len(t, topics.TopicNames, 25) + // Test read topics using login + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name)). + AddTokenAuth(token2) + res := MakeRequest(t, req, http.StatusOK) + topics := DecodeJSON(t, res, &api.TopicName{}) + assert.ElementsMatch(t, []string{"topicname1", "topicname2"}, topics.TopicNames) - // Test writing more topics than allowed - newTopics = append(newTopics, "t26") - req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ - Topics: newTopics, - }).AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // Test delete a topic + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNoContent) - // Test add a topic when there is already maximum - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "t26"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusUnprocessableEntity) + // Test add an existing topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Golang"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNoContent) - // Test delete a topic that repo doesn't have - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). - AddTokenAuth(token2) - MakeRequest(t, req, http.StatusNotFound) + // Test add a topic + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "topicName3"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNoContent) - // Get user4's token - token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteRepository) + url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) - // Test read topics with write access - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", org3.Name, repo3.Name)). - AddTokenAuth(token4) - res = MakeRequest(t, req, http.StatusOK) - topics = DecodeJSON(t, res, &api.TopicName{}) - assert.Empty(t, topics.TopicNames) + // Test read topics using token + req = NewRequest(t, "GET", url). + AddTokenAuth(token2) + res = MakeRequest(t, req, http.StatusOK) + topics = DecodeJSON(t, res, &api.TopicName{}) + assert.ElementsMatch(t, []string{"topicname2", "golang", "topicname3"}, topics.TopicNames) - // Test add a topic to repo with write access (requires repo admin access) - req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", org3.Name, repo3.Name, "topicName"). - AddTokenAuth(token4) - MakeRequest(t, req, http.StatusForbidden) -} - -func TestAPIRepoTopicArchived(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 30}) // owner of the archived repo51 - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 51}) - assert.True(t, repo.IsArchived) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository) - - // writing topics on an archived repo must be rejected, matching the web UI - req := NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusLocked) - - req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user.Name, repo.Name, "archivedtopic"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusLocked) - - req = NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name), - &api.RepoTopicOptions{Topics: []string{"archivedtopic"}}).AddTokenAuth(token) - MakeRequest(t, req, http.StatusLocked) - - // reading topics stays allowed on an archived repo - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", user.Name, repo.Name)). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) + newTopics := []string{"t1", "t2", "t1", "t3", "t4", "t5", "t6", "t7", "t8", "t9", "t10", "t11", "t12", "t13", "t14", "t15", "t16", "17", "t18", "t19", "t20", "t21", "t22", "t23", "t24", "t25"} + for _, testCase := range []struct { + topics []string + status int + expected []string + }{ + // Test replace topics + {[]string{" windows ", " ", "MAC "}, http.StatusNoContent, []string{"windows", "mac"}}, + // Test replace topics with something invalid + {[]string{"topicname1", "topicname2", "topicname!"}, http.StatusUnprocessableEntity, []string{"windows", "mac"}}, + // Test with some topics multiple times, less than 25 unique + {newTopics, http.StatusNoContent, nil}, + } { + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: testCase.topics, + }).AddTokenAuth(token2) + MakeRequest(t, req, testCase.status) + req = NewRequest(t, "GET", url). + AddTokenAuth(token2) + res = MakeRequest(t, req, http.StatusOK) + topics = DecodeJSON(t, res, &api.TopicName{}) + if testCase.expected != nil { + assert.ElementsMatch(t, testCase.expected, topics.TopicNames) + } else { + assert.Len(t, topics.TopicNames, 25) + } + } + + // Test writing more topics than allowed + newTopics = append(newTopics, "t26") + req = NewRequestWithJSON(t, "PUT", url, &api.RepoTopicOptions{ + Topics: newTopics, + }).AddTokenAuth(token2) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test add a topic when there is already maximum + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "t26"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusUnprocessableEntity) + + // Test delete a topic that repo doesn't have + req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/topics/%s", user2.Name, repo2.Name, "Topicname1"). + AddTokenAuth(token2) + MakeRequest(t, req, http.StatusNotFound) + + // Get user4's token + token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteRepository) + + // Test read topics with write access + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/topics", org3.Name, repo3.Name)). + AddTokenAuth(token4) + res = MakeRequest(t, req, http.StatusOK) + topics = DecodeJSON(t, res, &api.TopicName{}) + assert.Empty(t, topics.TopicNames) + + // Test add a topic to repo with write access (requires repo admin access) + req = NewRequestf(t, "PUT", "/api/v1/repos/%s/%s/topics/%s", org3.Name, repo3.Name, "topicName"). + AddTokenAuth(token4) + MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/tests/integration/api_repository_creation_token_scope_test.go b/tests/integration/api_repository_creation_token_scope_test.go index 7091fd1ef7a..63035a3b44b 100644 --- a/tests/integration/api_repository_creation_token_scope_test.go +++ b/tests/integration/api_repository_creation_token_scope_test.go @@ -20,9 +20,10 @@ func TestAPIRepositoryCreationTokenScopes(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + session := loginUser(t, user.Name) t.Run("migration rejects public-only tokens", func(t *testing.T) { - publicOnlyToken := getUserToken(t, user.Name, + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopePublicOnly, ) @@ -30,19 +31,19 @@ func TestAPIRepositoryCreationTokenScopes(t *testing.T) { AddTokenAuth(publicOnlyToken) MakeRequest(t, req, http.StatusForbidden) - writeRepoToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteRepository) + writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req = NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &api.MigrateRepoOptions{Private: true}). AddTokenAuth(writeRepoToken) MakeRequest(t, req, http.StatusUnprocessableEntity) }) t.Run("organization creation requires repository scope", func(t *testing.T) { - orgOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) + orgOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) req := NewRequestWithJSON(t, "POST", "/api/v1/orgs/org3/repos", &api.CreateRepoOption{Name: "missing-repository-scope"}). AddTokenAuth(orgOnlyToken) MakeRequest(t, req, http.StatusForbidden) - orgRepoToken := getUserToken(t, user.Name, + orgRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, ) diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index 17a86ede464..be34f2f682e 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -29,176 +29,309 @@ import ( func TestAPITeam(t *testing.T) { defer tests.PrepareTestEnv(t)() - teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 1}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}) - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: teamUser.OrgID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) + t.Run("Search", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + results := DecodeJSON(t, resp, &TeamSearchResults{}) + assert.NotEmpty(t, results.Data) + assert.Len(t, results.Data, 1) + assert.Equal(t, "test_team", results.Data[0].Name) - apiTeam := DecodeJSON(t, resp, &api.Team{}) - assert.Equal(t, team.ID, apiTeam.ID) - assert.Equal(t, team.Name, apiTeam.Name) - assert.Equal(t, convert.ToOrganization(t.Context(), org), apiTeam.Organization) + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - // non team member user will not access the teams details - teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team"). + AddTokenAuth(token5) + MakeRequest(t, req, http.StatusForbidden) + }) - session = loginUser(t, user2.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). - AddTokenAuth(token) - _ = MakeRequest(t, req, http.StatusForbidden) + t.Run("GetTeamRepo", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID) - _ = MakeRequest(t, req, http.StatusUnauthorized) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &api.Repository{}) + assert.Equal(t, "big_test_private_4", teamRepo.Name) - // Get an admin user able to create, update and delete teams. - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - session = loginUser(t, user.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) + token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). + AddTokenAuth(token5) + MakeRequest(t, req, http.StatusNotFound) + }) - // Create team. - teamToCreate := &api.CreateTeamOption{ - Name: "team1", - Description: "team one", - IncludesAllRepositories: true, - Permission: "read", - Units: []string{"repo.code", "repo.issues"}, - } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - expectedTeamUnitsMap := map[string]string{"repo.code": "read", "repo.issues": "read"} - checkTeamResponse(t, "CreateTeam1", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) - checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) - teamID := apiTeam.ID + t.Run("CRUD", func(t *testing.T) { + teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 1}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: teamUser.OrgID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) - // Edit team. - editDescription := "team 1" - editFalse := false - teamToEdit := &api.EditTeamOption{ - Name: "teamone", - Description: &editDescription, - Permission: "admin", - IncludesAllRepositories: &editFalse, - } + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + req := NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam1", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + apiTeam := DecodeJSON(t, resp, &api.Team{}) + assert.Equal(t, team.ID, apiTeam.ID) + assert.Equal(t, team.Name, apiTeam.Name) + assert.Equal(t, convert.ToOrganization(t.Context(), org), apiTeam.Organization) - // Edit team Description only - editDescription = "first team" - teamToEditDesc := api.EditTeamOption{Description: &editDescription} - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam1_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + // non team member user will not access the teams details + teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) - // Edit team to granular permissions, team's permission should be reset to none - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ - Permission: "read", - Units: []string{"repo.code", "repo.issues"}, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam1_Granular", apiTeam, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) - checkTeamBean(t, teamID, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) + session = loginUser(t, user2.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID). + AddTokenAuth(token) + _ = MakeRequest(t, req, http.StatusForbidden) - // Read team. - teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, teamRead.LoadUnits(t.Context())) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "ReadTeam1", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamUser.TeamID) + _ = MakeRequest(t, req, http.StatusUnauthorized) - // Delete team. - req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + // Get an admin user able to create, update and delete teams. + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session = loginUser(t, user.Name) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - // create team again via UnitsMap (granular: do not send permission=write). - teamToCreate = &api.CreateTeamOption{ - Name: "team2", - Description: "team two", - IncludesAllRepositories: true, - UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"}, - } - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "CreateTeam2", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) - checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) - teamID = apiTeam.ID + org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) - // Create a team with permission=write, then edit it to granular permissions later, team's permission should be reset to none - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ - Permission: "write", - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam2_Write", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) - checkTeamBean(t, teamID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) + // Create team. + teamToCreate := &api.CreateTeamOption{ + Name: "team1", + Description: "team one", + IncludesAllRepositories: true, + Permission: "read", + Units: []string{"repo.code", "repo.issues"}, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusCreated) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + expectedTeamUnitsMap := map[string]string{"repo.code": "read", "repo.issues": "read"} + checkTeamResponse(t, "CreateTeam1", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, expectedTeamUnitsMap) + teamID := apiTeam.ID - // Edit team. - editDescription = "team 1" - editFalse = false - teamToEdit = &api.EditTeamOption{ - Name: "teamtwo", - Description: &editDescription, - IncludesAllRepositories: &editFalse, - UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"}, - } + // Edit team. + editDescription := "team 1" + editFalse := false + teamToEdit := &api.EditTeamOption{ + Name: "teamone", + Description: &editDescription, + Permission: "admin", + IncludesAllRepositories: &editFalse, + } - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam2", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam1", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - // Edit team Description only - editDescription = "second team" - teamToEditDesc = api.EditTeamOption{Description: &editDescription} - req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - checkTeamResponse(t, "EditTeam2_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) - checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + // Edit team Description only + editDescription = "first team" + teamToEditDesc := api.EditTeamOption{Description: &editDescription} + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam1_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelName(teamToEdit.Permission), nil) - // Read team. - teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - apiTeam = DecodeJSON(t, resp, &api.Team{}) - assert.NoError(t, teamRead.LoadUnits(t.Context())) - checkTeamResponse(t, "ReadTeam2", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + // Edit team to granular permissions, team's permission should be reset to none + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ + Permission: "read", + Units: []string{"repo.code", "repo.issues"}, + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam1_Granular", apiTeam, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) + checkTeamBean(t, teamID, teamToEdit.Name, editDescription, editFalse, api.AccessLevelNameNone, expectedTeamUnitsMap) - // Delete team. - req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + // Read team. + teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + assert.NoError(t, teamRead.LoadUnits(t.Context())) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "ReadTeam1", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + + // Delete team. + req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + + // create team again via UnitsMap (granular: do not send permission=write). + teamToCreate = &api.CreateTeamOption{ + Name: "team2", + Description: "team two", + IncludesAllRepositories: true, + UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"}, + } + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", org.Name), teamToCreate). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusCreated) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "CreateTeam2", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameNone, teamToCreate.UnitsMap) + teamID = apiTeam.ID + + // Create a team with permission=write, then edit it to granular permissions later, team's permission should be reset to none + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), api.EditTeamOption{ + Permission: "write", + }).AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam2_Write", apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) + checkTeamBean(t, teamID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, api.AccessLevelNameWrite, nil) + + // Edit team. + editDescription = "team 1" + editFalse = false + teamToEdit = &api.EditTeamOption{ + Name: "teamtwo", + Description: &editDescription, + IncludesAllRepositories: &editFalse, + UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"}, + } + + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEdit). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam2", apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + + // Edit team Description only + editDescription = "second team" + teamToEditDesc = api.EditTeamOption{Description: &editDescription} + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", teamID), teamToEditDesc). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + checkTeamResponse(t, "EditTeam2_DescOnly", apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, api.AccessLevelNameNone, teamToEdit.UnitsMap) + + // Read team. + teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = DecodeJSON(t, resp, &api.Team{}) + assert.NoError(t, teamRead.LoadUnits(t.Context())) + checkTeamResponse(t, "ReadTeam2", apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories, api.AccessLevelName(teamRead.AccessMode.ToString()), teamRead.GetUnitsMap()) + + // Delete team. + req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + }) + + t.Run("VisibilityAccess", func(t *testing.T) { + insertTestTeam := func(t *testing.T, orgID int64, name string, visibility structs.VisibleType) *organization.Team { + t.Helper() + team := &organization.Team{ + OrgID: orgID, + LowerName: name, + Name: name, + AccessMode: perm.AccessModeRead, + Visibility: visibility, + } + assert.NoError(t, db.Insert(t.Context(), team)) + return team + } + + limitedTeam := insertTestTeam(t, 3, "limited-team", structs.VisibleTypeLimited) + + // Org member who can read a limited team must not mutate its repos without membership. + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + token := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequestf(t, "PUT", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + + publicTeam := insertTestTeam(t, 23, "public-team", structs.VisibleTypePublic) + + // Public team in a private org must not be readable by outsiders. + outsider := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + token = getUserToken(t, outsider.Name, auth_model.AccessTokenScopeReadOrganization) + req = NewRequestf(t, "GET", "/api/v1/teams/%d", publicTeam.ID). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + // Member lookup must require org membership even for public teams. + req = NewRequestf(t, "GET", "/api/v1/teams/%d/members/%s", publicTeam.ID, outsider.Name). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNotFound) + + // A limited team's repo list must not leak repos the viewer cannot access. + // repo3 is private; user28 is an org3 member (team12, no repo access) who can + // read the limited team but has no access to repo3. + assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: 3, TeamID: limitedTeam.ID, RepoID: 3})) + user28 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) + token28 := getUserToken(t, user28.Name, auth_model.AccessTokenScopeReadOrganization) + + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos", limitedTeam.ID).AddTokenAuth(token28) + resp := MakeRequest(t, req, http.StatusOK) + var repos []*api.Repository + DecodeJSON(t, resp, &repos) + for _, r := range repos { + assert.NotEqual(t, int64(3), r.ID, "must not leak inaccessible private repo3") + } + + // The single-repo lookup must not confirm an inaccessible repo's existence. + req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID).AddTokenAuth(token28) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("RepositoryAccess", func(t *testing.T) { + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 12}) + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: team.OrgID}) + assert.False(t, org.RepoAdminChangeTeamAccess) + targetRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 32, OwnerID: org.ID}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user, perm.AccessModeAdmin)) + + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) + url := fmt.Sprintf("/api/v1/teams/%d/repos/%s", team.ID, targetRepo.FullName()) + + req := NewRequest(t, "PUT", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + + assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: targetRepo.ID})) + + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusForbidden) + unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + + siteAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + token = getUserToken(t, siteAdmin.Name, auth_model.AccessTokenScopeWriteOrganization) + req = NewRequest(t, "DELETE", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + req = NewRequest(t, "PUT", url).AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) + }) } func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission api.AccessLevelName, unitsMap map[string]string) { @@ -224,141 +357,3 @@ type TeamSearchResults struct { OK bool `json:"ok"` Data []*api.Team `json:"data"` } - -func TestAPITeamSearch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "_team"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - results := DecodeJSON(t, resp, &TeamSearchResults{}) - assert.NotEmpty(t, results.Data) - assert.Len(t, results.Data, 1) - assert.Equal(t, "test_team", results.Data[0].Name) - - // no access if not organization member - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team"). - AddTokenAuth(token5) - MakeRequest(t, req, http.StatusForbidden) -} - -func TestAPIGetTeamRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) - teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) - req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - DecodeJSON(t, resp, &api.Repository{}) - assert.Equal(t, "big_test_private_4", teamRepo.Name) - - // no access if not organization member - user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) - - req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/", team.ID, teamRepo.FullName()). - AddTokenAuth(token5) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestAPIAddRemoveTeamRepositoryRequiresOrgOwnerOrSetting(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 12}) - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: team.OrgID}) - assert.False(t, org.RepoAdminChangeTeamAccess) - targetRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 32, OwnerID: org.ID}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), targetRepo, user, perm.AccessModeAdmin)) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteOrganization) - url := fmt.Sprintf("/api/v1/teams/%d/repos/%s", team.ID, targetRepo.FullName()) - - req := NewRequest(t, "PUT", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) - - assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: targetRepo.ID})) - - req = NewRequest(t, "DELETE", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) - - siteAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - token = getUserToken(t, siteAdmin.Name, auth_model.AccessTokenScopeWriteOrganization) - req = NewRequest(t, "DELETE", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) - req = NewRequest(t, "PUT", url).AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: team.ID, RepoID: targetRepo.ID}) -} - -func TestAPITeamVisibilityAccess(t *testing.T) { - defer tests.PrepareTestEnv(t)() - insertTestTeam := func(t *testing.T, orgID int64, name string, visibility structs.VisibleType) *organization.Team { - t.Helper() - team := &organization.Team{ - OrgID: orgID, - LowerName: name, - Name: name, - AccessMode: perm.AccessModeRead, - Visibility: visibility, - } - assert.NoError(t, db.Insert(t.Context(), team)) - return team - } - - limitedTeam := insertTestTeam(t, 3, "limited-team", structs.VisibleTypeLimited) - - // Org member who can read a limited team must not mutate its repos without membership. - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - token := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteOrganization) - req := NewRequestf(t, "PUT", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - - publicTeam := insertTestTeam(t, 23, "public-team", structs.VisibleTypePublic) - - // Public team in a private org must not be readable by outsiders. - outsider := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token = getUserToken(t, outsider.Name, auth_model.AccessTokenScopeReadOrganization) - req = NewRequestf(t, "GET", "/api/v1/teams/%d", publicTeam.ID). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - // Member lookup must require org membership even for public teams. - req = NewRequestf(t, "GET", "/api/v1/teams/%d/members/%s", publicTeam.ID, outsider.Name). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) - - // A limited team's repo list must not leak repos the viewer cannot access. - // repo3 is private; user28 is an org3 member (team12, no repo access) who can - // read the limited team but has no access to repo3. - assert.NoError(t, db.Insert(t.Context(), &organization.TeamRepo{OrgID: 3, TeamID: limitedTeam.ID, RepoID: 3})) - user28 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - token28 := getUserToken(t, user28.Name, auth_model.AccessTokenScopeReadOrganization) - - req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos", limitedTeam.ID).AddTokenAuth(token28) - resp := MakeRequest(t, req, http.StatusOK) - var repos []*api.Repository - DecodeJSON(t, resp, &repos) - for _, r := range repos { - assert.NotEqual(t, int64(3), r.ID, "must not leak inaccessible private repo3") - } - - // The single-repo lookup must not confirm an inaccessible repo's existence. - req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/org3/repo3", limitedTeam.ID).AddTokenAuth(token28) - MakeRequest(t, req, http.StatusNotFound) -} diff --git a/tests/integration/api_token_self_test.go b/tests/integration/api_token_self_test.go index 2720c59d2c5..8f6841e76b3 100644 --- a/tests/integration/api_token_self_test.go +++ b/tests/integration/api_token_self_test.go @@ -20,35 +20,28 @@ import ( func TestAPIGetCurrentToken(t *testing.T) { defer tests.PrepareTestEnv(t)() - t.Run("Success with all scopes", func(t *testing.T) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-get-current-token-all", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + for _, testCase := range []struct { + name, tokenName string + scope auth_model.AccessTokenScope + }{ + {"Success with all scopes", "test-get-current-token-all", auth_model.AccessTokenScopeAll}, + {"Success with limited scopes", "test-get-current-token-limited", auth_model.AccessTokenScopeReadRepository}, + } { + t.Run(testCase.name, func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + accessToken := createAPIAccessTokenWithoutCleanUp(t, testCase.tokenName, user, []auth_model.AccessTokenScope{testCase.scope}) - req := NewRequest(t, "GET", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + resp := MakeRequest(t, req, http.StatusOK) - currentToken := DecodeJSON(t, resp, &api.CurrentAccessToken{}) - assert.Equal(t, accessToken.ID, currentToken.ID) - assert.Equal(t, accessToken.Name, currentToken.Name) - assert.Equal(t, user.ID, currentToken.User.ID) - assert.Equal(t, user.Name, currentToken.User.Login) - }) - - t.Run("Success with limited scopes", func(t *testing.T) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-get-current-token-limited", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeReadRepository}) - - req := NewRequest(t, "GET", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - resp := MakeRequest(t, req, http.StatusOK) - - currentToken := DecodeJSON(t, resp, &api.CurrentAccessToken{}) - assert.Equal(t, accessToken.ID, currentToken.ID) - assert.Equal(t, accessToken.Name, currentToken.Name) - assert.Equal(t, user.ID, currentToken.User.ID) - assert.Equal(t, user.Name, currentToken.User.Login) - }) + currentToken := DecodeJSON(t, resp, &api.CurrentAccessToken{}) + assert.Equal(t, accessToken.ID, currentToken.ID) + assert.Equal(t, accessToken.Name, currentToken.Name) + assert.Equal(t, user.ID, currentToken.User.ID) + assert.Equal(t, user.Name, currentToken.User.Login) + }) + } t.Run("Bad token", func(t *testing.T) { req := NewRequest(t, "GET", "/api/v1/token"). @@ -58,41 +51,39 @@ func TestAPIGetCurrentToken(t *testing.T) { req = NewRequest(t, "GET", "/api/v1/token") MakeRequest(t, req, http.StatusUnauthorized) }) -} -// TestAPITokenSelfService tests delete operations on token -func TestAPITokenSelfService(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // TestAPITokenSelfService tests delete operations on token + t.Run("SelfService", func(t *testing.T) { + t.Run("Success then verify deleted", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-delete-current-token", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - t.Run("Success then verify deleted", func(t *testing.T) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-delete-current-token", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + // Delete the token via the endpoint + req := NewRequest(t, "DELETE", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + MakeRequest(t, req, http.StatusNoContent) - // Delete the token via the endpoint - req := NewRequest(t, "DELETE", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - MakeRequest(t, req, http.StatusNoContent) + // Verify the token is deleted + unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID}) - // Verify the token is deleted - unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID}) + // Verify the token can no longer be used for GET + req = NewRequest(t, "GET", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + MakeRequest(t, req, http.StatusUnauthorized) - // Verify the token can no longer be used for GET - req = NewRequest(t, "GET", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - MakeRequest(t, req, http.StatusUnauthorized) + // Verify the token can no longer be used for DELETE + req = NewRequest(t, "DELETE", "/api/v1/token"). + AddTokenAuth(accessToken.Token) + MakeRequest(t, req, http.StatusUnauthorized) + }) - // Verify the token can no longer be used for DELETE - req = NewRequest(t, "DELETE", "/api/v1/token"). - AddTokenAuth(accessToken.Token) - MakeRequest(t, req, http.StatusUnauthorized) - }) + t.Run("Bad token", func(t *testing.T) { + req := NewRequest(t, "DELETE", "/api/v1/token"). + AddTokenAuth("this does not exist") + MakeRequest(t, req, http.StatusUnauthorized) - t.Run("Bad token", func(t *testing.T) { - req := NewRequest(t, "DELETE", "/api/v1/token"). - AddTokenAuth("this does not exist") - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", "/api/v1/token") - MakeRequest(t, req, http.StatusUnauthorized) + req = NewRequest(t, "DELETE", "/api/v1/token") + MakeRequest(t, req, http.StatusUnauthorized) + }) }) } diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 7f2986b1a2f..566a2de94e6 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -18,132 +18,243 @@ import ( "github.com/stretchr/testify/assert" ) -// TestAPICreateAndDeleteToken tests that token that was just created can be deleted -func TestAPICreateAndDeleteToken(t *testing.T) { +func TestAPITokens(t *testing.T) { defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - deleteAPIAccessToken(t, newAccessToken, user) + // TestAPIGetTokensPermission ensures that only the admin can get tokens from other users + t.Run("GetTokensPermission", func(t *testing.T) { + // admin can get tokens for other users + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + req := NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) - newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - deleteAPIAccessToken(t, newAccessToken, user) -} + // non-admin can get tokens for himself + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) -// TestAPICreateTokenScopeEscalation ensures a token-authenticated request cannot -// mint a new token with a broader scope than the authenticating token. -func TestAPICreateTokenScopeEscalation(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - // a write:user-scoped token authenticates the create requests below - writeUserToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser) - - // requesting a broader scope ("all") than the authenticating token is rejected - req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "escalated", - "scopes": []string{"all"}, + // non-admin can't get tokens for other users + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusForbidden) }) - req.Request.SetBasicAuth(user.Name, writeUserToken) - MakeRequest(t, req, http.StatusForbidden) - // requesting a subset scope ("read:user") is allowed - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "subset", - "scopes": []string{"read:user"}, + // TestAPIDeleteMissingToken ensures that error is thrown when token not found + t.Run("DeleteMissingToken", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNotFound) }) - req.Request.SetBasicAuth(user.Name, writeUserToken) - MakeRequest(t, req, http.StatusCreated) - // password (non-token) auth may still create a token with any scope - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "by-password", - "scopes": []string{"all"}, - }).AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + // TestAPICreateAndDeleteToken tests that token that was just created can be deleted + t.Run("CreateAndDeleteToken", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - // a public-only token must not mint a token that drops the public-only restriction - publicOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopePublicOnly) - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "still-public-only", - "scopes": []string{"write:user"}, + newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + deleteAPIAccessToken(t, newAccessToken, user) + + newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + deleteAPIAccessToken(t, newAccessToken, user) }) - req.Request.SetBasicAuth(user.Name, publicOnlyToken) - resp := MakeRequest(t, req, http.StatusCreated) - var createdToken api.AccessToken - DecodeJSON(t, resp, &createdToken) - assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) - // an unrestricted parent token may create a narrower public-only child: public-only is a restriction, - // not a grantable permission, so the subset check must not reject it - req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ - "name": "narrower-public-only", - "scopes": []string{"write:user", "public-only"}, + // TestAPIDeniesPermissionBasedOnTokenScope tests that API routes forbid access + // when the correct token scope is not included. + t.Run("DeniesPermissionBasedOnTokenScope", func(t *testing.T) { + // We'll assert that each endpoint, when fetched with a token with all + // scopes *except* the ones specified, a forbidden status code is returned. + // + // This is to protect against endpoints having their access check copied + // from other endpoints and not updated. + // + // Test cases are in alphabetical order by URL. + type route struct { + url, method string + level auth_model.AccessTokenScopeLevel + } + testCases := []struct { + categories []auth_model.AccessTokenScopeCategory + routes []route + }{ + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryAdmin}, + []route{ + {"/api/v1/admin/emails", "GET", auth_model.Read}, + {"/api/v1/admin/users", "GET", auth_model.Read}, + {"/api/v1/admin/users", "POST", auth_model.Write}, + {"/api/v1/admin/users/user2", "PATCH", auth_model.Write}, + {"/api/v1/admin/users/user2/orgs", "GET", auth_model.Read}, + {"/api/v1/admin/users/user2/orgs", "POST", auth_model.Write}, + {"/api/v1/admin/orgs", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryNotification}, + []route{ + {"/api/v1/notifications", "GET", auth_model.Read}, + {"/api/v1/notifications", "PUT", auth_model.Write}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository}, + []route{ + {"/api/v1/org/org1/repos", "POST", auth_model.Write}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryPackage}, + []route{ + {"/api/v1/packages/user1/type/name/1", "GET", auth_model.Read}, + {"/api/v1/packages/user1/type/name/1", "DELETE", auth_model.Write}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryRepository}, + []route{ + {"/api/v1/repos/user1/repo1", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1", "PATCH", auth_model.Write}, + {"/api/v1/repos/user1/repo1", "DELETE", auth_model.Write}, + {"/api/v1/repos/user1/repo1/branches", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/archive/foo", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryIssue}, + []route{ + {"/api/v1/repos/user1/repo1/issues", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryRepository}, + []route{ + {"/api/v1/repos/user1/repo1/media/foo", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/raw/foo", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/teams", "GET", auth_model.Read}, + {"/api/v1/repos/user1/repo1/teams/team1", "PUT", auth_model.Write}, + {"/api/v1/repos/user1/repo1/transfer", "POST", auth_model.Write}, + // Private repo + {"/api/v1/repos/user2/repo2", "GET", auth_model.Read}, + // Private repo + {"/api/v1/repos/user2/repo2", "GET", auth_model.Read}, + }, + }, + { + []auth_model.AccessTokenScopeCategory{auth_model.AccessTokenScopeCategoryUser}, + []route{ + {"/api/v1/user", "GET", auth_model.Read}, + {"/api/v1/user/emails", "GET", auth_model.Read}, + {"/api/v1/user/emails", "POST", auth_model.Write}, + {"/api/v1/user/emails", "DELETE", auth_model.Write}, + {"/api/v1/user/applications/oauth2", "GET", auth_model.Read}, + {"/api/v1/user/applications/oauth2", "POST", auth_model.Write}, + {"/api/v1/users/search", "GET", auth_model.Read}, + // Private user + {"/api/v1/users/user31", "GET", auth_model.Read}, + // Private user + {"/api/v1/users/user31/gpg_keys", "GET", auth_model.Read}, + }, + }, + } + + // User needs to be admin so that we can verify that tokens without admin + // scopes correctly deny access. + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.True(t, user.IsAdmin, "User needs to be admin") + + for _, testCase := range testCases { + for _, route := range testCase.routes { + requiredPermissions := make([]permission, len(testCase.categories)) + for i, category := range testCase.categories { + requiredPermissions[i] = permission{category, route.level} + } + runTestCase(t, &requiredScopeTestCase{route.url, route.method, requiredPermissions}, user) + } + } }) - req.Request.SetBasicAuth(user.Name, writeUserToken) - resp = MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, &createdToken) - assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) -} -// TestAPIDeleteMissingToken ensures that error is thrown when token not found -func TestAPIDeleteMissingToken(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + // TestAPIDeleteTokensPermission ensures that only the admin can delete tokens from other users + t.Run("DeleteTokensPermission", func(t *testing.T) { + admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNotFound) -} + // admin can delete tokens for other users + createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1"). + AddBasicAuth(admin.Name) + MakeRequest(t, req, http.StatusNoContent) -// TestAPIGetTokensPermission ensures that only the admin can get tokens from other users -func TestAPIGetTokensPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // non-admin can delete tokens for himself + createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2"). + AddBasicAuth(user2.Name) + MakeRequest(t, req, http.StatusNoContent) - // admin can get tokens for other users - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - req := NewRequest(t, "GET", "/api/v1/users/user2/tokens"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + // non-admin can't delete tokens for other users + createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) + req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3"). + AddBasicAuth(user4.Name) + MakeRequest(t, req, http.StatusForbidden) + }) - // non-admin can get tokens for himself - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + // TestAPICreateTokenScopeEscalation ensures a token-authenticated request cannot + // mint a new token with a broader scope than the authenticating token. + t.Run("CreateTokenScopeEscalation", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // non-admin can't get tokens for other users - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - req = NewRequest(t, "GET", "/api/v1/users/user2/tokens"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusForbidden) -} + // a write:user-scoped token authenticates the create requests below + writeUserToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser) -// TestAPIDeleteTokensPermission ensures that only the admin can delete tokens from other users -func TestAPIDeleteTokensPermission(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // requesting a broader scope ("all") than the authenticating token is rejected + req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "escalated", + "scopes": []string{"all"}, + }) + req.Request.SetBasicAuth(user.Name, writeUserToken) + MakeRequest(t, req, http.StatusForbidden) - admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + // requesting a subset scope ("read:user") is allowed + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "subset", + "scopes": []string{"read:user"}, + }) + req.Request.SetBasicAuth(user.Name, writeUserToken) + MakeRequest(t, req, http.StatusCreated) - // admin can delete tokens for other users - createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - req := NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1"). - AddBasicAuth(admin.Name) - MakeRequest(t, req, http.StatusNoContent) + // password (non-token) auth may still create a token with any scope + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "by-password", + "scopes": []string{"all"}, + }).AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) - // non-admin can delete tokens for himself - createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2"). - AddBasicAuth(user2.Name) - MakeRequest(t, req, http.StatusNoContent) + // a public-only token must not mint a token that drops the public-only restriction + publicOnlyToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopePublicOnly) + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "still-public-only", + "scopes": []string{"write:user"}, + }) + req.Request.SetBasicAuth(user.Name, publicOnlyToken) + resp := MakeRequest(t, req, http.StatusCreated) + var createdToken api.AccessToken + DecodeJSON(t, resp, &createdToken) + assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) - // non-admin can't delete tokens for other users - createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, []auth_model.AccessTokenScope{auth_model.AccessTokenScopeAll}) - req = NewRequest(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3"). - AddBasicAuth(user4.Name) - MakeRequest(t, req, http.StatusForbidden) + // an unrestricted parent token may create a narrower public-only child: public-only is a restriction, + // not a grantable permission, so the subset check must not reject it + req = NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", map[string]any{ + "name": "narrower-public-only", + "scopes": []string{"write:user", "public-only"}, + }) + req.Request.SetBasicAuth(user.Name, writeUserToken) + resp = MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, &createdToken) + assert.Contains(t, createdToken.Scopes, string(auth_model.AccessTokenScopePublicOnly)) + }) } type permission struct { @@ -161,379 +272,6 @@ func (c *requiredScopeTestCase) Name() string { return fmt.Sprintf("%v %v", c.method, c.url) } -// TestAPIDeniesPermissionBasedOnTokenScope tests that API routes forbid access -// when the correct token scope is not included. -func TestAPIDeniesPermissionBasedOnTokenScope(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // We'll assert that each endpoint, when fetched with a token with all - // scopes *except* the ones specified, a forbidden status code is returned. - // - // This is to protect against endpoints having their access check copied - // from other endpoints and not updated. - // - // Test cases are in alphabetical order by URL. - testCases := []requiredScopeTestCase{ - { - "/api/v1/admin/emails", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/admin/users", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/admin/users", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Write, - }, - }, - }, - { - "/api/v1/admin/users/user2", - "PATCH", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Write, - }, - }, - }, - { - "/api/v1/admin/users/user2/orgs", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/admin/users/user2/orgs", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Write, - }, - }, - }, - { - "/api/v1/admin/orgs", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryAdmin, - auth_model.Read, - }, - }, - }, - { - "/api/v1/notifications", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryNotification, - auth_model.Read, - }, - }, - }, - { - "/api/v1/notifications", - "PUT", - []permission{ - { - auth_model.AccessTokenScopeCategoryNotification, - auth_model.Write, - }, - }, - }, - { - "/api/v1/org/org1/repos", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryOrganization, - auth_model.Write, - }, - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/packages/user1/type/name/1", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryPackage, - auth_model.Read, - }, - }, - }, - { - "/api/v1/packages/user1/type/name/1", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryPackage, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1", - "PATCH", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/branches", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/archive/foo", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/issues", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryIssue, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/media/foo", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/raw/foo", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/teams", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/teams/team1", - "PUT", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - { - "/api/v1/repos/user1/repo1/transfer", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, - // Private repo - { - "/api/v1/repos/user2/repo2", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - // Private repo - { - "/api/v1/repos/user2/repo2", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user/emails", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user/emails", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Write, - }, - }, - }, - { - "/api/v1/user/emails", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Write, - }, - }, - }, - { - "/api/v1/user/applications/oauth2", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - { - "/api/v1/user/applications/oauth2", - "POST", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Write, - }, - }, - }, - { - "/api/v1/users/search", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - // Private user - { - "/api/v1/users/user31", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - // Private user - { - "/api/v1/users/user31/gpg_keys", - "GET", - []permission{ - { - auth_model.AccessTokenScopeCategoryUser, - auth_model.Read, - }, - }, - }, - } - - // User needs to be admin so that we can verify that tokens without admin - // scopes correctly deny access. - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.True(t, user.IsAdmin, "User needs to be admin") - - for _, testCase := range testCases { - runTestCase(t, &testCase, user) - } -} - // runTestCase Helper function to run a single test case. func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model.User) { t.Run(testCase.Name(), func(t *testing.T) { diff --git a/tests/integration/api_twofa_test.go b/tests/integration/api_twofa_test.go index 141b7e4cdcf..264e04c9240 100644 --- a/tests/integration/api_twofa_test.go +++ b/tests/integration/api_twofa_test.go @@ -20,92 +20,92 @@ import ( func TestAPITwoFactor(t *testing.T) { defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) + t.Run("WebAuthn", func(t *testing.T) { + // user1 has no webauthn enrolled, he can request API with basic auth + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + unittest.AssertNotExistsBean(t, &auth_model.WebAuthnCredential{UserID: user1.ID}) + req := NewRequest(t, "GET", "/api/v1/user") + req.SetBasicAuth(user1.Name, "password") + MakeRequest(t, req, http.StatusOK) - req := NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusOK) + // user1 has no webauthn enrolled, he can request git protocol with basic auth + req = NewRequest(t, "GET", "/user2/repo1/info/refs") + req.SetBasicAuth(user1.Name, "password") + MakeRequest(t, req, http.StatusOK) - otpKey, err := totp.Generate(totp.GenerateOpts{ - SecretSize: 40, - Issuer: "gitea-test", - AccountName: user.Name, + // user1 has no webauthn enrolled, he can request container package with basic auth + req = NewRequest(t, "GET", "/v2/token") + req.SetBasicAuth(user1.Name, "password") + resp := MakeRequest(t, req, http.StatusOK) + + type tokenResponse struct { + Token string `json:"token"` + } + tokenParsed := DecodeJSON(t, resp, &tokenResponse{}) + assert.NotEmpty(t, tokenParsed.Token) + + // user32 has webauthn enrolled, he can't request API with basic auth + user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) + unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{UserID: user32.ID}) + + req = NewRequest(t, "GET", "/api/v1/user") + req.SetBasicAuth(user32.Name, "notpassword") + resp = MakeRequest(t, req, http.StatusUnauthorized) + + type userResponse struct { + Message string `json:"message"` + } + userParsed := DecodeJSON(t, resp, &userResponse{}) + assert.Equal(t, "basic authorization is not allowed while WebAuthn enrolled", userParsed.Message) + + // user32 has webauthn enrolled, he can't request git protocol with basic auth + req = NewRequest(t, "GET", "/user2/repo1/info/refs") + req.SetBasicAuth(user32.Name, "notpassword") + MakeRequest(t, req, http.StatusUnauthorized) + + // user32 has webauthn enrolled, he can't request container package with basic auth + req = NewRequest(t, "GET", "/v2/token") + req.SetBasicAuth(user1.Name, "notpassword") + MakeRequest(t, req, http.StatusUnauthorized) }) - assert.NoError(t, err) - tfa := &auth_model.TwoFactor{ - UID: user.ID, - } - assert.NoError(t, tfa.SetSecret(otpKey.Secret())) + t.Run("TOTP", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) - assert.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) + req := NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusUnauthorized) + otpKey, err := totp.Generate(totp.GenerateOpts{ + SecretSize: 40, + Issuer: "gitea-test", + AccountName: user.Name, + }) + assert.NoError(t, err) - passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) - assert.NoError(t, err) + tfa := &auth_model.TwoFactor{ + UID: user.ID, + } + assert.NoError(t, tfa.SetSecret(otpKey.Secret())) - req = NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - req.Header.Set("X-Gitea-OTP", passcode) - MakeRequest(t, req, http.StatusOK) + assert.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) - // the same passcode must not be replayable on the basic-auth surface (RFC 6238 single-use) - req = NewRequest(t, "GET", "/api/v1/user"). - AddBasicAuth(user.Name) - req.Header.Set("X-Gitea-OTP", passcode) - MakeRequest(t, req, http.StatusUnauthorized) -} - -func TestBasicAuthWithWebAuthn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user1 has no webauthn enrolled, he can request API with basic auth - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - unittest.AssertNotExistsBean(t, &auth_model.WebAuthnCredential{UserID: user1.ID}) - req := NewRequest(t, "GET", "/api/v1/user") - req.SetBasicAuth(user1.Name, "password") - MakeRequest(t, req, http.StatusOK) - - // user1 has no webauthn enrolled, he can request git protocol with basic auth - req = NewRequest(t, "GET", "/user2/repo1/info/refs") - req.SetBasicAuth(user1.Name, "password") - MakeRequest(t, req, http.StatusOK) - - // user1 has no webauthn enrolled, he can request container package with basic auth - req = NewRequest(t, "GET", "/v2/token") - req.SetBasicAuth(user1.Name, "password") - resp := MakeRequest(t, req, http.StatusOK) - - type tokenResponse struct { - Token string `json:"token"` - } - tokenParsed := DecodeJSON(t, resp, &tokenResponse{}) - assert.NotEmpty(t, tokenParsed.Token) - - // user32 has webauthn enrolled, he can't request API with basic auth - user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) - unittest.AssertExistsAndLoadBean(t, &auth_model.WebAuthnCredential{UserID: user32.ID}) - - req = NewRequest(t, "GET", "/api/v1/user") - req.SetBasicAuth(user32.Name, "notpassword") - resp = MakeRequest(t, req, http.StatusUnauthorized) - - type userResponse struct { - Message string `json:"message"` - } - userParsed := DecodeJSON(t, resp, &userResponse{}) - assert.Equal(t, "basic authorization is not allowed while WebAuthn enrolled", userParsed.Message) - - // user32 has webauthn enrolled, he can't request git protocol with basic auth - req = NewRequest(t, "GET", "/user2/repo1/info/refs") - req.SetBasicAuth(user32.Name, "notpassword") - MakeRequest(t, req, http.StatusUnauthorized) - - // user32 has webauthn enrolled, he can't request container package with basic auth - req = NewRequest(t, "GET", "/v2/token") - req.SetBasicAuth(user1.Name, "notpassword") - MakeRequest(t, req, http.StatusUnauthorized) + req = NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusUnauthorized) + + passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) + assert.NoError(t, err) + + req = NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + req.Header.Set("X-Gitea-OTP", passcode) + MakeRequest(t, req, http.StatusOK) + + // the same passcode must not be replayable on the basic-auth surface (RFC 6238 single-use) + req = NewRequest(t, "GET", "/api/v1/user"). + AddBasicAuth(user.Name) + req.Header.Set("X-Gitea-OTP", passcode) + MakeRequest(t, req, http.StatusUnauthorized) + }) } diff --git a/tests/integration/api_user_avatar_test.go b/tests/integration/api_user_avatar_test.go index accf0b7dc7c..d78114610bb 100644 --- a/tests/integration/api_user_avatar_test.go +++ b/tests/integration/api_user_avatar_test.go @@ -25,48 +25,34 @@ func TestAPIUpdateUserAvatar(t *testing.T) { session := loginUser(t, normalUsername) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - // Test what happens if you use a valid image - avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) - assert.NoError(t, err) - if err != nil { - assert.FailNow(t, "Unable to open avatar.png") - } + t.Run("Delete", func(t *testing.T) { + req := NewRequest(t, "DELETE", "/api/v1/user/avatar"). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusNoContent) + }) - // Test what happens if you don't have a valid Base64 string - opts := api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString(avatar), - } + t.Run("Update", func(t *testing.T) { + // Test what happens if you use a valid image + avatar, err := os.ReadFile(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/avatar.png")) + assert.NoError(t, err) + if err != nil { + assert.FailNow(t, "Unable to open avatar.png") + } - req := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) - - opts = api.UpdateUserAvatarOption{ - Image: "Invalid", - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) - - // Test what happens if you use a file that is not an image - opts = api.UpdateUserAvatarOption{ - Image: base64.StdEncoding.EncodeToString([]byte("This is not an image")), - } - - req = NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusInternalServerError) -} - -func TestAPIDeleteUserAvatar(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - - req := NewRequest(t, "DELETE", "/api/v1/user/avatar"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNoContent) + for _, avatarTest := range []struct { + image string + status int + }{ + {base64.StdEncoding.EncodeToString(avatar), http.StatusNoContent}, + // Test what happens if you don't have a valid Base64 string + {"Invalid", http.StatusBadRequest}, + // Test what happens if you use a file that is not an image + {base64.StdEncoding.EncodeToString([]byte("This is not an image")), http.StatusInternalServerError}, + } { + req := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &api.UpdateUserAvatarOption{ + Image: avatarTest.image, + }).AddTokenAuth(token) + MakeRequest(t, req, avatarTest.status) + } + }) } diff --git a/tests/integration/api_user_block_test.go b/tests/integration/api_user_block_test.go index 56dc81cc0f1..ed7b0e05b0b 100644 --- a/tests/integration/api_user_block_test.go +++ b/tests/integration/api_user_block_test.go @@ -67,172 +67,105 @@ func TestBlockUser(t *testing.T) { return count } - t.Run("User", func(t *testing.T) { - var blockerID int64 = 16 - blockerName := "user16" - blockerToken := getUserToken(t, blockerName, auth_model.AccessTokenScopeWriteUser) + for _, blocker := range []struct { + name string + id int64 + username string + }{ + {"User", 16, "user16"}, + {"Organization", 3, "org3"}, + } { + t.Run(blocker.name, func(t *testing.T) { + var blockerToken string + blocksURL := "/api/v1/user/blocks" + counts := []func(*testing.T, int64, int64) int64{countStars, countWatches, countRepositoryTransfers} + if blocker.name == "User" { + blockerToken = getUserToken(t, blocker.username, auth_model.AccessTokenScopeWriteUser) + } else { + blockerToken = getUserToken(t, "user2", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization) + blocksURL = fmt.Sprintf("/api/v1/orgs/%s/blocks", blocker.username) + counts = append(counts, countAssignedIssues) + } + counts = append(counts, countCollaborations) - var blockeeID int64 = 10 - blockeeName := "user10" + var blockeeID int64 = 10 + blockeeName := "user10" - t.Run("Block", func(t *testing.T) { - req := NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName) - MakeRequest(t, req, http.StatusUnauthorized) + t.Run("Block", func(t *testing.T) { + req := NewRequest(t, "PUT", blocksURL+"/"+blockeeName) + MakeRequest(t, req, http.StatusUnauthorized) - assert.EqualValues(t, 1, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countCollaborations(t, blockerID, blockeeID)) + if blocker.name == "Organization" { + req = NewRequest(t, "PUT", blocksURL+"/user4"). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) // can't block member + } - req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNotFound) + for _, count := range counts { + assert.EqualValues(t, 1, count(t, blocker.id, blockeeID)) + } - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s?reason=test", blockeeName)). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "GET", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNotFound) - assert.EqualValues(t, 0, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countCollaborations(t, blockerID, blockeeID)) + req = NewRequest(t, "PUT", fmt.Sprintf("%s/%s?reason=test", blocksURL, blockeeName)). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNoContent) + for _, count := range counts { + assert.EqualValues(t, 0, count(t, blocker.id, blockeeID)) + } - req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user + req = NewRequest(t, "GET", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+"org3"). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block organization + req = NewRequest(t, "PUT", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user - req = NewRequest(t, "GET", "/api/v1/user/blocks") - MakeRequest(t, req, http.StatusUnauthorized) + req = NewRequest(t, "PUT", blocksURL+"/org3"). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) // can't block organization - req = NewRequest(t, "GET", "/api/v1/user/blocks"). - AddTokenAuth(blockerToken) - resp := MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", blocksURL) + MakeRequest(t, req, http.StatusUnauthorized) - users := DecodeJSON(t, resp, []api.User{}) + req = NewRequest(t, "GET", blocksURL). + AddTokenAuth(blockerToken) + resp := MakeRequest(t, req, http.StatusOK) - assert.Len(t, users, 1) - assert.Equal(t, blockeeName, users[0].UserName) + users := DecodeJSON(t, resp, []api.User{}) + + assert.Len(t, users, 1) + assert.Equal(t, blockeeName, users[0].UserName) + }) + + t.Run("Unblock", func(t *testing.T) { + req := NewRequest(t, "DELETE", blocksURL+"/"+blockeeName) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", blocksURL+"/"+blockeeName). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequest(t, "DELETE", blocksURL+"/org3"). + AddTokenAuth(blockerToken) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequest(t, "GET", blocksURL). + AddTokenAuth(blockerToken) + resp := MakeRequest(t, req, http.StatusOK) + + users := DecodeJSON(t, resp, []api.User{}) + + assert.Empty(t, users) + }) }) - - t.Run("Unblock", func(t *testing.T) { - req := NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+"org3"). - AddTokenAuth(blockerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "GET", "/api/v1/user/blocks"). - AddTokenAuth(blockerToken) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - - assert.Empty(t, users) - }) - }) - - t.Run("Organization", func(t *testing.T) { - var blockerID int64 = 3 - blockerName := "org3" - - doerToken := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteOrganization) - - var blockeeID int64 = 10 - blockeeName := "user10" - - t.Run("Block", func(t *testing.T) { - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, "user4")). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block member - - assert.EqualValues(t, 1, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countAssignedIssues(t, blockerID, blockeeID)) - assert.EqualValues(t, 1, countCollaborations(t, blockerID, blockeeID)) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s?reason=test", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNoContent) - - assert.EqualValues(t, 0, countStars(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countWatches(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countRepositoryTransfers(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countAssignedIssues(t, blockerID, blockeeID)) - assert.EqualValues(t, 0, countCollaborations(t, blockerID, blockeeID)) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user - - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, "org3")). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) // can't block organization - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks", blockerName)) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks", blockerName)). - AddTokenAuth(doerToken) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - - assert.Len(t, users, 1) - assert.Equal(t, blockeeName, users[0].UserName) - }) - - t.Run("Unblock", func(t *testing.T) { - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)) - MakeRequest(t, req, http.StatusUnauthorized) - - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, blockeeName)). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/%s/blocks/%s", blockerName, "org3")). - AddTokenAuth(doerToken) - MakeRequest(t, req, http.StatusBadRequest) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/blocks", blockerName)). - AddTokenAuth(doerToken) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - - assert.Empty(t, users) - }) - }) + } } diff --git a/tests/integration/api_user_email_test.go b/tests/integration/api_user_email_test.go index bc61a1d3939..99650db25e8 100644 --- a/tests/integration/api_user_email_test.go +++ b/tests/integration/api_user_email_test.go @@ -15,54 +15,6 @@ import ( "github.com/stretchr/testify/assert" ) -// TestAPIManageEmailsFeatureDisabled ensures the email management API honors the -// manage_credentials feature restriction, matching the web UI. -func TestAPIManageEmailsFeatureDisabled(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - - WithDisabledFeatures(t, setting.UserFeatureManageCredentials) - - addReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{ - Emails: []string{"user2-3@example.com"}, - }).AddTokenAuth(token) - MakeRequest(t, addReq, http.StatusNotFound) - - delReq := NewRequestWithJSON(t, "DELETE", "/api/v1/user/emails", &api.DeleteEmailOption{ - Emails: []string{"user2-2@example.com"}, - }).AddTokenAuth(token) - MakeRequest(t, delReq, http.StatusNotFound) -} - -func TestAPIListEmails(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - - req := NewRequest(t, "GET", "/api/v1/user/emails"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - emails := DecodeJSON(t, resp, []*api.Email{}) - - assert.Equal(t, []*api.Email{ - { - Email: "user2@example.com", - Verified: true, - Primary: true, - }, - { - Email: "user2-2@example.com", - Verified: false, - Primary: false, - }, - }, emails) -} - func TestAPIAddEmail(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -70,46 +22,85 @@ func TestAPIAddEmail(t *testing.T) { session := loginUser(t, normalUsername) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - opts := api.CreateEmailOption{ - Emails: []string{"user101@example.com"}, - } + t.Run("ManageCredentialsDisabled", func(t *testing.T) { + WithDisabledFeatures(t, setting.UserFeatureManageCredentials) - req := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusConflict) + addReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{ + Emails: []string{"user2-3@example.com"}, + }).AddTokenAuth(token) + MakeRequest(t, addReq, http.StatusNotFound) - opts = api.CreateEmailOption{ - Emails: []string{"user2-3@example.com"}, - } - req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) + delReq := NewRequestWithJSON(t, "DELETE", "/api/v1/user/emails", &api.DeleteEmailOption{ + Emails: []string{"user2-2@example.com"}, + }).AddTokenAuth(token) + MakeRequest(t, delReq, http.StatusNotFound) + }) - emails := DecodeJSON(t, resp, []*api.Email{}) - assert.Equal(t, []*api.Email{ - { - Email: "user2@example.com", - Verified: true, - Primary: true, - }, - { - Email: "user2-2@example.com", - Verified: false, - Primary: false, - }, - { - Email: "user2-3@example.com", - Verified: true, - Primary: false, - }, - }, emails) + t.Run("List", func(t *testing.T) { + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - opts = api.CreateEmailOption{ - Emails: []string{"notAEmail"}, - } - req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusBadRequest) + req := NewRequest(t, "GET", "/api/v1/user/emails"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + emails := DecodeJSON(t, resp, []*api.Email{}) + + assert.Equal(t, []*api.Email{ + { + Email: "user2@example.com", + Verified: true, + Primary: true, + }, + { + Email: "user2-2@example.com", + Verified: false, + Primary: false, + }, + }, emails) + }) + + t.Run("Add", func(t *testing.T) { + opts := api.CreateEmailOption{ + Emails: []string{"user101@example.com"}, + } + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusConflict) + + opts = api.CreateEmailOption{ + Emails: []string{"user2-3@example.com"}, + } + req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + + emails := DecodeJSON(t, resp, []*api.Email{}) + assert.Equal(t, []*api.Email{ + { + Email: "user2@example.com", + Verified: true, + Primary: true, + }, + { + Email: "user2-2@example.com", + Verified: false, + Primary: false, + }, + { + Email: "user2-3@example.com", + Verified: true, + Primary: false, + }, + }, emails) + + opts = api.CreateEmailOption{ + Emails: []string{"notAEmail"}, + } + req = NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &opts). + AddTokenAuth(token) + MakeRequest(t, req, http.StatusBadRequest) + }) } func TestAPIDeleteEmail(t *testing.T) { diff --git a/tests/integration/api_user_follow_test.go b/tests/integration/api_user_follow_test.go index 6a7b135b801..7aeec13a864 100644 --- a/tests/integration/api_user_follow_test.go +++ b/tests/integration/api_user_follow_test.go @@ -43,53 +43,29 @@ func TestAPIFollow(t *testing.T) { MakeRequest(t, req, http.StatusForbidden) }) - t.Run("ListFollowing", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, list := range []struct { + name string + url string + token string + username string + }{ + {"ListFollowing", fmt.Sprintf("/api/v1/users/%s/following", user2), token2, user1}, + {"ListMyFollowing", "/api/v1/user/following", token2, user1}, + {"ListFollowers", fmt.Sprintf("/api/v1/users/%s/followers", user1), token1, user2}, + {"ListMyFollowers", "/api/v1/user/followers", token1, user2}, + } { + t.Run(list.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/following", user2)). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", list.url). + AddTokenAuth(list.token) + resp := MakeRequest(t, req, http.StatusOK) - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user1, users[0].UserName) - }) - - t.Run("ListMyFollowing", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/following"). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user1, users[0].UserName) - }) - - t.Run("ListFollowers", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/followers", user1)). - AddTokenAuth(token1) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user2, users[0].UserName) - }) - - t.Run("ListMyFollowers", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/followers"). - AddTokenAuth(token1) - resp := MakeRequest(t, req, http.StatusOK) - - users := DecodeJSON(t, resp, []api.User{}) - assert.Len(t, users, 1) - assert.Equal(t, user2, users[0].UserName) - }) + users := DecodeJSON(t, resp, []api.User{}) + assert.Len(t, users, 1) + assert.Equal(t, list.username, users[0].UserName) + }) + } t.Run("CheckFollowing", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_user_orgs_test.go b/tests/integration/api_user_orgs_test.go index 381ea43f52d..ca067098e24 100644 --- a/tests/integration/api_user_orgs_test.go +++ b/tests/integration/api_user_orgs_test.go @@ -19,69 +19,165 @@ import ( func TestUserOrgs(t *testing.T) { defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - normalUsername := "user2" - privateMemberUsername := "user4" - unrelatedUsername := "user5" - orgs := getUserOrgs(t, adminUsername, normalUsername) + t.Run("UserOrgs", func(t *testing.T) { + adminUsername := "user1" + normalUsername := "user2" + privateMemberUsername := "user4" + unrelatedUsername := "user5" - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) - org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) - org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) + orgs := getUserOrgs(t, adminUsername, normalUsername) - assert.Equal(t, []*api.Organization{ - { - ID: 17, - Name: org17.Name, - UserName: org17.Name, - FullName: org17.FullName, - Email: org17.Email, - AvatarURL: org17.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 3, - Name: org3.Name, - UserName: org3.Name, - FullName: org3.FullName, - Email: org3.Email, - AvatarURL: org3.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 35, - Name: org35.Name, - UserName: org35.Name, - FullName: org35.FullName, - Email: org35.Email, - AvatarURL: org35.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "private", - }, - }, orgs) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - // user itself should get it's org's he is a member of - orgs = getUserOrgs(t, privateMemberUsername, privateMemberUsername) - assert.Len(t, orgs, 1) + assert.Equal(t, []*api.Organization{ + { + ID: 17, + Name: org17.Name, + UserName: org17.Name, + FullName: org17.FullName, + Email: org17.Email, + AvatarURL: org17.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + Name: org3.Name, + UserName: org3.Name, + FullName: org3.FullName, + Email: org3.Email, + AvatarURL: org3.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 35, + Name: org35.Name, + UserName: org35.Name, + FullName: org35.FullName, + Email: org35.Email, + AvatarURL: org35.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "private", + }, + }, orgs) - // unrelated user should not get private org membership of privateMemberUsername - orgs = getUserOrgs(t, unrelatedUsername, privateMemberUsername) - assert.Empty(t, orgs) + // user itself should get it's org's he is a member of + orgs = getUserOrgs(t, privateMemberUsername, privateMemberUsername) + assert.Len(t, orgs, 1) - orgs = getUserOrgs(t, "user29", adminUsername) - assert.Empty(t, orgs) + // unrelated user should not get private org membership of privateMemberUsername + orgs = getUserOrgs(t, unrelatedUsername, privateMemberUsername) + assert.Empty(t, orgs) - // not authenticated call should not be allowed - testUserOrgsUnauthenticated(t, privateMemberUsername) + orgs = getUserOrgs(t, "user29", adminUsername) + assert.Empty(t, orgs) + + // not authenticated call should not be allowed + testUserOrgsUnauthenticated(t, privateMemberUsername) + }) + + t.Run("MyOrgs", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/user/orgs") + MakeRequest(t, req, http.StatusUnauthorized) + + normalUsername := "user2" + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) + req = NewRequest(t, "GET", "/api/v1/user/orgs"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + orgs := DecodeJSON(t, resp, []*api.Organization{}) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) + + assert.Equal(t, []*api.Organization{ + { + ID: 17, + Name: org17.Name, + UserName: org17.Name, + FullName: org17.FullName, + Email: org17.Email, + AvatarURL: org17.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + Name: org3.Name, + UserName: org3.Name, + FullName: org3.FullName, + Email: org3.Email, + AvatarURL: org3.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 35, + Name: org35.Name, + UserName: org35.Name, + FullName: org35.FullName, + Email: org35.Email, + AvatarURL: org35.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "private", + }, + }, orgs) + }) + + t.Run("MyOrgsPublicOnly", func(t *testing.T) { + normalUsername := "user2" + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/user/orgs"). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + var orgs []*api.Organization + DecodeJSON(t, resp, &orgs) + org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) + org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) + + assert.Equal(t, []*api.Organization{ + { + ID: 17, + Name: org17.Name, + UserName: org17.Name, + FullName: org17.FullName, + Email: org17.Email, + AvatarURL: org17.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + { + ID: 3, + Name: org3.Name, + UserName: org3.Name, + FullName: org3.FullName, + Email: org3.Email, + AvatarURL: org3.AvatarLink(t.Context()), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, + }, orgs) + }) } func getUserOrgs(t *testing.T, userDoer, userCheck string) (orgs []*api.Organization) { @@ -101,100 +197,3 @@ func testUserOrgsUnauthenticated(t *testing.T, userCheck string) { req := NewRequestf(t, "GET", "/api/v1/users/%s/orgs", userCheck) session.MakeRequest(t, req, http.StatusUnauthorized) } - -func TestMyOrgs(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/api/v1/user/orgs") - MakeRequest(t, req, http.StatusUnauthorized) - - normalUsername := "user2" - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) - req = NewRequest(t, "GET", "/api/v1/user/orgs"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - orgs := DecodeJSON(t, resp, []*api.Organization{}) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) - org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) - org35 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "private_org35"}) - - assert.Equal(t, []*api.Organization{ - { - ID: 17, - Name: org17.Name, - UserName: org17.Name, - FullName: org17.FullName, - Email: org17.Email, - AvatarURL: org17.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 3, - Name: org3.Name, - UserName: org3.Name, - FullName: org3.FullName, - Email: org3.Email, - AvatarURL: org3.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 35, - Name: org35.Name, - UserName: org35.Name, - FullName: org35.FullName, - Email: org35.Email, - AvatarURL: org35.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "private", - }, - }, orgs) -} - -func TestMyOrgsPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/user/orgs"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var orgs []*api.Organization - DecodeJSON(t, resp, &orgs) - org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"}) - org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"}) - - assert.Equal(t, []*api.Organization{ - { - ID: 17, - Name: org17.Name, - UserName: org17.Name, - FullName: org17.FullName, - Email: org17.Email, - AvatarURL: org17.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - { - ID: 3, - Name: org3.Name, - UserName: org3.Name, - FullName: org3.FullName, - Email: org3.Email, - AvatarURL: org3.AvatarLink(t.Context()), - Description: "", - Website: "", - Location: "", - Visibility: "public", - }, - }, orgs) -} diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index 5cbf62ebec9..4a7be8b0434 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -21,133 +21,130 @@ type SearchResults struct { Data []*api.User `json:"data"` } -func TestAPIUserSearchLoggedIn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - query := "user2" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - assert.NotEmpty(t, user.Email) - } - - publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) - req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(publicToken) - resp = MakeRequest(t, req, http.StatusOK) - results = DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - assert.NotEmpty(t, user.Email) - assert.Equal(t, api.VisibilityStringPublic, user.Visibility) - } -} - -func TestAPIUserSearchNotLoggedIn(t *testing.T) { - defer tests.PrepareTestEnv(t)() - query := "user2" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - var modelUser *user_model.User - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) - assert.Equal(t, modelUser.GetPlaceholderEmail(), user.Email) - } -} - -func TestAPIUserSearchSystemUsers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - for _, systemUser := range []*user_model.User{ - user_model.NewGhostUser(), - user_model.NewActionsUser(), - } { - t.Run(systemUser.Name, func(t *testing.T) { - req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - if assert.Len(t, results.Data, 1) { - user := results.Data[0] - assert.Equal(t, user.UserName, systemUser.Name) - assert.Equal(t, user.ID, systemUser.ID) - } - }) - } -} - -func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { - defer tests.PrepareTestEnv(t)() - adminUsername := "user1" - session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - query := "user31" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.NotEmpty(t, results.Data) - for _, user := range results.Data { - assert.Contains(t, user.UserName, query) - assert.NotEmpty(t, user.Email) - assert.Equal(t, api.VisibilityStringPrivate, user.Visibility) - } -} - -func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { - defer tests.PrepareTestEnv(t)() - query := "user31" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) - resp := MakeRequest(t, req, http.StatusOK) - - results := DecodeJSON(t, resp, &SearchResults{}) - assert.Empty(t, results.Data) -} - -func TestAPIUserSearchByEmail(t *testing.T) { +func TestAPIUserSearch(t *testing.T) { defer tests.PrepareTestEnv(t)() - // admin can search user with private email - adminUsername := "user1" - session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - query := "user2@example.com" - req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("LoggedIn", func(t *testing.T) { + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) - results := DecodeJSON(t, resp, &SearchResults{}) - assert.Len(t, results.Data, 1) - assert.Equal(t, query, results.Data[0].Email) + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + } - // no login user can not search user with private email - req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) - resp = MakeRequest(t, req, http.StatusOK) - results = DecodeJSON(t, resp, &SearchResults{}) - assert.Empty(t, results.Data) + publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(publicToken) + resp = MakeRequest(t, req, http.StatusOK) + results = DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + assert.Equal(t, api.VisibilityStringPublic, user.Visibility) + } + }) - // user can search self with private email - user2 := "user2" - session = loginUser(t, user2) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) + t.Run("NotLoggedIn", func(t *testing.T) { + query := "user2" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) - results = DecodeJSON(t, resp, &SearchResults{}) - assert.Len(t, results.Data, 1) - assert.Equal(t, query, results.Data[0].Email) + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + var modelUser *user_model.User + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) + assert.Equal(t, modelUser.GetPlaceholderEmail(), user.Email) + } + }) + + t.Run("SystemUsers", func(t *testing.T) { + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + t.Run(systemUser.Name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + if assert.Len(t, results.Data, 1) { + user := results.Data[0] + assert.Equal(t, user.UserName, systemUser.Name) + assert.Equal(t, user.ID, systemUser.ID) + } + }) + } + }) + + t.Run("AdminLoggedInUserHidden", func(t *testing.T) { + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user31" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + assert.Equal(t, api.VisibilityStringPrivate, user.Visibility) + } + }) + + t.Run("NotLoggedInUserHidden", func(t *testing.T) { + query := "user31" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.Empty(t, results.Data) + }) + + t.Run("ByEmail", func(t *testing.T) { + // admin can search user with private email + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user2@example.com" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + results := DecodeJSON(t, resp, &SearchResults{}) + assert.Len(t, results.Data, 1) + assert.Equal(t, query, results.Data[0].Email) + + // no login user can not search user with private email + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp = MakeRequest(t, req, http.StatusOK) + results = DecodeJSON(t, resp, &SearchResults{}) + assert.Empty(t, results.Data) + + // user can search self with private email + user2 := "user2" + session = loginUser(t, user2) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + + results = DecodeJSON(t, resp, &SearchResults{}) + assert.Len(t, results.Data, 1) + assert.Equal(t, query, results.Data[0].Email) + }) } diff --git a/tests/integration/api_user_star_test.go b/tests/integration/api_user_star_test.go index 7ea7f9f9715..626b010f56c 100644 --- a/tests/integration/api_user_star_test.go +++ b/tests/integration/api_user_star_test.go @@ -26,221 +26,161 @@ import ( func TestAPIStar(t *testing.T) { defer tests.PrepareTestEnv(t)() - user := "user1" - repo := "user2/repo1" - - session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - - t.Run("Star", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNoContent) - - // blocked user can't star a repo - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("GetStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starred", user)). + t.Run("PublicOnly", func(t *testing.T) { + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + req := NewRequest(t, "GET", "/api/v1/user/starred"). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) + if assert.Len(t, repos, 1) { + assert.Equal(t, "user5/repo4", repos[0].FullName) + } - t.Run("GetMyStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred"). - AddTokenAuth(tokenWithUserScope) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) - - t.Run("IsStarring", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNoContent) - - req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("Unstar", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusNoContent) - }) -} - -// TestAPIStarredReposAccessRevoked ensures a private repository disappears from a user's -// starred list once their access to it has been revoked, so no metadata leaks afterwards. -func TestAPIStarredReposAccessRevoked(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user2/repo2 is a private repository owned by user2 - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) - - // grant the collaborator read access and star the private repo as them - require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), repo, collaborator, perm.AccessModeRead)) - require.NoError(t, repo_model.StarRepo(t.Context(), collaborator, repo, true)) - - token := getUserToken(t, collaborator.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - - // while access is granted, the private repo is visible in the starred list - req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - repos := DecodeJSON(t, resp, []api.Repository{}) - require.Len(t, repos, 1) - assert.Equal(t, repo.FullName(), repos[0].FullName) - - // revoke access - require.NoError(t, repo_service.DeleteCollaboration(t.Context(), repo, collaborator)) - - // the star record still exists, but the repo (and its metadata) must no longer be returned - assert.True(t, repo_model.IsStaring(t.Context(), collaborator.ID, repo.ID)) - req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - assert.Empty(t, repos) - - // sanity: the owner still sees their own private repo - ownerToken := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - require.NoError(t, repo_model.StarRepo(t.Context(), owner, repo, true)) - req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(ownerToken) - resp = MakeRequest(t, req, http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - fullNames := make([]string, len(repos)) - for i, r := range repos { - fullNames[i] = r.FullName - } - assert.Contains(t, fullNames, repo.FullName()) -} - -func TestAPIStarDisabled(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := "user1" - repo := "user2/repo1" - - session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) - tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) - - defer test.MockVariableValue(&setting.Repository.DisableStars, true)() - - t.Run("Star", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - - user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). - AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("GetStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starred", user)). + req = NewRequest(t, "GET", "/api/v1/users/user2/starred"). AddTokenAuth(token) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("GetMyStarredRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred"). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("IsStarring", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - - req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - }) - - t.Run("Unstar", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). - AddTokenAuth(tokenWithUserScope) - MakeRequest(t, req, http.StatusForbidden) - }) -} - -func TestAPIStarPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - req := NewRequest(t, "GET", "/api/v1/user/starred"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - repos := DecodeJSON(t, resp, []api.Repository{}) - if assert.Len(t, repos, 1) { + resp = MakeRequest(t, req, http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + require.Len(t, repos, 1) assert.Equal(t, "user5/repo4", repos[0].FullName) + }) + + for _, testCase := range []struct { + name string + disabled bool + writeStatus, listStatus, missingStatus int + }{ + {"Enabled", false, http.StatusNoContent, http.StatusOK, http.StatusNotFound}, + {"Disabled", true, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden}, + } { + t.Run(testCase.name, func(t *testing.T) { + user := "user1" + repo := "user2/repo1" + + session := loginUser(t, user) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + + if testCase.disabled { + defer test.MockVariableValue(&setting.Repository.DisableStars, true)() + } + + t.Run("Star", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.writeStatus) + + // blocked user can't star a repo + user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) + req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). + AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) + MakeRequest(t, req, http.StatusForbidden) + }) + + for _, listCase := range []struct { + name, url, token string + }{ + {"GetStarredRepos", fmt.Sprintf("/api/v1/users/%s/starred", user), token}, + {"GetMyStarredRepos", "/api/v1/user/starred", tokenWithUserScope}, + } { + t.Run(listCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", listCase.url). + AddTokenAuth(listCase.token) + resp := MakeRequest(t, req, testCase.listStatus) + + if !testCase.disabled { + assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) + + repos := DecodeJSON(t, resp, []api.Repository{}) + assert.Len(t, repos, 1) + assert.Equal(t, repo, repos[0].FullName) + } + }) + } + + t.Run("IsStarring", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.writeStatus) + + req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.missingStatus) + }) + + t.Run("Unstar", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). + AddTokenAuth(tokenWithUserScope) + MakeRequest(t, req, testCase.writeStatus) + }) + }) } - req = NewRequest(t, "GET", "/api/v1/users/user2/starred"). - AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - require.Len(t, repos, 1) - assert.Equal(t, "user5/repo4", repos[0].FullName) -} - -func TestAPIStarredReposOmitsInaccessiblePrivateRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) - privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - assert.True(t, privateRepo.IsPrivate) - assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) - assert.NoError(t, repo_model.StarRepo(t.Context(), user, privateRepo, true)) - assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) - - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - repos := DecodeJSON(t, resp, []api.Repository{}) - for _, repo := range repos { - assert.NotEqual(t, privateRepo.FullName(), repo.FullName) - } + t.Run("AccessRevoked", func(t *testing.T) { + // user2/repo2 is a private repository owned by user2 + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + // grant the collaborator read access and star the private repo as them + require.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), repo, collaborator, perm.AccessModeRead)) + require.NoError(t, repo_model.StarRepo(t.Context(), collaborator, repo, true)) + + token := getUserToken(t, collaborator.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + + // while access is granted, the private repo is visible in the starred list + req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + repos := DecodeJSON(t, resp, []api.Repository{}) + require.Len(t, repos, 1) + assert.Equal(t, repo.FullName(), repos[0].FullName) + + // revoke access + require.NoError(t, repo_service.DeleteCollaboration(t.Context(), repo, collaborator)) + + // the star record still exists, but the repo (and its metadata) must no longer be returned + assert.True(t, repo_model.IsStaring(t.Context(), collaborator.ID, repo.ID)) + req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + assert.Empty(t, repos) + + // sanity: the owner still sees their own private repo + ownerToken := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + require.NoError(t, repo_model.StarRepo(t.Context(), owner, repo, true)) + req = NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(ownerToken) + resp = MakeRequest(t, req, http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + fullNames := make([]string, len(repos)) + for i, r := range repos { + fullNames[i] = r.FullName + } + assert.Contains(t, fullNames, repo.FullName()) + }) + + t.Run("OmitsInaccessiblePrivateRepos", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 40}) + privateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + assert.True(t, privateRepo.IsPrivate) + assert.NoError(t, repo_service.AddOrUpdateCollaborator(t.Context(), privateRepo, user, perm.AccessModeRead)) + assert.NoError(t, repo_model.StarRepo(t.Context(), user, privateRepo, true)) + assert.NoError(t, repo_service.DeleteCollaboration(t.Context(), privateRepo, user)) + + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + req := NewRequest(t, "GET", "/api/v1/user/starred").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + repos := DecodeJSON(t, resp, []api.Repository{}) + for _, repo := range repos { + assert.NotEqual(t, privateRepo.FullName(), repo.FullName) + } + }) } diff --git a/tests/integration/api_user_update_test.go b/tests/integration/api_user_update_test.go deleted file mode 100644 index a3cdc2558af..00000000000 --- a/tests/integration/api_user_update_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/tests" -) - -func TestAPIUpdateUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) - - req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", map[string]string{ - "website": "https://gitea.com", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) -} diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go index 00805ae3fe4..3b6137dd086 100644 --- a/tests/integration/api_user_variables_test.go +++ b/tests/integration/api_user_variables_test.go @@ -15,6 +15,17 @@ import ( func TestAPIUserVariables(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("UpdateUser", func(t *testing.T) { + normalUsername := "user2" + session := loginUser(t, normalUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + + req := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", map[string]string{ + "website": "https://gitea.com", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) + session := loginUser(t, "user1") token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) diff --git a/tests/integration/api_user_watch_test.go b/tests/integration/api_user_watch_test.go index 073eb0ed0b7..3e907832af7 100644 --- a/tests/integration/api_user_watch_test.go +++ b/tests/integration/api_user_watch_test.go @@ -41,33 +41,25 @@ func TestAPIWatch(t *testing.T) { MakeRequest(t, req, http.StatusForbidden) }) - t.Run("GetWatchedRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, tc := range []struct { + name, url, token string + }{ + {"GetWatchedRepos", fmt.Sprintf("/api/v1/users/%s/subscriptions", user), token}, + {"GetMyWatchedRepos", "/api/v1/user/subscriptions", tokenWithRepoScope}, + } { + t.Run(tc.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/subscriptions", user)). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + req := NewRequest(t, "GET", tc.url).AddTokenAuth(tc.token) + resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) - - t.Run("GetMyWatchedRepos", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/api/v1/user/subscriptions"). - AddTokenAuth(tokenWithRepoScope) - resp := MakeRequest(t, req, http.StatusOK) - - assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) - - repos := DecodeJSON(t, resp, []api.Repository{}) - assert.Len(t, repos, 1) - assert.Equal(t, repo, repos[0].FullName) - }) + repos := DecodeJSON(t, resp, []api.Repository{}) + assert.Len(t, repos, 1) + assert.Equal(t, repo, repos[0].FullName) + }) + } t.Run("IsWatching", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -84,6 +76,27 @@ func TestAPIWatch(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) }) + t.Run("PublicOnly", func(t *testing.T) { + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) + + MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(tokenWithRepoScope), http.StatusOK) + MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(tokenWithRepoScope), http.StatusOK) + + resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) + repos := DecodeJSON(t, resp, []api.Repository{}) + for _, r := range repos { + assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName) + } + assert.NotContains(t, repoNames(repos), "user2/repo2") + + resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) + repos = DecodeJSON(t, resp, []api.Repository{}) + for _, r := range repos { + assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName) + } + assert.NotContains(t, repoNames(repos), "user2/repo2") + }) + t.Run("Unwatch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -92,28 +105,3 @@ func TestAPIWatch(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) }) } - -func TestAPIWatchPublicOnly(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user1") - writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadUser) - publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository) - - MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(writeRepoToken), http.StatusOK) - MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(writeRepoToken), http.StatusOK) - - resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) - repos := DecodeJSON(t, resp, []api.Repository{}) - for _, r := range repos { - assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName) - } - assert.NotContains(t, repoNames(repos), "user2/repo2") - - resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK) - repos = DecodeJSON(t, resp, []api.Repository{}) - for _, r := range repos { - assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName) - } - assert.NotContains(t, repoNames(repos), "user2/repo2") -} diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go index 2ae617c6cff..2b09906a0f1 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -19,155 +19,168 @@ import ( func TestAPIGetWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() - username := "user2" + t.Run("GetWikiPage", func(t *testing.T) { + username := "user2" - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Home", username, "repo1") + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Home", username, "repo1") - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - page := DecodeJSON(t, resp, &api.WikiPage{}) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + page := DecodeJSON(t, resp, &api.WikiPage{}) - assert.Equal(t, &api.WikiPage{ - WikiPageMetaData: &api.WikiPageMetaData{ - Title: "Home", - HTMLURL: page.HTMLURL, - SubURL: "Home", - LastCommit: &api.WikiCommit{ - ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", + expectedPage := &api.WikiPage{ + WikiPageMetaData: &api.WikiPageMetaData{ + Title: "Home", + HTMLURL: page.HTMLURL, + SubURL: "Home", + LastCommit: &api.WikiCommit{ + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", }, - Date: "2017-11-27T04:31:18Z", + Message: "Add Home.md\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Message: "Add Home.md\n", }, - }, - ContentBase64: base64.RawStdEncoding.EncodeToString( - []byte("# Home page\n\nThis is the home page!\n"), - ), - CommitCount: 1, - Sidebar: "", - Footer: "", - }, page) -} + ContentBase64: base64.RawStdEncoding.EncodeToString( + []byte("# Home page\n\nThis is the home page!\n"), + ), + CommitCount: 1, + Sidebar: "", + Footer: "", + } + expectedPage.LastCommit.Committer = expectedPage.LastCommit.Author + assert.Equal(t, expectedPage, page) + }) + t.Run("ListWikiPages", func(t *testing.T) { + username := "user2" -func TestAPIListWikiPages(t *testing.T) { - defer tests.PrepareTestEnv(t)() + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", username, "repo1") - username := "user2" + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", username, "repo1") + meta := DecodeJSON(t, resp, []*api.WikiPageMetaData{}) - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - meta := DecodeJSON(t, resp, []*api.WikiPageMetaData{}) - - dummymeta := []*api.WikiPageMetaData{ - { - Title: "Home", - HTMLURL: meta[0].HTMLURL, - SubURL: "Home", - LastCommit: &api.WikiCommit{ - ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", + dummymeta := []*api.WikiPageMetaData{ + { + Title: "Home", + HTMLURL: meta[0].HTMLURL, + SubURL: "Home", + LastCommit: &api.WikiCommit{ + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", }, - Date: "2017-11-27T04:31:18Z", + Message: "Add Home.md\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Message: "Add Home.md\n", }, - }, - { - Title: "Page With Image", - HTMLURL: meta[1].HTMLURL, - SubURL: "Page-With-Image", - LastCommit: &api.WikiCommit{ - ID: "0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", + { + Title: "Page With Image", + HTMLURL: meta[1].HTMLURL, + SubURL: "Page-With-Image", + LastCommit: &api.WikiCommit{ + ID: "0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:41:55Z", }, - Date: "2019-01-25T01:41:55Z", + Message: "Add jpeg.jpg and page with image\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", - }, - Date: "2019-01-25T01:41:55Z", - }, - Message: "Add jpeg.jpg and page with image\n", }, - }, - { - Title: "Page With Spaced Name", - HTMLURL: meta[2].HTMLURL, - SubURL: "Page-With-Spaced-Name", - LastCommit: &api.WikiCommit{ - ID: "c10d10b7e655b3dab1f53176db57c8219a5488d6", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", + { + Title: "Page With Spaced Name", + HTMLURL: meta[2].HTMLURL, + SubURL: "Page-With-Spaced-Name", + LastCommit: &api.WikiCommit{ + ID: "c10d10b7e655b3dab1f53176db57c8219a5488d6", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Gabriel Silva Simões", + Email: "simoes.sgabriel@gmail.com", + }, + Date: "2019-01-25T01:39:51Z", }, - Date: "2019-01-25T01:39:51Z", + Message: "Add page with spaced name\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Gabriel Silva Simões", - Email: "simoes.sgabriel@gmail.com", - }, - Date: "2019-01-25T01:39:51Z", - }, - Message: "Add page with spaced name\n", }, - }, - { - Title: "Unescaped File", - HTMLURL: meta[3].HTMLURL, - SubURL: "Unescaped-File", - LastCommit: &api.WikiCommit{ - ID: "0dca5bd9b5d7ef937710e056f575e86c0184ba85", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "6543", - Email: "6543@obermui.de", + { + Title: "Unescaped File", + HTMLURL: meta[3].HTMLURL, + SubURL: "Unescaped-File", + LastCommit: &api.WikiCommit{ + ID: "0dca5bd9b5d7ef937710e056f575e86c0184ba85", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "6543", + Email: "6543@obermui.de", + }, + Date: "2021-07-19T16:42:46Z", }, - Date: "2021-07-19T16:42:46Z", + Message: "add unescaped file\n", }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "6543", - Email: "6543@obermui.de", - }, - Date: "2021-07-19T16:42:46Z", - }, - Message: "add unescaped file\n", }, - }, - } + } - assert.Equal(t, dummymeta, meta) + for _, page := range dummymeta { + page.LastCommit.Committer = page.LastCommit.Author + } + assert.Equal(t, dummymeta, meta) + }) + t.Run("ListPageRevisions", func(t *testing.T) { + username := "user2" + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/revisions/Home", username, "repo1") + + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + revisions := DecodeJSON(t, resp, &api.WikiCommitList{}) + + dummyrevisions := &api.WikiCommitList{ + WikiCommits: []*api.WikiCommit{ + { + ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "Ethan Koenig", + Email: "ethantkoenig@gmail.com", + }, + Date: "2017-11-27T04:31:18Z", + }, + Message: "Add Home.md\n", + }, + }, + Count: 1, + } + + dummyrevisions.WikiCommits[0].Committer = dummyrevisions.WikiCommits[0].Author + assert.Equal(t, dummyrevisions, revisions) + }) + t.Run("EditWikiPage", func(t *testing.T) { + username := "user2" + session := loginUser(t, username) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name", username, "repo1") + + req := NewRequestWithJSON(t, "PATCH", urlStr, &api.CreateWikiPageOptions{ + Title: "edited title", + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Edited wiki page content for API unit tests")), + Message: "", + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusOK) + }) } func testAPICreateWikiPage(t *testing.T, session *TestSession, userName, repoName, title string, status int) { @@ -194,57 +207,3 @@ func TestAPINewWikiPage(t *testing.T) { testAPICreateWikiPage(t, session, username, "repo1", title, http.StatusCreated) } } - -func TestAPIEditWikiPage(t *testing.T) { - defer tests.PrepareTestEnv(t)() - username := "user2" - session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name", username, "repo1") - - req := NewRequestWithJSON(t, "PATCH", urlStr, &api.CreateWikiPageOptions{ - Title: "edited title", - ContentBase64: base64.StdEncoding.EncodeToString([]byte("Edited wiki page content for API unit tests")), - Message: "", - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusOK) -} - -func TestAPIListPageRevisions(t *testing.T) { - defer tests.PrepareTestEnv(t)() - username := "user2" - - urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/revisions/Home", username, "repo1") - - req := NewRequest(t, "GET", urlStr) - resp := MakeRequest(t, req, http.StatusOK) - - revisions := DecodeJSON(t, resp, &api.WikiCommitList{}) - - dummyrevisions := &api.WikiCommitList{ - WikiCommits: []*api.WikiCommit{ - { - ID: "2c54faec6c45d31c1abfaecdab471eac6633738a", - Author: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Committer: &api.CommitUser{ - Identity: api.Identity{ - Name: "Ethan Koenig", - Email: "ethantkoenig@gmail.com", - }, - Date: "2017-11-27T04:31:18Z", - }, - Message: "Add Home.md\n", - }, - }, - Count: 1, - } - - assert.Equal(t, dummyrevisions, revisions) -} diff --git a/tests/integration/attachment_test.go b/tests/integration/attachment_test.go index 58ac34095c4..ac8d653e198 100644 --- a/tests/integration/attachment_test.go +++ b/tests/integration/attachment_test.go @@ -27,15 +27,6 @@ import ( "github.com/stretchr/testify/require" ) -type attachmentScopeCase struct { - name string - url string - readIssueStatus int - readRepoStatus int - publicOnlyIssueStatus int - publicOnlyRepoStatus int -} - func testGeneratePngBytes() []byte { myImage := image.NewRGBA(image.Rect(0, 0, 32, 32)) var buff bytes.Buffer @@ -86,6 +77,88 @@ func testDeleteReleaseAttachment(t *testing.T, session *TestSession, repoURL, uu func TestAttachments(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("TokenScopes", func(t *testing.T) { + for _, uuid := range []string{ + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", + "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", + } { + _, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(uuid), strings.NewReader("hello world"), -1) + require.NoError(t, err) + } + + session := loginUser(t, "user2") + readIssueToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + readRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + miscToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadMisc) + publicOnlyIssueToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly) + publicOnlyRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) + + cases := []struct { + name string + url string + repoPath string + readIssueStatus int + readRepoStatus int + publicOnlyIssueStatus int + publicOnlyRepoStatus int + }{ + { + name: "PublicIssueAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", + repoPath: "/user2/repo1", + readIssueStatus: http.StatusOK, + readRepoStatus: http.StatusForbidden, + publicOnlyIssueStatus: http.StatusOK, + publicOnlyRepoStatus: http.StatusForbidden, + }, + { + name: "PrivateIssueAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", + repoPath: "/user2/repo2", + readIssueStatus: http.StatusOK, + readRepoStatus: http.StatusForbidden, + publicOnlyIssueStatus: http.StatusNotFound, + publicOnlyRepoStatus: http.StatusNotFound, + }, + { + name: "PublicReleaseAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", + repoPath: "/user2/repo1/releases", + readIssueStatus: http.StatusForbidden, + readRepoStatus: http.StatusOK, + publicOnlyIssueStatus: http.StatusForbidden, + publicOnlyRepoStatus: http.StatusOK, + }, + { + name: "PrivateReleaseAttachment", + url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", + repoPath: "/user2/repo2/releases", + readIssueStatus: http.StatusForbidden, + readRepoStatus: http.StatusOK, + publicOnlyIssueStatus: http.StatusNotFound, + publicOnlyRepoStatus: http.StatusNotFound, + }, + } + + for _, tc := range cases { + for _, route := range []struct { + name, url string + }{ + {"Global", tc.url}, + {"Repo", tc.repoPath + tc.url}, + } { + t.Run(route.name+tc.name, func(t *testing.T) { + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(miscToken), http.StatusForbidden) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(readIssueToken), tc.readIssueStatus) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(readRepoToken), tc.readRepoStatus) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(publicOnlyIssueToken), tc.publicOnlyIssueStatus) + MakeRequest(t, NewRequest(t, "GET", route.url).AddTokenAuth(publicOnlyRepoToken), tc.publicOnlyRepoStatus) + }) + } + } + }) t.Run("CreateAnonymousAttachment", testCreateAnonymousAttachment) t.Run("CreateUser2IssueAttachment", testCreateUser2IssueAttachment) t.Run("UploadAttachmentDeleteTemp", testUploadAttachmentDeleteTemp) @@ -235,100 +308,3 @@ func testDeleteAttachmentPermissions(t *testing.T) { // test deleting release attachment from another repo testDeleteReleaseAttachment(t, ownerSession, "/user2/repo2", crossRepoUUID, http.StatusBadRequest) } - -func TestAttachmentTokenScopes(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - for _, uuid := range []string{ - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", - "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", - } { - _, err := storage.Attachments.Save(repo_model.AttachmentRelativePath(uuid), strings.NewReader("hello world"), -1) - require.NoError(t, err) - } - - readIssueToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue) - readRepoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) - miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) - publicOnlyIssueToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly) - publicOnlyRepoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - - cases := []attachmentScopeCase{ - { - name: "GlobalPublicIssueAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusOK, - publicOnlyRepoStatus: http.StatusForbidden, - }, - { - name: "RepoPublicIssueAttachment", - url: "/user2/repo1/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusOK, - publicOnlyRepoStatus: http.StatusForbidden, - }, - { - name: "GlobalPrivateIssueAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - { - name: "RepoPrivateIssueAttachment", - url: "/user2/repo2/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", - readIssueStatus: http.StatusOK, - readRepoStatus: http.StatusForbidden, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - { - name: "GlobalPublicReleaseAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusForbidden, - publicOnlyRepoStatus: http.StatusOK, - }, - { - name: "RepoPublicReleaseAttachment", - url: "/user2/repo1/releases/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusForbidden, - publicOnlyRepoStatus: http.StatusOK, - }, - { - name: "GlobalPrivateReleaseAttachment", - url: "/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - { - name: "RepoPrivateReleaseAttachment", - url: "/user2/repo2/releases/attachments/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a22", - readIssueStatus: http.StatusForbidden, - readRepoStatus: http.StatusOK, - publicOnlyIssueStatus: http.StatusNotFound, - publicOnlyRepoStatus: http.StatusNotFound, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(miscToken), http.StatusForbidden) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(readIssueToken), tc.readIssueStatus) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(readRepoToken), tc.readRepoStatus) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(publicOnlyIssueToken), tc.publicOnlyIssueStatus) - MakeRequest(t, NewRequest(t, "GET", tc.url).AddTokenAuth(publicOnlyRepoToken), tc.publicOnlyRepoStatus) - }) - } -} diff --git a/tests/integration/auth_oauth2_test.go b/tests/integration/auth_oauth2_test.go index c65854c15bf..9ae37d73731 100644 --- a/tests/integration/auth_oauth2_test.go +++ b/tests/integration/auth_oauth2_test.go @@ -29,122 +29,361 @@ import ( "xorm.io/builder" ) -// TestMigrateAzureADV2ToOIDC simulates a login source migration from the Azure AD V2 OAuth2 provider to the OpenID Connect provider, -// and verifies that setting ExternalIDClaim = "oid" restores account continuity. -// -// Background: Azure AD V2 (goth's azureadv2 provider) fetches the user profile from Microsoft Graph API (/v1.0/me) -// and uses the "id" field - the stable Object ID (OID) - as gothUser.UserID. That OID is stored as ExternalID in external_login_user. -// -// When the admin migrates the same source to OpenID Connect, the goth openidConnect provider defaults to ["sub"] for UserIdClaims. -// Azure AD's "sub" is pairwise (unique per application), so it differs from the OID that was previously stored, -// causing every existing user to appear as a new account. -// -// Setting ExternalIDClaim = "oid" on the OIDC source overrides UserIdClaims to ["oid"], -// so the same OID is extracted and matched against the existing rows, restoring continuity. -func TestMigrateAzureADV2ToOIDC(t *testing.T) { +func TestAuthOAuth2Flows(t *testing.T) { defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - // Use UserID (gothUser.UserID) as the Gitea username so that different ExternalID values produce different, non-conflicting usernames. - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - const ( - sourceName = "test-migrate-azure" + // TestMigrateAzureADV2ToOIDC simulates a login source migration from the Azure AD V2 OAuth2 provider to the OpenID Connect provider, + // and verifies that setting ExternalIDClaim = "oid" restores account continuity. + // + // Background: Azure AD V2 (goth's azureadv2 provider) fetches the user profile from Microsoft Graph API (/v1.0/me) + // and uses the "id" field - the stable Object ID (OID) - as gothUser.UserID. That OID is stored as ExternalID in external_login_user. + // + // When the admin migrates the same source to OpenID Connect, the goth openidConnect provider defaults to ["sub"] for UserIdClaims. + // Azure AD's "sub" is pairwise (unique per application), so it differs from the OID that was previously stored, + // causing every existing user to appear as a new account. + // + // Setting ExternalIDClaim = "oid" on the OIDC source overrides UserIdClaims to ["oid"], + // so the same OID is extracted and matched against the existing rows, restoring continuity. + t.Run("MigrateAzureADV2ToOIDC", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + // Use UserID (gothUser.UserID) as the Gitea username so that different ExternalID values produce different, non-conflicting usernames. + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - // oidValue is the stable Azure AD Object ID, used as ExternalID by the Azure AD V2 provider. - oidValue = "oid-object-id-stable" + const ( + sourceName = "test-migrate-azure" - // subValue is the pairwise sub issued by Azure AD for OpenID Connect; it differs from oidValue and would produce a separate account if used. - subValue = "sub-pairwise-value" - ) + // oidValue is the stable Azure AD Object ID, used as ExternalID by the Azure AD V2 provider. + oidValue = "oid-object-id-stable" - // The fake OIDC server issues tokens containing both sub and oid claims, mirroring what Azure AD v2.0 returns. - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: subValue, OID: oidValue}) + // subValue is the pairwise sub issued by Azure AD for OpenID Connect; it differs from oidValue and would produce a separate account if used. + subValue = "sub-pairwise-value" + ) - // --- Step 1: Establish the legacy Azure AD V2 state --- - // Create an azureadv2 auth source. In production this would have been the source used before the migration. - addOAuth2Source(t, sourceName, oauth2.Source{ - Provider: "azureadv2", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - CustomURLMapping: &oauth2.CustomURLMapping{ - Tenant: "test-tenant-id", - }, - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) - require.NoError(t, err) + // The fake OIDC server issues tokens containing both sub and oid claims, mirroring what Azure AD v2.0 returns. + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: subValue, OID: oidValue}) - // Create a user to represent the "legacy" account that was originally registered through the Azure AD V2 provider. - legacyUser := &user_model.User{ - Name: "legacy-azure-user", - Email: "legacy-azure-user@example.com", - } - require.NoError(t, user_model.CreateUser(t.Context(), legacyUser, &user_model.Meta{})) - require.NoError(t, user_model.LinkExternalToUser(t.Context(), legacyUser, &user_model.ExternalLoginUser{ - ExternalID: oidValue, - UserID: legacyUser.ID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - })) + // --- Step 1: Establish the legacy Azure AD V2 state --- + // Create an azureadv2 auth source. In production this would have been the source used before the migration. + addOAuth2Source(t, sourceName, oauth2.Source{ + Provider: "azureadv2", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + Tenant: "test-tenant-id", + }, + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) + require.NoError(t, err) - // --- Step 2: Migrate the source to OIDC without ExternalIDClaim --- - // The provider type of the OAuth2 source is changed from azureadv2 to openidConnect. - // Without ExternalIDClaim the goth provider defaults to "sub", which does not match the stored OID, so every sign-in creates a fresh account. - authSource.Cfg = &oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", - // ExternalIDClaim intentionally not set; goth defaults to "sub". - } - err = auth_model.UpdateSource(t.Context(), authSource) - require.NoError(t, err) + // Create a user to represent the "legacy" account that was originally registered through the Azure AD V2 provider. + legacyUser := &user_model.User{ + Name: "legacy-azure-user", + Email: "legacy-azure-user@example.com", + } + require.NoError(t, user_model.CreateUser(t.Context(), legacyUser, &user_model.Meta{})) + require.NoError(t, user_model.LinkExternalToUser(t.Context(), legacyUser, &user_model.ExternalLoginUser{ + ExternalID: oidValue, + UserID: legacyUser.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + })) - t.Run("without ExternalIDClaim: legacy user is NOT matched", func(t *testing.T) { - // Confirm the external user with ExternalID=subValue doesn't exist. - unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + // --- Step 2: Migrate the source to OIDC without ExternalIDClaim --- + // The provider type of the OAuth2 source is changed from azureadv2 to openidConnect. + // Without ExternalIDClaim the goth provider defaults to "sub", which does not match the stored OID, so every sign-in creates a fresh account. + authSource.Cfg = &oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + // ExternalIDClaim intentionally not set; goth defaults to "sub". + } + err = auth_model.UpdateSource(t.Context(), authSource) + require.NoError(t, err) - doOIDCSignIn(t, sourceName) + t.Run("without ExternalIDClaim: legacy user is NOT matched", func(t *testing.T) { + // Confirm the external user with ExternalID=subValue doesn't exist. + unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - // "sub" is now the ExternalID - a new user was auto-registered. - subEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - // The auto-registered user is NOT the legacy user. - assert.NotEqual(t, legacyUser.ID, subEntry.UserID) + doOIDCSignIn(t, sourceName) + + // "sub" is now the ExternalID - a new user was auto-registered. + subEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: subValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + // The auto-registered user is NOT the legacy user. + assert.NotEqual(t, legacyUser.ID, subEntry.UserID) + }) + + // --- Step 3: Set ExternalIDClaim = "oid" to restore account continuity --- + // Set ExternalIDClaim = "oid" so that the OIDC source extracts the same Object ID that the Azure AD V2 provider previously stored. + oauth2Source, ok := authSource.Cfg.(*oauth2.Source) + require.True(t, ok) + oauth2Source.ExternalIDClaim = "oid" + err = auth_model.UpdateSource(t.Context(), authSource) + require.NoError(t, err) + + t.Run("with ExternalIDClaim=oid: legacy user IS matched", func(t *testing.T) { + // Confirm the legacy oid row has no RawData yet - it was created directly via LinkExternalToUser in setup, without going through an OAuth flow. + oidEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + require.Nil(t, oidEntry.RawData) + + doOIDCSignIn(t, sourceName) + + // After sign-in, RawData should contain both "oid" and "name". + oidEntry = unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + assert.Equal(t, oidValue, oidEntry.RawData["oid"]) + assert.Equal(t, "OIDC Test User", oidEntry.RawData["name"]) + + // The matched user must still be the original legacy user. + assert.Equal(t, legacyUser.ID, oidEntry.UserID) + }) }) - // --- Step 3: Set ExternalIDClaim = "oid" to restore account continuity --- - // Set ExternalIDClaim = "oid" so that the OIDC source extracts the same Object ID that the Azure AD V2 provider previously stored. - oauth2Source, ok := authSource.Cfg.(*oauth2.Source) - require.True(t, ok) - oauth2Source.ExternalIDClaim = "oid" - err = auth_model.UpdateSource(t.Context(), authSource) - require.NoError(t, err) + t.Run("OIDCIgnoresStaleExternalLoginLinks", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - t.Run("with ExternalIDClaim=oid: legacy user IS matched", func(t *testing.T) { - // Confirm the legacy oid row has no RawData yet - it was created directly via LinkExternalToUser in setup, without going through an OAuth flow. - oidEntry := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - require.Nil(t, oidEntry.RawData) + for _, tc := range []struct { + name, sourceName, sub, userName string + staleUserID int64 + }{ + {"organization", "test-oidc-stale-org-link", "oidc-stale-org-link-sub", "guizar_m", 3}, + // link the external account to a user id that does not exist, simulating a deleted user + {"deleted user", "test-oidc-stale-deleted", "oidc-stale-deleted-link-sub", "guizar_d", 999999}, + } { + t.Run(tc.name, func(t *testing.T) { + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: tc.sub, OID: tc.sub + "-oid", Email: tc.userName + "@example.com", Name: "OIDC Test User"}) + addOAuth2Source(t, tc.sourceName, oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), tc.sourceName) + require.NoError(t, err) + correctUser := &user_model.User{Name: tc.userName, Email: tc.userName + "@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), correctUser, &user_model.Meta{})) - doOIDCSignIn(t, sourceName) - - // After sign-in, RawData should contain both "oid" and "name". - oidEntry = unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: oidValue, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - assert.Equal(t, oidValue, oidEntry.RawData["oid"]) - assert.Equal(t, "OIDC Test User", oidEntry.RawData["name"]) - - // The matched user must still be the original legacy user. - assert.Equal(t, legacyUser.ID, oidEntry.UserID) + staleUser := &user_model.User{ID: tc.staleUserID} + if tc.staleUserID == 3 { + staleUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) + } + require.NoError(t, user_model.LinkExternalToUser(t.Context(), staleUser, &user_model.ExternalLoginUser{ + ExternalID: tc.sub, + UserID: staleUser.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + })) + doOIDCSignIn(t, authSource.Name) + // external_login_user has no "id" column, so order by the primary key instead + externalLink := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: tc.sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + assert.Equal(t, correctUser.ID, externalLink.UserID) + assert.Equal(t, correctUser.Email, externalLink.Email) + assert.Equal(t, "OIDC Test User", externalLink.Name) + }) + } }) -} -func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() + // TestOAuth2CallbackReactivationGating exercises the gate in handleOAuth2SignIn: + // an inactive user can only be reactivated when who was disabled by auto-sync + t.Run("OAuth2CallbackReactivationGating", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - setup := func(t *testing.T, sourceName, sub, userName, email string) (*auth_model.Source, *user_model.User) { - t.Helper() - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, OID: sub + "-oid", Email: email, Name: "OIDC Test User"}) + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"}) + addOAuth2Source(t, "test-oauth-source", oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oauth-source") + require.NoError(t, err) + + u := &user_model.User{Name: "test-user", Email: "test@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + + extLink := &user_model.ExternalLoginUser{ + UserID: u.ID, + LoginSourceID: authSource.ID, + Provider: authSource.Name, + ExternalID: "test-sub", + } + require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, extLink)) + + prepareUserExternalLink := func(t *testing.T, accessToken, refreshToken string, expiresAt time.Time) { + err := user_model.UpdateUserCols(t.Context(), &user_model.User{ID: u.ID, IsActive: false}, "is_active") + require.NoError(t, err) + _, err = db.GetEngine(t.Context()).Where(builder.Eq{"user_id": u.ID}).Cols("access_token", "refresh_token", "expires_at"). + Update(&user_model.ExternalLoginUser{AccessToken: accessToken, RefreshToken: refreshToken, ExpiresAt: expiresAt}) + require.NoError(t, err) + require.False(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}).IsActive) + } + + t.Run("admin-disabled user is not reactivated", func(t *testing.T) { + prepareUserExternalLink(t, "an-access-token", "non-empty-refresh-token", time.Now().Add(time.Hour)) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.False(t, after.IsActive, "OAuth callback must not re-enable an administrator-disabled account") + }) + + t.Run("admin-disabled user without refresh token is not reactivated", func(t *testing.T) { + // GitHub / OIDC-without-offline_access sources never store a refresh token, so a + // stored access token (and no refresh token) is the normal admin-disabled state + prepareUserExternalLink(t, "an-access-token", "" /* no refresh token */, time.Now().Add(time.Hour)) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.False(t, after.IsActive, "OAuth callback must not re-enable an admin-disabled account on a no-refresh-token source") + }) + + t.Run("auto-sync-disabled user is reactivated", func(t *testing.T) { + // the auto-sync cron clears all three token fields when it disables a user + prepareUserExternalLink(t, "", "", time.Time{}) + doOIDCSignIn(t, authSource.Name) + after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) + assert.True(t, after.IsActive, "OAuth callback must reactivate a sync-disabled account on successful login") + }) + }) + + // TestOAuth2GroupClaimsAppliedOnFirstLogin verifies that group claims from OAuth2/OIDC + // are correctly applied to newly created users on the first login + t.Run("OAuth2GroupClaimsAppliedOnFirstLogin", func(t *testing.T) { + // Enable auto-registration to ensure first login creates user with group claims + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + // Use sub claim as username for deterministic user naming + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() + + tt := []struct { + Name string + IsAdmin bool + IsRestricted bool + SourceName string + }{ + { + Name: "user in both admin and restricted groups", + IsAdmin: true, + IsRestricted: true, + SourceName: "test-group-claims", + }, + { + Name: "no groups", + IsAdmin: false, + IsRestricted: false, + SourceName: "test-no-groups", + }, + } + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + // Set up OIDC server with group claims + srv := newFakeOIDCServer(t, FakeOIDCConfig{ + Sub: tc.SourceName, + Email: tc.SourceName + "@example.com", + Name: "Test User", + Groups: []string{"admins", "restricted-users"}, + }) + + // Ensure it's the first login so no user in database + unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) + + addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) + + doOIDCSignIn(t, tc.SourceName) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) + assert.Equal(t, tc.IsAdmin, user.IsAdmin) + assert.Equal(t, tc.IsRestricted, user.IsRestricted) + assert.Equal(t, auth_model.OAuth2, user.LoginType) + }) + } + }) + + // TestOAuth2GroupClaimsManualLinking tests that group claims are applied correctly + // when a user goes through the manual linking flow (auto-registration disabled). + t.Run("OAuth2GroupClaimsManualLinking", func(t *testing.T) { + // Disable auto-registration to force manual linking flow + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, false)() + defer test.MockVariableValue(&setting.Service.AllowOnlyInternalRegistration, false)() + + tt := []struct { + Name string + IsAdmin bool + IsRestricted bool + SourceName string + }{ + { + Name: "user in both admin and restricted groups", + IsAdmin: true, + IsRestricted: true, + SourceName: "test-group-claims-manual-linking", + }, + { + Name: "no groups", + IsAdmin: false, + IsRestricted: false, + SourceName: "test-no-groups-manual-linking", + }, + } + + for _, tc := range tt { + t.Run(tc.Name, func(t *testing.T) { + srv := newFakeOIDCServer(t, FakeOIDCConfig{ + Sub: tc.SourceName, + Email: tc.SourceName + "@example.com", + Name: "Manual User", + Groups: []string{"admins", "restricted-users"}, + }) + addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) + unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+tc.SourceName), http.StatusTemporaryRedirect) + + location := resp.Header().Get("Location") + u, err := url.Parse(location) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state, "redirect to OIDC provider must include state") + + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", tc.SourceName, url.QueryEscape(state)) + session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + + // Submit the form to create a new account + linkAccountResp := session.MakeRequest(t, NewRequest(t, "GET", "/user/link_account"), http.StatusOK) + // Verify we're on the link account page + assert.Contains(t, linkAccountResp.Body.String(), "link_account") + + // Use NewRequestWithValues to POST form data (no CSRF needed in tests) + // Field names are lowercase in HTML forms: user_name, email, password, retype + req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ + "user_name": tc.SourceName, + "email": tc.SourceName + "@example.com", + "password": "", // AllowOnlyExternalRegistration means no password needed + "retype": "", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) + assert.Equal(t, tc.IsAdmin, user.IsAdmin) + assert.Equal(t, tc.IsRestricted, user.IsRestricted) + assert.Equal(t, auth_model.OAuth2, user.LoginType) + }) + } + }) + + // TestOAuth2AutoLinkWithTwoFactor verifies that automatic account linking completes + // after the user passes local 2FA when an OIDC identity matches an existing account. + t.Run("OAuth2AutoLinkWithTwoFactor", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() + + const ( + sourceName = "test-oauth-auto-link-2fa" + sub = "oidc-auto-link-2fa-sub" + email = "oidc-auto-link-2fa@example.com" + userName = "oidc-auto-link-2fa" + ) + + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: email, Name: "OIDC Auto Link 2FA"}) addOAuth2Source(t, sourceName, oauth2.Source{ Provider: "openidConnect", ClientID: "test-client-id", @@ -153,109 +392,110 @@ func TestOIDCIgnoresStaleExternalLoginLinks(t *testing.T) { }) authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) require.NoError(t, err) - correctUser := &user_model.User{Name: userName, Email: email} - require.NoError(t, user_model.CreateUser(t.Context(), correctUser, &user_model.Meta{})) - return authSource, correctUser - } - // assertRelinked signs in via OIDC and asserts the stale link now points at the correct individual user. - assertRelinked := func(t *testing.T, authSource *auth_model.Source, sub string, correctUser *user_model.User) { - t.Helper() - doOIDCSignIn(t, authSource.Name) - // external_login_user has no "id" column, so order by the primary key instead + localUser := &user_model.User{Name: userName, Email: email} + require.NoError(t, user_model.CreateUser(t.Context(), localUser, &user_model.Meta{})) + + otpKey, err := totp.Generate(totp.GenerateOpts{ + SecretSize: 40, + Issuer: "gitea-test", + AccountName: localUser.Name, + }) + require.NoError(t, err) + + tfa := &auth_model.TwoFactor{UID: localUser.ID} + require.NoError(t, tfa.SetSecret(otpKey.Secret())) + require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) + + unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) + + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) + + location := resp.Header().Get("Location") + u, err := url.Parse(location) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state) + + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) + resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + assert.Contains(t, resp.Header().Get("Location"), "/user/two_factor") + + session.MakeRequest(t, NewRequest(t, "GET", "/user/two_factor"), http.StatusOK) + + passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) + require.NoError(t, err) + + req := NewRequestWithValues(t, "POST", "/user/two_factor", map[string]string{ + "passcode": passcode, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + externalLink := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - assert.Equal(t, correctUser.ID, externalLink.UserID) - assert.Equal(t, correctUser.Email, externalLink.Email) - assert.Equal(t, "OIDC Test User", externalLink.Name) - } + assert.Equal(t, localUser.ID, externalLink.UserID) - t.Run("organization", func(t *testing.T) { - const sub, userName, email = "oidc-stale-org-link-sub", "guizar_m", "guizar_m@example.com" - authSource, correctUser := setup(t, "test-oidc-stale-org-link", sub, userName, email) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) - require.NoError(t, user_model.LinkExternalToUser(t.Context(), org, &user_model.ExternalLoginUser{ - ExternalID: sub, - UserID: org.ID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - })) - assertRelinked(t, authSource, sub, correctUser) + session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusOK) }) - t.Run("deleted user", func(t *testing.T) { - const sub, userName, email = "oidc-stale-deleted-link-sub", "guizar_d", "guizar_d@example.com" - const deletedUserID = 999999 - authSource, correctUser := setup(t, "test-oidc-stale-deleted", sub, userName, email) - // link the external account to a user id that does not exist, simulating a deleted user - require.NoError(t, user_model.LinkExternalToUser(t.Context(), &user_model.User{ID: deletedUserID}, &user_model.ExternalLoginUser{ - ExternalID: sub, - UserID: deletedUserID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - })) - assertRelinked(t, authSource, sub, correctUser) - }) -} + // a security key must be challenged on every path that issues a session, not just the password login + t.Run("WebAuthnSecondFactorRequired", func(t *testing.T) { + newWebAuthnUser := func(t *testing.T, name string) *user_model.User { + u := &user_model.User{Name: name, Email: name + "@example.com"} + require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + _, err := auth_model.CreateCredential(t.Context(), u.ID, "test-key", &webauthn.Credential{ID: []byte(name)}) + require.NoError(t, err) + return u + } -// TestOAuth2CallbackReactivationGating exercises the gate in handleOAuth2SignIn: -// an inactive user can only be reactivated when who was disabled by auto-sync -func TestOAuth2CallbackReactivationGating(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() + assertOAuth2Challenged := func(t *testing.T, sourceName string) { + session := emptyTestSession(t) + resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) + u, err := url.Parse(resp.Header().Get("Location")) + require.NoError(t, err) + state := u.Query().Get("state") + require.NotEmpty(t, state) - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: "test-sub", Email: "test@example.com", Name: "Test User"}) - addOAuth2Source(t, "test-oauth-source", oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oauth-source") - require.NoError(t, err) + callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) + resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) + assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") + session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) // the redirect alone does not prove no session was issued + } - u := &user_model.User{Name: "test-user", Email: "test@example.com"} - require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) + t.Run("OAuth2AutoLink", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() + defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - extLink := &user_model.ExternalLoginUser{ - UserID: u.ID, - LoginSourceID: authSource.ID, - Provider: authSource.Name, - ExternalID: "test-sub", - } - require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, extLink)) + const sourceName, sub = "oauth-autolink-webauthn", "autolink-sub" + u := newWebAuthnUser(t, "autolink-webauthn") + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) + addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) + assertOAuth2Challenged(t, sourceName) + }) - prepareUserExternalLink := func(t *testing.T, accessToken, refreshToken string, expiresAt time.Time) { - err := user_model.UpdateUserCols(t.Context(), &user_model.User{ID: u.ID, IsActive: false}, "is_active") - require.NoError(t, err) - _, err = db.GetEngine(t.Context()).Where(builder.Eq{"user_id": u.ID}).Cols("access_token", "refresh_token", "expires_at"). - Update(&user_model.ExternalLoginUser{AccessToken: accessToken, RefreshToken: refreshToken, ExpiresAt: expiresAt}) - require.NoError(t, err) - require.False(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}).IsActive) - } + t.Run("OAuth2LinkedIdentity", func(t *testing.T) { + const sourceName, sub = "oauth-signin-webauthn", "signin-sub" + u := newWebAuthnUser(t, "signin-webauthn") + srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) + addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) + require.NoError(t, err) + require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, &user_model.ExternalLoginUser{ + ExternalID: sub, UserID: u.ID, LoginSourceID: authSource.ID, Provider: "openidConnect", + })) + assertOAuth2Challenged(t, sourceName) + }) - t.Run("admin-disabled user is not reactivated", func(t *testing.T) { - prepareUserExternalLink(t, "an-access-token", "non-empty-refresh-token", time.Now().Add(time.Hour)) - doOIDCSignIn(t, authSource.Name) - after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) - assert.False(t, after.IsActive, "OAuth callback must not re-enable an administrator-disabled account") - }) - - t.Run("admin-disabled user without refresh token is not reactivated", func(t *testing.T) { - // GitHub / OIDC-without-offline_access sources never store a refresh token, so a - // stored access token (and no refresh token) is the normal admin-disabled state - prepareUserExternalLink(t, "an-access-token", "" /* no refresh token */, time.Now().Add(time.Hour)) - doOIDCSignIn(t, authSource.Name) - after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) - assert.False(t, after.IsActive, "OAuth callback must not re-enable an admin-disabled account on a no-refresh-token source") - }) - - t.Run("auto-sync-disabled user is reactivated", func(t *testing.T) { - // the auto-sync cron clears all three token fields when it disables a user - prepareUserExternalLink(t, "", "", time.Time{}) - doOIDCSignIn(t, authSource.Name) - after := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: u.ID}) - assert.True(t, after.IsActive, "OAuth callback must reactivate a sync-disabled account on successful login") + t.Run("PasswordReset", func(t *testing.T) { + u := newWebAuthnUser(t, "reset-webauthn") + code := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, u) + session := emptyTestSession(t) + req := NewRequestWithValues(t, "POST", "/user/recover_account", map[string]string{"code": code, "password": "new-Password!1"}) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") + session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) + }) }) } @@ -377,261 +617,3 @@ func newOIDCSource(srv *httptest.Server, withAdmin, withRestricted bool) oauth2. } return src } - -// TestOAuth2GroupClaimsAppliedOnFirstLogin verifies that group claims from OAuth2/OIDC -// are correctly applied to newly created users on the first login -func TestOAuth2GroupClaimsAppliedOnFirstLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - // Enable auto-registration to ensure first login creates user with group claims - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - // Use sub claim as username for deterministic user naming - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameUserid)() - - tt := []struct { - Name string - IsAdmin bool - IsRestricted bool - SourceName string - }{ - { - Name: "user in both admin and restricted groups", - IsAdmin: true, - IsRestricted: true, - SourceName: "test-group-claims", - }, - { - Name: "no groups", - IsAdmin: false, - IsRestricted: false, - SourceName: "test-no-groups", - }, - } - for _, tc := range tt { - t.Run(tc.Name, func(t *testing.T) { - // Set up OIDC server with group claims - srv := newFakeOIDCServer(t, FakeOIDCConfig{ - Sub: tc.SourceName, - Email: tc.SourceName + "@example.com", - Name: "Test User", - Groups: []string{"admins", "restricted-users"}, - }) - - // Ensure it's the first login so no user in database - unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) - - addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) - - doOIDCSignIn(t, tc.SourceName) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) - assert.Equal(t, tc.IsAdmin, user.IsAdmin) - assert.Equal(t, tc.IsRestricted, user.IsRestricted) - assert.Equal(t, auth_model.OAuth2, user.LoginType) - }) - } -} - -// TestOAuth2GroupClaimsManualLinking tests that group claims are applied correctly -// when a user goes through the manual linking flow (auto-registration disabled). -func TestOAuth2GroupClaimsManualLinking(t *testing.T) { - defer tests.PrepareTestEnv(t)() - // Disable auto-registration to force manual linking flow - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, false)() - defer test.MockVariableValue(&setting.Service.AllowOnlyInternalRegistration, false)() - - tt := []struct { - Name string - IsAdmin bool - IsRestricted bool - SourceName string - }{ - { - Name: "user in both admin and restricted groups", - IsAdmin: true, - IsRestricted: true, - SourceName: "test-group-claims-manual-linking", - }, - { - Name: "no groups", - IsAdmin: false, - IsRestricted: false, - SourceName: "test-no-groups-manual-linking", - }, - } - - for _, tc := range tt { - t.Run(tc.Name, func(t *testing.T) { - srv := newFakeOIDCServer(t, FakeOIDCConfig{ - Sub: tc.SourceName, - Email: tc.SourceName + "@example.com", - Name: "Manual User", - Groups: []string{"admins", "restricted-users"}, - }) - addOAuth2Source(t, tc.SourceName, newOIDCSource(srv, tc.IsAdmin, tc.IsRestricted)) - unittest.AssertNotExistsBean(t, &user_model.User{Name: tc.SourceName}) - session := emptyTestSession(t) - resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+tc.SourceName), http.StatusTemporaryRedirect) - - location := resp.Header().Get("Location") - u, err := url.Parse(location) - require.NoError(t, err) - state := u.Query().Get("state") - require.NotEmpty(t, state, "redirect to OIDC provider must include state") - - callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", tc.SourceName, url.QueryEscape(state)) - session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) - - // Submit the form to create a new account - linkAccountResp := session.MakeRequest(t, NewRequest(t, "GET", "/user/link_account"), http.StatusOK) - // Verify we're on the link account page - assert.Contains(t, linkAccountResp.Body.String(), "link_account") - - // Use NewRequestWithValues to POST form data (no CSRF needed in tests) - // Field names are lowercase in HTML forms: user_name, email, password, retype - req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ - "user_name": tc.SourceName, - "email": tc.SourceName + "@example.com", - "password": "", // AllowOnlyExternalRegistration means no password needed - "retype": "", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: tc.SourceName}) - assert.Equal(t, tc.IsAdmin, user.IsAdmin) - assert.Equal(t, tc.IsRestricted, user.IsRestricted) - assert.Equal(t, auth_model.OAuth2, user.LoginType) - }) - } -} - -// TestOAuth2AutoLinkWithTwoFactor verifies that automatic account linking completes -// after the user passes local 2FA when an OIDC identity matches an existing account. -func TestOAuth2AutoLinkWithTwoFactor(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - - const ( - sourceName = "test-oauth-auto-link-2fa" - sub = "oidc-auto-link-2fa-sub" - email = "oidc-auto-link-2fa@example.com" - userName = "oidc-auto-link-2fa" - ) - - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: email, Name: "OIDC Auto Link 2FA"}) - addOAuth2Source(t, sourceName, oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - ClientSecret: "test-client-secret", - OpenIDConnectAutoDiscoveryURL: srv.URL + "/.well-known/openid-configuration", - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) - require.NoError(t, err) - - localUser := &user_model.User{Name: userName, Email: email} - require.NoError(t, user_model.CreateUser(t.Context(), localUser, &user_model.Meta{})) - - otpKey, err := totp.Generate(totp.GenerateOpts{ - SecretSize: 40, - Issuer: "gitea-test", - AccountName: localUser.Name, - }) - require.NoError(t, err) - - tfa := &auth_model.TwoFactor{UID: localUser.ID} - require.NoError(t, tfa.SetSecret(otpKey.Secret())) - require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa)) - - unittest.AssertNotExistsBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - - session := emptyTestSession(t) - resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) - - location := resp.Header().Get("Location") - u, err := url.Parse(location) - require.NoError(t, err) - state := u.Query().Get("state") - require.NotEmpty(t, state) - - callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) - resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) - assert.Contains(t, resp.Header().Get("Location"), "/user/two_factor") - - session.MakeRequest(t, NewRequest(t, "GET", "/user/two_factor"), http.StatusOK) - - passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) - require.NoError(t, err) - - req := NewRequestWithValues(t, "POST", "/user/two_factor", map[string]string{ - "passcode": passcode, - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - externalLink := unittest.AssertExistsAndLoadBean(t, &user_model.ExternalLoginUser{ExternalID: sub, LoginSourceID: authSource.ID}, unittest.OrderBy("external_id ASC")) - assert.Equal(t, localUser.ID, externalLink.UserID) - - session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusOK) -} - -// a security key must be challenged on every path that issues a session, not just the password login -func TestWebAuthnSecondFactorRequired(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - newWebAuthnUser := func(t *testing.T, name string) *user_model.User { - u := &user_model.User{Name: name, Email: name + "@example.com"} - require.NoError(t, user_model.CreateUser(t.Context(), u, &user_model.Meta{})) - _, err := auth_model.CreateCredential(t.Context(), u.ID, "test-key", &webauthn.Credential{ID: []byte(name)}) - require.NoError(t, err) - return u - } - - assertOAuth2Challenged := func(t *testing.T, sourceName string) { - session := emptyTestSession(t) - resp := session.MakeRequest(t, NewRequest(t, "GET", "/user/oauth2/"+sourceName), http.StatusTemporaryRedirect) - u, err := url.Parse(resp.Header().Get("Location")) - require.NoError(t, err) - state := u.Query().Get("state") - require.NotEmpty(t, state) - - callbackURL := fmt.Sprintf("/user/oauth2/%s/callback?code=test-code&state=%s", sourceName, url.QueryEscape(state)) - resp = session.MakeRequest(t, NewRequest(t, "GET", callbackURL), http.StatusSeeOther) - assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") - session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) // the redirect alone does not prove no session was issued - } - - t.Run("OAuth2AutoLink", func(t *testing.T) { - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&setting.OAuth2Client.AccountLinking, setting.OAuth2AccountLinkingAuto)() - defer test.MockVariableValue(&setting.OAuth2Client.Username, setting.OAuth2UsernameEmail)() - - const sourceName, sub = "oauth-autolink-webauthn", "autolink-sub" - u := newWebAuthnUser(t, "autolink-webauthn") - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) - addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) - assertOAuth2Challenged(t, sourceName) - }) - - t.Run("OAuth2LinkedIdentity", func(t *testing.T) { - const sourceName, sub = "oauth-signin-webauthn", "signin-sub" - u := newWebAuthnUser(t, "signin-webauthn") - srv := newFakeOIDCServer(t, FakeOIDCConfig{Sub: sub, Email: u.Email, Name: u.Name}) - addOAuth2Source(t, sourceName, newOIDCSource(srv, false, false)) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), sourceName) - require.NoError(t, err) - require.NoError(t, user_model.LinkExternalToUser(t.Context(), u, &user_model.ExternalLoginUser{ - ExternalID: sub, UserID: u.ID, LoginSourceID: authSource.ID, Provider: "openidConnect", - })) - assertOAuth2Challenged(t, sourceName) - }) - - t.Run("PasswordReset", func(t *testing.T) { - u := newWebAuthnUser(t, "reset-webauthn") - code := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, u) - session := emptyTestSession(t) - req := NewRequestWithValues(t, "POST", "/user/recover_account", map[string]string{"code": code, "password": "new-Password!1"}) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Contains(t, resp.Header().Get("Location"), "/user/webauthn") - session.MakeRequest(t, NewRequest(t, "GET", "/user/settings"), http.StatusSeeOther) - }) -} diff --git a/tests/integration/change_default_branch_test.go b/tests/integration/change_default_branch_test.go index f4521ed1cbe..061a2f3e028 100644 --- a/tests/integration/change_default_branch_test.go +++ b/tests/integration/change_default_branch_test.go @@ -20,23 +20,77 @@ import ( func TestChangeDefaultBranch(t *testing.T) { defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - session := loginUser(t, owner.Name) - branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + t.Run("ChangeDefaultBranch", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - req := NewRequestWithValues(t, "POST", branchesURL, map[string]string{ - "action": "default_branch", - "branch": "DefaultBranch", + session := loginUser(t, owner.Name) + branchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + + req := NewRequestWithValues(t, "POST", branchesURL, map[string]string{ + "action": "default_branch", + "branch": "DefaultBranch", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + req = NewRequestWithValues(t, "POST", branchesURL, map[string]string{ + "action": "default_branch", + "branch": "does_not_exist", + }) + session.MakeRequest(t, req, http.StatusNotFound) }) - session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequestWithValues(t, "POST", branchesURL, map[string]string{ - "action": "default_branch", - "branch": "does_not_exist", + t.Run("Divergence", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + session := loginUser(t, owner.Name) + branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name) + settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) + + // check branch divergence before switching default branch + expectedBranchToDivergenceBefore := map[string]*git.DivergeObject{ + "not-signed": { + Ahead: 0, + Behind: 0, + }, + "good-sign-not-yet-validated": { + Ahead: 0, + Behind: 1, + }, + "good-sign": { + Ahead: 1, + Behind: 3, + }, + } + checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore) + + // switch default branch + newDefaultBranch := "good-sign-not-yet-validated" + req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{ + "action": "default_branch", + "branch": newDefaultBranch, + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // check branch divergence after switching default branch + expectedBranchToDivergenceAfter := map[string]*git.DivergeObject{ + "master": { + Ahead: 1, + Behind: 0, + }, + "not-signed": { + Ahead: 1, + Behind: 0, + }, + "good-sign": { + Ahead: 1, + Behind: 2, + }, + } + checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter) }) - session.MakeRequest(t, req, http.StatusNotFound) } func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]*git.DivergeObject) { @@ -77,55 +131,3 @@ func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDe assert.Equal(t, expectedCountBehind, countBehind) } } - -func TestChangeDefaultBranchDivergence(t *testing.T) { - defer tests.PrepareTestEnv(t)() - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - - session := loginUser(t, owner.Name) - branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name) - settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name) - - // check branch divergence before switching default branch - expectedBranchToDivergenceBefore := map[string]*git.DivergeObject{ - "not-signed": { - Ahead: 0, - Behind: 0, - }, - "good-sign-not-yet-validated": { - Ahead: 0, - Behind: 1, - }, - "good-sign": { - Ahead: 1, - Behind: 3, - }, - } - checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore) - - // switch default branch - newDefaultBranch := "good-sign-not-yet-validated" - req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{ - "action": "default_branch", - "branch": newDefaultBranch, - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - // check branch divergence after switching default branch - expectedBranchToDivergenceAfter := map[string]*git.DivergeObject{ - "master": { - Ahead: 1, - Behind: 0, - }, - "not-signed": { - Ahead: 1, - Behind: 0, - }, - "good-sign": { - Ahead: 1, - Behind: 2, - }, - } - checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter) -} diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go index 8fd28dd3235..a42a3758e70 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -26,42 +26,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestCompareTag(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") - // A dropdown for both base and head. - assert.Lenf(t, selection.Nodes, 2, "The template has changed") - - req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...HEAD") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - - req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...NotExisting").SetHeader("Accept", "text/html") - resp = session.MakeRequest(t, req, http.StatusNotFound) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - - req = NewRequest(t, "GET", "/user2/repo1/compare/invalid").SetHeader("Accept", "text/html") - resp = session.MakeRequest(t, req, http.StatusNotFound) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) -} - -// Compare with inferred default branch (master) -func TestCompareDefault(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") - assert.Lenf(t, selection.Nodes, 2, "The template has changed") -} - // Ensure the comparison matches what we expect func inspectCompare(t *testing.T, htmlDoc *HTMLDoc, diffCount int, diffChanges []string) { selection := htmlDoc.doc.Find("#diff-file-boxes").Children() @@ -74,154 +38,225 @@ func inspectCompare(t *testing.T, htmlDoc *HTMLDoc, diffCount int, diffChanges [ } } -// Git commit graph for repo20 -// * 8babce9 (origin/remove-files-b) Add a dummy file -// * b67e43a Delete test.csv and link_hi -// | * cfe3b3c (origin/remove-files-a) Delete test.csv and link_hi -// |/ -// * c8e31bc (origin/add-csv) Add test csv file -// * 808038d (HEAD -> master, origin/master, origin/HEAD) Added test links - -func TestCompareBranches(t *testing.T) { +func TestCompare(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") + t.Run("CompareTag", func(t *testing.T) { + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") + // A dropdown for both base and head. + assert.Lenf(t, selection.Nodes, 2, "The template has changed") - // Indirect compare remove-files-b (head) with add-csv (base) branch - // - // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) + req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...HEAD") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - diffCount := 3 - diffChanges := []string{"link_hi", "test.csv", "test.txt"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - // Indirect compare remove-files-b (head) with remove-files-a (base) branch - // - // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added - - req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-a...remove-files-b") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - diffCount = 3 - diffChanges = []string{"link_hi", "test.csv", "test.txt"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - // Indirect compare remove-files-a (head) with remove-files-b (base) branch - // - // 'link_hi' and 'test.csv' are deleted - - req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b...remove-files-a") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - diffCount = 2 - diffChanges = []string{"link_hi", "test.csv"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - // Direct compare remove-files-b (head) with remove-files-a (base) branch - // - // 'test.txt' is deleted - - req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b..remove-files-a") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - diffCount = 1 - diffChanges = []string{"test.txt"} - - inspectCompare(t, htmlDoc, diffCount, diffChanges) - - req = NewRequest(t, "GET", "/user2/repo20/compare/8babce967f21b9dfa6987f943b91093dac58a4f0..c8e31bc7688741a5287fcde4fbb8fc129ca07027") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - - inspectCompare(t, htmlDoc, 3, []string{"link_hi", "test.csv", "test.txt"}) - assert.NotContains(t, htmlDoc.doc.Text(), translation.NewLocale("en-US").TrString("repo.commits.nothing_to_compare")) - commitsHeader := strings.Join(strings.Fields(htmlDoc.doc.Find(".ui.top.attached.header .flex-text-block").First().Text()), " ") - assert.Equal(t, "0 "+translation.NewLocale("en-US").TrString("repo.commits.commits"), commitsHeader) -} - -func TestCompareWithRefSuffix(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - - // remove-files-b^ resolves to the tip's parent, so the test.txt added by the tip is excluded - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b^") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) - - // a suffix resolves to a commit rather than a branch, so the page offers no pull request to create - assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) - - // the same suffix on the direct ".." comparison resolves to the same commit - req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv..remove-files-b^") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) - - // a ~N suffix on the base side resolves and renders the compare page, but also - // resolves to a commit rather than a branch, so no pull request form is offered - req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv~1...remove-files-b") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - htmlDoc = NewHTMLParser(t, resp.Body) - assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) - - // the web handler folds an unsupported (^{...}) and an unresolvable (~50) suffix alike into 404 - for _, basehead := range []string{ - "add-csv...remove-files-b~50", - "add-csv...remove-files-b^{/Add}", - "add-csv^{/Add}...remove-files-b", - } { - req = NewRequest(t, "GET", "/user2/repo20/compare/"+basehead).SetHeader("Accept", "text/html") + req = NewRequest(t, "GET", "/user2/repo1/compare/v1.1...NotExisting").SetHeader("Accept", "text/html") resp = session.MakeRequest(t, req, http.StatusNotFound) assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) - } -} -func TestResolveRefWithSuffixContract(t *testing.T) { - defer tests.PrepareTestEnv(t)() + req = NewRequest(t, "GET", "/user2/repo1/compare/invalid").SetHeader("Accept", "text/html") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) + }) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31}) - gitRepo, err := git.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() + // Compare with inferred default branch (master) + t.Run("CompareDefault", func(t *testing.T) { + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") + assert.Lenf(t, selection.Nodes, 2, "The template has changed") + }) - // a nil error guarantees a usable RefName - ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, "add-csv", "^") - require.NoError(t, err) - assert.NotEmpty(t, ref) - // a ref resolved with a suffix must be a commit SHA, not a branch ref - // (branch refs would break "New Pull Request" logic) - assert.False(t, ref.IsBranch(), "ref with suffix must not resolve to a branch") + // Git commit graph for repo20 + // * 8babce9 (origin/remove-files-b) Add a dummy file + // * b67e43a Delete test.csv and link_hi + // | * cfe3b3c (origin/remove-files-a) Delete test.csv and link_hi + // |/ + // * c8e31bc (origin/add-csv) Add test csv file + // * 808038d (HEAD -> master, origin/master, origin/HEAD) Added test links + t.Run("CompareBranches", func(t *testing.T) { + session := loginUser(t, "user2") - // a missing ref and an unresolvable suffix both report not-found instead of an empty RefName - for _, tc := range []struct{ oriRef, suffix string }{ - {"does-not-exist", ""}, - {"add-csv", "~50"}, - } { - ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, tc.oriRef, tc.suffix) - assert.ErrorIs(t, err, util.ErrNotExist, "ref %q suffix %q", tc.oriRef, tc.suffix) - assert.Empty(t, ref, "ref %q suffix %q", tc.oriRef, tc.suffix) - } -} + // Indirect compare remove-files-b (head) with add-csv (base) branch + // + // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) -func TestCompareBranchesNoCommonMergeBase(t *testing.T) { - defer tests.PrepareTestEnv(t)() + diffCount := 3 + diffChanges := []string{"link_hi", "test.csv", "test.txt"} - user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"}) + inspectCompare(t, htmlDoc, diffCount, diffChanges) - _, _, runErr := gitcmd.NewCommand("fast-import").WithRepo(repo1).WithStdinBytes([]byte(strings.TrimSpace(` + // Indirect compare remove-files-b (head) with remove-files-a (base) branch + // + // 'link_hi' and 'test.csv' are deleted, 'test.txt' is added + + req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-a...remove-files-b") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + diffCount = 3 + diffChanges = []string{"link_hi", "test.csv", "test.txt"} + + inspectCompare(t, htmlDoc, diffCount, diffChanges) + + // Indirect compare remove-files-a (head) with remove-files-b (base) branch + // + // 'link_hi' and 'test.csv' are deleted + + req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b...remove-files-a") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + diffCount = 2 + diffChanges = []string{"link_hi", "test.csv"} + + inspectCompare(t, htmlDoc, diffCount, diffChanges) + + // Direct compare remove-files-b (head) with remove-files-a (base) branch + // + // 'test.txt' is deleted + + req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b..remove-files-a") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + diffCount = 1 + diffChanges = []string{"test.txt"} + + inspectCompare(t, htmlDoc, diffCount, diffChanges) + + req = NewRequest(t, "GET", "/user2/repo20/compare/8babce967f21b9dfa6987f943b91093dac58a4f0..c8e31bc7688741a5287fcde4fbb8fc129ca07027") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + + inspectCompare(t, htmlDoc, 3, []string{"link_hi", "test.csv", "test.txt"}) + assert.NotContains(t, htmlDoc.doc.Text(), translation.NewLocale("en-US").TrString("repo.commits.nothing_to_compare")) + commitsHeader := strings.Join(strings.Fields(htmlDoc.doc.Find(".ui.top.attached.header .flex-text-block").First().Text()), " ") + assert.Equal(t, "0 "+translation.NewLocale("en-US").TrString("repo.commits.commits"), commitsHeader) + }) + + t.Run("CompareWithRefSuffix", func(t *testing.T) { + session := loginUser(t, "user2") + + // remove-files-b^ resolves to the tip's parent, so the test.txt added by the tip is excluded + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b^") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) + + // a suffix resolves to a commit rather than a branch, so the page offers no pull request to create + assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) + + // the same suffix on the direct ".." comparison resolves to the same commit + req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv..remove-files-b^") + resp = session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + inspectCompare(t, htmlDoc, 2, []string{"link_hi", "test.csv"}) + + // a ~N suffix on the base side resolves and renders the compare page, but also + // resolves to a commit rather than a branch, so no pull request form is offered + req = NewRequest(t, "GET", "/user2/repo20/compare/add-csv~1...remove-files-b") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) + htmlDoc = NewHTMLParser(t, resp.Body) + assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) + + // the web handler folds an unsupported (^{...}) and an unresolvable (~50) suffix alike into 404 + for _, basehead := range []string{ + "add-csv...remove-files-b~50", + "add-csv...remove-files-b^{/Add}", + "add-csv^{/Add}...remove-files-b", + } { + req = NewRequest(t, "GET", "/user2/repo20/compare/"+basehead).SetHeader("Accept", "text/html") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.True(t, test.IsNormalPageCompleted(resp.Body.String())) + } + }) + + t.Run("ResolveRefWithSuffixContract", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31}) + gitRepo, err := git.OpenRepository(t.Context(), repo) + require.NoError(t, err) + defer gitRepo.Close() + + // a nil error guarantees a usable RefName + ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, "add-csv", "^") + require.NoError(t, err) + assert.NotEmpty(t, ref) + // a ref resolved with a suffix must be a commit SHA, not a branch ref + // (branch refs would break "New Pull Request" logic) + assert.False(t, ref.IsBranch(), "ref with suffix must not resolve to a branch") + + // a missing ref and an unresolvable suffix both report not-found instead of an empty RefName + for _, tc := range []struct{ oriRef, suffix string }{ + {"does-not-exist", ""}, + {"add-csv", "~50"}, + } { + ref, err := common.ResolveRefWithSuffix(t.Context(), gitRepo, tc.oriRef, tc.suffix) + assert.ErrorIs(t, err, util.ErrNotExist, "ref %q suffix %q", tc.oriRef, tc.suffix) + assert.Empty(t, ref, "ref %q suffix %q", tc.oriRef, tc.suffix) + } + }) + + t.Run("CompareDownloadDiffOrPatch", func(t *testing.T) { + session := loginUser(t, "user2") + + t.Run("BranchToBranchDiff", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.diff") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Contains(t, resp.Body.String(), "diff --git ") + }) + + t.Run("BranchToBranchPatch", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.patch") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) + assert.True(t, strings.HasPrefix(resp.Body.String(), "From "), "patch output should start with a format-patch header") + }) + + t.Run("SingleRefImplicitBase", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv.diff") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "diff --git ") + }) + + t.Run("InvalidBaseRef", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo20/compare/does-not-exist...remove-files-b.diff") + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("PrivateRepoAnonymous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // repo16 is private; an unauthenticated request must not leak its existence. + req := NewRequest(t, "GET", "/user2/repo16/compare/master...good-sign.diff") + MakeRequest(t, req, http.StatusNotFound) + }) + }) + + t.Run("CompareBranchesNoCommonMergeBase", func(t *testing.T) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"}) + + _, _, runErr := gitcmd.NewCommand("fast-import").WithRepo(repo1).WithStdinBytes([]byte(strings.TrimSpace(` commit refs/heads/unrelated-history committer User 1714310400 +0000 data 13 @@ -230,66 +265,20 @@ M 100644 inline file2.txt data 12 Hello from 2 `))).RunStdString(t.Context()) - require.NoError(t, runErr) + require.NoError(t, runErr) - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/repo1/compare/master...unrelated-history") - resp := session.MakeRequest(t, req, http.StatusOK) - body := resp.Body.String() - htmlDoc := NewHTMLParser(t, resp.Body) - - selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") - assert.Lenf(t, selection.Nodes, 2, "The template has changed") - assert.Contains(t, body, "These branches do not share a common merge base") - assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...unrelated-history"]`).Length()) - assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...master"]`).Length()) - assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) -} - -func TestCompareDownloadDiffOrPatch(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - - t.Run("BranchToBranchDiff", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.diff") + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/master...unrelated-history") resp := session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) + body := resp.Body.String() + htmlDoc := NewHTMLParser(t, resp.Body) - t.Run("BranchToBranchPatch", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b.patch") - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) - assert.True(t, strings.HasPrefix(resp.Body.String(), "From "), "patch output should start with a format-patch header") - }) - - t.Run("SingleRefImplicitBase", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv.diff") - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "diff --git ") - }) - - t.Run("InvalidBaseRef", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - req := NewRequest(t, "GET", "/user2/repo20/compare/does-not-exist...remove-files-b.diff") - session.MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("PrivateRepoAnonymous", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - // repo16 is private; an unauthenticated request must not leak its existence. - req := NewRequest(t, "GET", "/user2/repo16/compare/master...good-sign.diff") - MakeRequest(t, req, http.StatusNotFound) + selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") + assert.Lenf(t, selection.Nodes, 2, "The template has changed") + assert.Contains(t, body, "These branches do not share a common merge base") + assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...unrelated-history"]`).Length()) + assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...master"]`).Length()) + assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) }) } diff --git a/tests/integration/delete_user_test.go b/tests/integration/delete_user_test.go index fe84d19573d..e9733361f0e 100644 --- a/tests/integration/delete_user_test.go +++ b/tests/integration/delete_user_test.go @@ -34,24 +34,24 @@ func assertUserDeleted(t *testing.T, userID int64) { func TestUserDeleteAccount(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user8") - urlStr := "/user/settings/account/delete?password=" + userPassword - req := NewRequest(t, "POST", urlStr) - resp := session.MakeRequest(t, req, http.StatusOK) - assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect) + t.Run("StillOwnRepos", func(t *testing.T) { + session := loginUser(t, "user2") + urlStr := "/user/settings/account/delete?password=" + userPassword + req := NewRequest(t, "POST", urlStr) + resp := session.MakeRequest(t, req, http.StatusBadRequest) + assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) + // user should not have been deleted, because the user still owns repos + unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + }) - assertUserDeleted(t, 8) - unittest.CheckConsistencyFor(t, &user_model.User{}) -} - -func TestUserDeleteAccountStillOwnRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - urlStr := "/user/settings/account/delete?password=" + userPassword - req := NewRequest(t, "POST", urlStr) - resp := session.MakeRequest(t, req, http.StatusBadRequest) - assert.NotEmpty(t, test.ParseJSONError(resp.Body.Bytes()).ErrorMessage) - // user should not have been deleted, because the user still owns repos - unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + t.Run("Delete", func(t *testing.T) { + session := loginUser(t, "user8") + urlStr := "/user/settings/account/delete?password=" + userPassword + req := NewRequest(t, "POST", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + assert.NotEmpty(t, test.ParseJSONRedirect(resp.Body.Bytes()).Redirect) + + assertUserDeleted(t, 8) + unittest.CheckConsistencyFor(t, &user_model.User{}) + }) } diff --git a/tests/integration/download_test.go b/tests/integration/download_test.go index ce6da3a4e44..ea4bf2c0a84 100644 --- a/tests/integration/download_test.go +++ b/tests/integration/download_test.go @@ -5,6 +5,7 @@ package integration import ( "net/http" + "strings" "testing" auth_model "gitea.dev/models/auth" @@ -19,7 +20,6 @@ type downloadScopeCase struct { name string method string url string - withScope int publicOnlyOK bool } @@ -28,33 +28,24 @@ func TestDownloadRepoContent(t *testing.T) { session := loginUser(t, "user2") - t.Run("RawBlob", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f") - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) - }) + for _, route := range []struct{ path, blobName, svgName string }{ + {"raw", "RawBlob", "SVGUsesSecureHeaders"}, + {"media", "MediaBlob", "MediaSVGUsesSecureHeaders"}, + } { + t.Run(route.blobName, func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/"+route.path+"/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) + }) - t.Run("SVGUsesSecureHeaders", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b") - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "sandbox") - assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type")) - assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options")) - }) - - t.Run("MediaBlob", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f") - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) - }) - - t.Run("MediaSVGUsesSecureHeaders", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo2/media/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b") - resp := session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "sandbox") - assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type")) - assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options")) - }) + t.Run(route.svgName, func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo2/"+route.path+"/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Header().Get("Content-Security-Policy"), "sandbox") + assert.Equal(t, "image/svg+xml", resp.Header().Get("Content-Type")) + assert.Equal(t, "nosniff", resp.Header().Get("X-Content-Type-Options")) + }) + } t.Run("MimeTypeMap", func(t *testing.T) { req := NewRequest(t, "GET", "/user2/repo2/raw/branch/master/test.xml") @@ -62,7 +53,6 @@ func TestDownloadRepoContent(t *testing.T) { // although the file is a valid XML file, it is served as "text/plain" to avoid site content spamming (the same to "text/html" files) assert.Equal(t, "text/plain; charset=utf-8", resp.Header().Get("Content-Type")) - defer tests.PrepareTestEnv(t)() defer test.MockVariableValue(&setting.MimeTypeMap)() setting.MimeTypeMap.Enabled = true @@ -79,133 +69,85 @@ func TestDownloadRepoContent(t *testing.T) { // non-text file don't have "charset" assert.Equal(t, "application/xml", resp.Header().Get("Content-Type")) }) -} -func TestDownloadRepoContentTokenScopes(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("TokenScopes", func(t *testing.T) { + ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) + miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) + publicOnlyToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - ownerReadToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) - miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) - publicOnlyToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly) - - cases := []downloadScopeCase{ - { - name: "PublicArchiveDownload", - method: http.MethodGet, - url: "/user2/repo1/archive/master.tar.gz", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PrivateArchiveDownload", - method: http.MethodGet, - url: "/user2/repo2/archive/master.tar.gz", - withScope: http.StatusOK, - publicOnlyOK: false, - }, - { - name: "PublicArchiveInitiate", - method: http.MethodPost, - url: "/user2/repo1/archive/master.tar.gz", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PrivateArchiveInitiate", - method: http.MethodPost, - url: "/user2/repo2/archive/master.tar.gz", - withScope: http.StatusOK, - publicOnlyOK: false, - }, - { - name: "PublicRawBlob", - method: http.MethodGet, - url: "/user2/repo1/raw/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicRawBranch", - method: http.MethodGet, - url: "/user2/repo1/raw/branch/master/README.md", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicRawTag", - method: http.MethodGet, - url: "/user2/repo1/raw/tag/v1.1/README.md", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicRawCommit", - method: http.MethodGet, - url: "/user2/repo1/raw/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicMediaBlob", - method: http.MethodGet, - url: "/user2/repo1/media/blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicMediaBranch", - method: http.MethodGet, - url: "/user2/repo1/media/branch/master/README.md", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicMediaTag", - method: http.MethodGet, - url: "/user2/repo1/media/tag/v1.1/README.md", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PublicMediaCommit", - method: http.MethodGet, - url: "/user2/repo1/media/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md", - withScope: http.StatusOK, - publicOnlyOK: true, - }, - { - name: "PrivateRawBranch", - method: http.MethodGet, - url: "/user2/repo2/raw/branch/master/test.xml", - withScope: http.StatusOK, - publicOnlyOK: false, - }, - { - name: "PrivateRawBlob", - method: http.MethodGet, - url: "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b", - withScope: http.StatusOK, - publicOnlyOK: false, - }, - { - name: "PrivateMediaBranch", - method: http.MethodGet, - url: "/user2/repo2/media/branch/master/test.xml", - withScope: http.StatusOK, - publicOnlyOK: false, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(miscToken), http.StatusForbidden) - MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(ownerReadToken), tc.withScope) - - publicOnlyStatus := http.StatusNotFound - if tc.publicOnlyOK { - publicOnlyStatus = tc.withScope + cases := []downloadScopeCase{ + { + name: "PublicArchiveDownload", + method: http.MethodGet, + url: "/user2/repo1/archive/master.tar.gz", + publicOnlyOK: true, + }, + { + name: "PrivateArchiveDownload", + method: http.MethodGet, + url: "/user2/repo2/archive/master.tar.gz", + publicOnlyOK: false, + }, + { + name: "PublicArchiveInitiate", + method: http.MethodPost, + url: "/user2/repo1/archive/master.tar.gz", + publicOnlyOK: true, + }, + { + name: "PrivateArchiveInitiate", + method: http.MethodPost, + url: "/user2/repo2/archive/master.tar.gz", + publicOnlyOK: false, + }, + } + for _, route := range []string{"Raw", "Media"} { + for _, ref := range []struct{ name, path string }{ + {"Blob", "blob/4b4851ad51df6a7d9f25c979345979eaeb5b349f"}, + {"Branch", "branch/master/README.md"}, + {"Tag", "tag/v1.1/README.md"}, + {"Commit", "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md"}, + } { + cases = append(cases, downloadScopeCase{ + name: "Public" + route + ref.name, + method: http.MethodGet, + url: "/user2/repo1/" + strings.ToLower(route) + "/" + ref.path, + publicOnlyOK: true, + }) } - MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(publicOnlyToken), publicOnlyStatus) - }) - } + } + cases = append(cases, + downloadScopeCase{ + name: "PrivateRawBranch", + method: http.MethodGet, + url: "/user2/repo2/raw/branch/master/test.xml", + publicOnlyOK: false, + }, + downloadScopeCase{ + name: "PrivateRawBlob", + method: http.MethodGet, + url: "/user2/repo2/raw/blob/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b", + publicOnlyOK: false, + }, + downloadScopeCase{ + name: "PrivateMediaBranch", + method: http.MethodGet, + url: "/user2/repo2/media/branch/master/test.xml", + publicOnlyOK: false, + }, + ) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(miscToken), http.StatusForbidden) + MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(ownerReadToken), http.StatusOK) + + publicOnlyStatus := http.StatusNotFound + if tc.publicOnlyOK { + publicOnlyStatus = http.StatusOK + } + MakeRequest(t, NewRequest(t, tc.method, tc.url).AddTokenAuth(publicOnlyToken), publicOnlyStatus) + }) + } + }) } diff --git a/tests/integration/explore_repos_test.go b/tests/integration/explore_repos_test.go deleted file mode 100644 index c8348d9c395..00000000000 --- a/tests/integration/explore_repos_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestExploreRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/explore/repos?q=TheKeyword&topic=1&language=TheLang") - resp := MakeRequest(t, req, http.StatusOK) - respStr := resp.Body.String() - - assert.Contains(t, respStr, ``) - assert.Contains(t, respStr, ``) - assert.Contains(t, respStr, ``) + assert.Contains(t, respStr, ``) + assert.Contains(t, respStr, ` refs/for/master/test-agit-push (request `force-push` push option)") + // test force push without confirm + var stderr string + _, stderr, err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push"). + WithDir(dstPath). + RunStdString(t.Context()) + assert.Error(t, err) + assert.Contains(t, stderr, "[remote rejected] HEAD -> refs/for/master/test-agit-push (request `force-push` push option)") - // test force push with confirm - err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push"). - WithDir(dstPath). - Run(t.Context()) - assert.NoError(t, err) - }) -} - -func TestAgitReviewStaleness(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - u.Path = baseAPITestContext.GitPath() - u.User = url.UserPassword("user2", userPassword) - - dstPath := t.TempDir() - doGitClone(dstPath, u)(t) - - gitRepo, err := git.OpenRepositoryLocal(t.Context(), dstPath) - assert.NoError(t, err) - defer gitRepo.Close() - - doGitCreateBranch(dstPath, "test-agit-review") - - // Create initial commit - _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "initial-") - assert.NoError(t, err) - - // create PR via agit - err = gitcmd.NewCommand("push", "origin", - "-o", "title=Test agit Review Staleness", "-o", "description=Testing review staleness", - "HEAD:refs/for/master/test-agit-review", - ).WithDir(dstPath).Run(t.Context()) - assert.NoError(t, err) - - pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ - BaseRepoID: 1, - Flow: issues_model.PullRequestFlowAGit, - HeadBranch: "user2/test-agit-review", - }) - assert.NoError(t, pr.LoadIssue(t.Context())) - - // Get initial commit ID for the review - initialCommitID := pr.HeadCommitID - t.Logf("Initial commit ID: %s", initialCommitID) - - // Create a review on the PR (as user1 reviewing user2's PR) - reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - review, err := issues_model.CreateReview(t.Context(), issues_model.CreateReviewOptions{ - Type: issues_model.ReviewTypeApprove, - Reviewer: reviewer, - Issue: pr.Issue, - CommitID: initialCommitID, - Content: "LGTM! Looks good to merge.", - Official: false, - }) - assert.NoError(t, err) - assert.False(t, review.Stale, "New review should not be stale") - - // Verify review exists and is not stale - reviews, err := issues_model.FindReviews(t.Context(), issues_model.FindReviewOptions{ - IssueID: pr.IssueID, - }) - assert.NoError(t, err) - assert.Len(t, reviews, 1) - assert.Equal(t, initialCommitID, reviews[0].CommitID) - assert.False(t, reviews[0].Stale, "Review should not be stale initially") - - // Create a new commit and update the agit PR - _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "updated-") - assert.NoError(t, err) - - err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review"). - WithDir(dstPath). - Run(t.Context()) - assert.NoError(t, err) - - // Reload PR to get updated commit ID - pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ - BaseRepoID: 1, - Flow: issues_model.PullRequestFlowAGit, - HeadBranch: "user2/test-agit-review", - }) - assert.NoError(t, pr.LoadIssue(t.Context())) - - // For AGit PRs, HeadCommitID must be loaded from git references - baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - baseGitRepo, err := git.OpenRepository(t.Context(), baseRepo) - assert.NoError(t, err) - defer baseGitRepo.Close() - - updatedCommitID, err := baseGitRepo.GetRefCommitID(t.Context(), pr.GetGitHeadRefName()) - assert.NoError(t, err) - t.Logf("Updated commit ID: %s", updatedCommitID) - - // Verify the PR was updated with new commit - assert.NotEqual(t, initialCommitID, updatedCommitID, "PR should have new commit ID after update") - - // Check that the review is now marked as stale - reviews, err = issues_model.FindReviews(t.Context(), issues_model.FindReviewOptions{ - IssueID: pr.IssueID, - }) - assert.NoError(t, err) - assert.Len(t, reviews, 1) - - assert.True(t, reviews[0].Stale, "Review should be marked as stale after AGit PR update") - - // The review commit ID should remain the same (pointing to the original commit) - assert.Equal(t, initialCommitID, reviews[0].CommitID, "Review commit ID should remain unchanged and point to original commit") + // test force push with confirm + err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-push", "-o", "force-push"). + WithDir(dstPath). + Run(t.Context()) + assert.NoError(t, err) + } else { + // Get initial commit ID for the review + initialCommitID := pr.HeadCommitID + t.Logf("Initial commit ID: %s", initialCommitID) + + // Create a review on the PR (as user1 reviewing user2's PR) + reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + review, err := issues_model.CreateReview(t.Context(), issues_model.CreateReviewOptions{ + Type: issues_model.ReviewTypeApprove, + Reviewer: reviewer, + Issue: pr.Issue, + CommitID: initialCommitID, + Content: "LGTM! Looks good to merge.", + Official: false, + }) + assert.NoError(t, err) + assert.False(t, review.Stale, "New review should not be stale") + + // Verify review exists and is not stale + reviews, err := issues_model.FindReviews(t.Context(), issues_model.FindReviewOptions{ + IssueID: pr.IssueID, + }) + assert.NoError(t, err) + assert.Len(t, reviews, 1) + assert.Equal(t, initialCommitID, reviews[0].CommitID) + assert.False(t, reviews[0].Stale, "Review should not be stale initially") + + // Create a new commit and update the agit PR + _, err = generateCommitWithNewData(t.Context(), testFileSizeSmall, dstPath, "user2@example.com", "User Two", "updated-") + assert.NoError(t, err) + + err = gitcmd.NewCommand("push", "origin", "HEAD:refs/for/master/test-agit-review"). + WithDir(dstPath). + Run(t.Context()) + assert.NoError(t, err) + + // Reload PR to get updated commit ID + pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ + BaseRepoID: 1, + Flow: issues_model.PullRequestFlowAGit, + HeadBranch: "user2/test-agit-review", + }) + assert.NoError(t, pr.LoadIssue(t.Context())) + + // For AGit PRs, HeadCommitID must be loaded from git references + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + baseGitRepo, err := git.OpenRepository(t.Context(), baseRepo) + assert.NoError(t, err) + defer baseGitRepo.Close() + + updatedCommitID, err := baseGitRepo.GetRefCommitID(t.Context(), pr.GetGitHeadRefName()) + assert.NoError(t, err) + t.Logf("Updated commit ID: %s", updatedCommitID) + + // Verify the PR was updated with new commit + assert.NotEqual(t, initialCommitID, updatedCommitID, "PR should have new commit ID after update") + + // Check that the review is now marked as stale + reviews, err = issues_model.FindReviews(t.Context(), issues_model.FindReviewOptions{ + IssueID: pr.IssueID, + }) + assert.NoError(t, err) + assert.Len(t, reviews, 1) + + assert.True(t, reviews[0].Stale, "Review should be marked as stale after AGit PR update") + + // The review commit ID should remain the same (pointing to the original commit) + assert.Equal(t, initialCommitID, reviews[0].CommitID, "Review commit ID should remain unchanged and point to original commit") + } + }) + } }) } diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index bfd4f7e01c0..d38d59b5c40 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -19,13 +19,79 @@ import ( "gitea.dev/modules/setting" "gitea.dev/modules/test" repo_service "gitea.dev/services/repository" + "gitea.dev/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGitPush(t *testing.T) { - onGiteaRun(t, testGitPush) + onGiteaRun(t, func(t *testing.T, u *url.URL) { + t.Run("Branches", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testGitPush(t, u) + }) + + t.Run("VisibilityOption", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{ + Name: "repo-visibility-option", + AutoInit: false, + DefaultBranch: "master", + IsPrivate: false, + }) + require.NoError(t, err) + require.NotEmpty(t, repo) + + gitPath := t.TempDir() + doGitInitTestRepository(gitPath)(t) + + oldPath, oldUser := u.Path, u.User + defer func() { u.Path, u.User = oldPath, oldUser }() + u.Path = repo.FullName() + ".git" + u.User = url.UserPassword(user.LowerName, userPassword) + doGitAddRemote(gitPath, "origin", u)(t) + + // The first push into an empty repository is a "push-to-create", so the + // repo.private push option is honored to set the initial visibility. + doGitPushTestRepository(gitPath, "origin", "master", "-o", "repo.private=true")(t) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.True(t, repo.IsPrivate, "repo.private option should apply on push-to-create") + + // The repository is now populated; a later push must NOT silently flip + // visibility, otherwise a repo admin could change it bypassing the audit + // trail, webhooks, and notifications a proper settings change would fire. + doGitCreateBranch(gitPath, "branch2")(t) + doGitPushTestRepository(gitPath, "origin", "branch2", "-o", "repo.private=false")(t) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) + assert.True(t, repo.IsPrivate, "repo.private option must be ignored on an existing repository") + + defer test.MockVariableValue(&setting.Repository.ForcePrivate, true)() + forcedRepo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{Name: "repo-visibility-forced", DefaultBranch: "master", IsPrivate: true}) + require.NoError(t, err) + u.Path = forcedRepo.FullName() + ".git" + doGitAddRemote(gitPath, "forced", u)(t) + doGitPushTestRepository(gitPath, "forced", "master", "-o", "repo.private=false")(t) + assert.True(t, unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: forcedRepo.ID}).IsPrivate) + }) + + t.Run("PullRefs", func(t *testing.T) { + baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + u.Path = baseAPITestContext.GitPath() + u.User = url.UserPassword("user2", userPassword) + + dstPath := t.TempDir() + doGitClone(dstPath, u)(t) + + cmd := gitcmd.NewCommand("push", "--delete", "origin", "refs/pull/2/head") + stdout, stderr, err := cmd.WithDir(dstPath).RunStdString(t.Context()) + assert.Error(t, err) + assert.Empty(t, stdout) + assert.NotContains(t, stderr, "[deleted]", "stderr: %s", stderr) + }) + }) } func testGitPush(t *testing.T, u *url.URL) { @@ -141,51 +207,6 @@ func testGitPush(t *testing.T, u *url.URL) { }) } -func TestGitPushVisibilityOption(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - repo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{ - Name: "repo-visibility-option", - AutoInit: false, - DefaultBranch: "master", - IsPrivate: false, - }) - require.NoError(t, err) - require.NotEmpty(t, repo) - - gitPath := t.TempDir() - doGitInitTestRepository(gitPath)(t) - - oldPath, oldUser := u.Path, u.User - defer func() { u.Path, u.User = oldPath, oldUser }() - u.Path = repo.FullName() + ".git" - u.User = url.UserPassword(user.LowerName, userPassword) - doGitAddRemote(gitPath, "origin", u)(t) - - // The first push into an empty repository is a "push-to-create", so the - // repo.private push option is honored to set the initial visibility. - doGitPushTestRepository(gitPath, "origin", "master", "-o", "repo.private=true")(t) - repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) - assert.True(t, repo.IsPrivate, "repo.private option should apply on push-to-create") - - // The repository is now populated; a later push must NOT silently flip - // visibility, otherwise a repo admin could change it bypassing the audit - // trail, webhooks, and notifications a proper settings change would fire. - doGitCreateBranch(gitPath, "branch2")(t) - doGitPushTestRepository(gitPath, "origin", "branch2", "-o", "repo.private=false")(t) - repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) - assert.True(t, repo.IsPrivate, "repo.private option must be ignored on an existing repository") - - defer test.MockVariableValue(&setting.Repository.ForcePrivate, true)() - forcedRepo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{Name: "repo-visibility-forced", DefaultBranch: "master", IsPrivate: true}) - require.NoError(t, err) - u.Path = forcedRepo.FullName() + ".git" - doGitAddRemote(gitPath, "forced", u)(t) - doGitPushTestRepository(gitPath, "forced", "master", "-o", "repo.private=false")(t) - assert.True(t, unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: forcedRepo.ID}).IsPrivate) - }) -} - func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gitPath string) (pushed, deleted []string)) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) repo, err := repo_service.CreateRepository(t.Context(), user, user, repo_service.CreateRepoOptions{ @@ -244,21 +265,3 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi require.NoError(t, repo_service.DeleteRepositoryDirectly(t.Context(), repo.ID)) } - -func TestPushPullRefs(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - u.Path = baseAPITestContext.GitPath() - u.User = url.UserPassword("user2", userPassword) - - dstPath := t.TempDir() - doGitClone(dstPath, u)(t) - - cmd := gitcmd.NewCommand("push", "--delete", "origin", "refs/pull/2/head") - stdout, stderr, err := cmd.WithDir(dstPath).RunStdString(t.Context()) - assert.Error(t, err) - assert.Empty(t, stdout) - assert.NotContains(t, stderr, "[deleted]", "stderr: %s", stderr) - }) -} diff --git a/tests/integration/goget_test.go b/tests/integration/goget_test.go index de78e40e13c..2a20eb1eea1 100644 --- a/tests/integration/goget_test.go +++ b/tests/integration/goget_test.go @@ -22,125 +22,110 @@ import ( func TestGoGet(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1") - resp := MakeRequest(t, req, http.StatusOK) + for _, protocol := range []string{"http", "ssh"} { + t.Run(protocol, func(t *testing.T) { + cloneURL := setting.AppURL + "blah/glah.git" + if protocol == "ssh" { + defer test.MockVariableValue(&setting.Repository.GoGetCloneURLProtocol, "ssh")() + cloneURL = fmt.Sprintf("ssh://git@%s:%d/blah/glah.git", setting.SSH.Domain, setting.SSH.Port) + } - expected := fmt.Sprintf(` + req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1") + resp := MakeRequest(t, req, http.StatusOK) + + expected := fmt.Sprintf(` - + go get --insecure %[1]s:%[2]s/blah/glah -`, setting.AppDomain, setting.HTTPPort, setting.AppURL) +`, setting.AppDomain, setting.HTTPPort, setting.AppURL, cloneURL) - assert.Equal(t, expected, resp.Body.String()) -} - -func TestGoGetForSSH(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.Repository.GoGetCloneURLProtocol, "ssh")() - - req := NewRequest(t, "GET", "/blah/glah/plah?go-get=1") - resp := MakeRequest(t, req, http.StatusOK) - - expected := fmt.Sprintf(` - - - - - - - go get --insecure %[1]s:%[2]s/blah/glah - -`, setting.AppDomain, setting.HTTPPort, setting.AppURL, setting.SSH.Domain, setting.SSH.Port) - - assert.Equal(t, expected, resp.Body.String()) - - // go rejects scp-style addresses, so the standard port must still produce an ssh:// URL - defer test.MockVariableValue(&setting.SSH.Port, 22)() - resp = MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), fmt.Sprintf(`git ssh://git@%s/blah/glah.git">`, setting.SSH.Domain)) -} - -// TestGoGetPrivateRepoBranchNotLeaked ensures the go-get meta endpoint does not disclose a -// private repository's default branch name to unauthorized callers. -func TestGoGetPrivateRepoBranchNotLeaked(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user2/repo2 is private; give it a non-default branch name - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - require.True(t, repo.IsPrivate) - repo.DefaultBranch = "secretbranch" - require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), repo, "default_branch")) - - // an unauthenticated caller must see the neutral instance default, not the real branch - req := NewRequest(t, "GET", "/user2/repo2?go-get=1") - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "/src/branch/master{/dir}") - assert.NotContains(t, resp.Body.String(), "secretbranch") - - // the owner may still see the real default branch - session := loginUser(t, "user2") - req = NewRequest(t, "GET", "/user2/repo2?go-get=1") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "secretbranch") -} - -// TestGoGetPublicRepoUnderLimitedOwnerBranchNotLeaked ensures the go-get meta endpoint does not disclose -// the default branch of a public repo whose owner is not visible to the caller (here a limited org, which -// is hidden from anonymous callers but visible to authenticated ones). -func TestGoGetPublicRepoUnderLimitedOwnerBranchNotLeaked(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // repo id 38 is a public repo owned by a limited org; give it a non-default branch name - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 38}) - require.False(t, repo.IsPrivate) - require.NoError(t, repo.LoadOwner(t.Context())) - require.False(t, repo.Owner.Visibility.IsPublic()) - repo.DefaultBranch = "secretbranch" - require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), repo, "default_branch")) - - url := fmt.Sprintf("/%s/%s?go-get=1", repo.OwnerName, repo.Name) - - // an anonymous caller cannot see the limited owner, so the neutral instance default is returned - resp := MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK) - assert.Contains(t, resp.Body.String(), "/src/branch/master{/dir}") - assert.NotContains(t, resp.Body.String(), "secretbranch") - - // an authenticated caller can see a limited org's public repo, so the real branch is shown - session := loginUser(t, "user2") - resp = session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK) - assert.Contains(t, resp.Body.String(), "secretbranch") -} - -// TestGoGetPrivateRepoBranchNotLeakedToTokenWithoutRepoScope ensures a PAT that was not granted repository -// read scope cannot learn a private repo's default branch through go-get, even when the account behind the -// token could read the repository. -func TestGoGetPrivateRepoBranchNotLeakedToTokenWithoutRepoScope(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user2/repo2 is private; give it a non-default branch name - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - require.True(t, repo.IsPrivate) - repo.DefaultBranch = "secretbranch" - require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), repo, "default_branch")) - - // a token scoped only to read:misc does not grant repository read: the branch must stay hidden. - // Web routes authenticate a token via basic auth (username + token), which also records the scope. - miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) - req := NewRequest(t, "GET", "/user2/repo2?go-get=1") - req.Request.SetBasicAuth("user2", miscToken) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "/src/branch/master{/dir}") - assert.NotContains(t, resp.Body.String(), "secretbranch") - - // a token that includes repository read scope may see the real branch - repoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) - req = NewRequest(t, "GET", "/user2/repo2?go-get=1") - req.Request.SetBasicAuth("user2", repoToken) - resp = MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "secretbranch") + assert.Equal(t, expected, resp.Body.String()) + + if protocol == "ssh" { + // go rejects scp-style addresses, so the standard port must still produce an ssh:// URL + defer test.MockVariableValue(&setting.SSH.Port, 22)() + resp = MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), fmt.Sprintf(`git ssh://git@%s/blah/glah.git">`, setting.SSH.Domain)) + } + }) + } + + // TestGoGetPrivateRepoBranchNotLeaked ensures the go-get meta endpoint does not disclose a + // private repository's default branch name to unauthorized callers. + t.Run("PrivateRepoBranchNotLeaked", func(t *testing.T) { + // user2/repo2 is private; give it a non-default branch name + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + require.True(t, repo.IsPrivate) + repo.DefaultBranch = "secretbranch" + require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), repo, "default_branch")) + + // an unauthenticated caller must see the neutral instance default, not the real branch + req := NewRequest(t, "GET", "/user2/repo2?go-get=1") + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "/src/branch/master{/dir}") + assert.NotContains(t, resp.Body.String(), "secretbranch") + + // the owner may still see the real default branch + session := loginUser(t, "user2") + req = NewRequest(t, "GET", "/user2/repo2?go-get=1") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "secretbranch") + }) + + // TestGoGetPublicRepoUnderLimitedOwnerBranchNotLeaked ensures the go-get meta endpoint does not disclose + // the default branch of a public repo whose owner is not visible to the caller (here a limited org, which + // is hidden from anonymous callers but visible to authenticated ones). + t.Run("PublicRepoUnderLimitedOwnerBranchNotLeaked", func(t *testing.T) { + // repo id 38 is a public repo owned by a limited org; give it a non-default branch name + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 38}) + require.False(t, repo.IsPrivate) + require.NoError(t, repo.LoadOwner(t.Context())) + require.False(t, repo.Owner.Visibility.IsPublic()) + repo.DefaultBranch = "secretbranch" + require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), repo, "default_branch")) + + url := fmt.Sprintf("/%s/%s?go-get=1", repo.OwnerName, repo.Name) + + // an anonymous caller cannot see the limited owner, so the neutral instance default is returned + resp := MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK) + assert.Contains(t, resp.Body.String(), "/src/branch/master{/dir}") + assert.NotContains(t, resp.Body.String(), "secretbranch") + + // an authenticated caller can see a limited org's public repo, so the real branch is shown + session := loginUser(t, "user2") + resp = session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK) + assert.Contains(t, resp.Body.String(), "secretbranch") + }) + + // TestGoGetPrivateRepoBranchNotLeakedToTokenWithoutRepoScope ensures a PAT that was not granted repository + // read scope cannot learn a private repo's default branch through go-get, even when the account behind the + // token could read the repository. + t.Run("PrivateRepoBranchNotLeakedToTokenWithoutRepoScope", func(t *testing.T) { + // user2/repo2 is private; give it a non-default branch name + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + require.True(t, repo.IsPrivate) + repo.DefaultBranch = "secretbranch" + require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), repo, "default_branch")) + + // a token scoped only to read:misc does not grant repository read: the branch must stay hidden. + // Web routes authenticate a token via basic auth (username + token), which also records the scope. + miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) + req := NewRequest(t, "GET", "/user2/repo2?go-get=1") + req.Request.SetBasicAuth("user2", miscToken) + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "/src/branch/master{/dir}") + assert.NotContains(t, resp.Body.String(), "secretbranch") + + // a token that includes repository read scope may see the real branch + repoToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) + req = NewRequest(t, "GET", "/user2/repo2?go-get=1") + req.Request.SetBasicAuth("user2", repoToken) + resp = MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "secretbranch") + }) } diff --git a/tests/integration/heatmap_test.go b/tests/integration/heatmap_test.go index da278419c80..3002ffe67b9 100644 --- a/tests/integration/heatmap_test.go +++ b/tests/integration/heatmap_test.go @@ -40,23 +40,18 @@ func TestHeatmapEndpoints(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) }) - t.Run("OrgDashboard", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", "/org/org3/dashboard/-/heatmap") - resp := session.MakeRequest(t, req, http.StatusOK) + for _, testCase := range []struct{ name, url string }{ + {"OrgDashboard", "/org/org3/dashboard/-/heatmap"}, + {"OrgTeamDashboard", "/org/org3/dashboard/-/heatmap/team1"}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req := NewRequest(t, "GET", testCase.url) + resp := session.MakeRequest(t, req, http.StatusOK) - result := DecodeJSON(t, resp, map[string]any{}) - assert.Contains(t, result, "heatmapData") - assert.Contains(t, result, "totalContributions") - }) - - t.Run("OrgTeamDashboard", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", "/org/org3/dashboard/-/heatmap/team1") - resp := session.MakeRequest(t, req, http.StatusOK) - - result := DecodeJSON(t, resp, map[string]any{}) - assert.Contains(t, result, "heatmapData") - assert.Contains(t, result, "totalContributions") - }) + result := DecodeJSON(t, resp, map[string]any{}) + assert.Contains(t, result, "heatmapData") + assert.Contains(t, result, "totalContributions") + }) + } } diff --git a/tests/integration/incoming_email_test.go b/tests/integration/incoming_email_test.go index 2c57e8a8d0c..7a533e889ef 100644 --- a/tests/integration/incoming_email_test.go +++ b/tests/integration/incoming_email_test.go @@ -69,22 +69,35 @@ func TestIncomingEmail(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, token) - ht, u, p, err := token_service.DecodeToken(t.Context(), token) - assert.NoError(t, err) - assert.Equal(t, token_service.ReplyHandlerType, ht) - assert.Equal(t, user.ID, u.ID) - assert.Equal(t, payload, p) - // MTAs may lowercase the local-part of the reply-to address (RFC 5321 §2.4). - ht, u, p, err = token_service.DecodeToken(t.Context(), strings.ToLower(token)) - assert.NoError(t, err) - assert.Equal(t, token_service.ReplyHandlerType, ht) - assert.Equal(t, user.ID, u.ID) - assert.Equal(t, payload, p) + for _, token := range []string{token, strings.ToLower(token)} { + ht, u, p, err := token_service.DecodeToken(t.Context(), token) + assert.NoError(t, err) + assert.Equal(t, token_service.ReplyHandlerType, ht) + assert.Equal(t, user.ID, u.ID) + assert.Equal(t, payload, p) + } }) t.Run("Handler", func(t *testing.T) { t.Run("Reply", func(t *testing.T) { + assertReplyComment := func(t *testing.T, issueID int64, commentType issues_model.CommentType, content *incoming.MailContent) { + comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{ + IssueID: issueID, + Type: commentType, + }) + assert.NoError(t, err) + assert.NotEmpty(t, comments) + comment := comments[len(comments)-1] + assert.Equal(t, user.ID, comment.PosterID) + assert.Equal(t, content.Content, comment.Content) + assert.NoError(t, comment.LoadAttachments(t.Context())) + assert.Len(t, comment.Attachments, 1) + attachment := comment.Attachments[0] + assert.Equal(t, content.Attachments[0].Name, attachment.Name) + assert.EqualValues(t, 4, attachment.Size) + } + t.Run("Comment", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -108,20 +121,7 @@ func TestIncomingEmail(t *testing.T) { assert.NoError(t, handler.Handle(t.Context(), content, user, payload)) - comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{ - IssueID: issue.ID, - Type: issues_model.CommentTypeComment, - }) - assert.NoError(t, err) - assert.NotEmpty(t, comments) - comment := comments[len(comments)-1] - assert.Equal(t, user.ID, comment.PosterID) - assert.Equal(t, content.Content, comment.Content) - assert.NoError(t, comment.LoadAttachments(t.Context())) - assert.Len(t, comment.Attachments, 1) - attachment := comment.Attachments[0] - assert.Equal(t, content.Attachments[0].Name, attachment.Name) - assert.EqualValues(t, 4, attachment.Size) + assertReplyComment(t, issue.ID, issues_model.CommentTypeComment, content) }) t.Run("CodeComment", func(t *testing.T) { @@ -146,20 +146,7 @@ func TestIncomingEmail(t *testing.T) { assert.NoError(t, handler.Handle(t.Context(), content, user, payload)) - comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{ - IssueID: issue.ID, - Type: issues_model.CommentTypeCode, - }) - assert.NoError(t, err) - assert.NotEmpty(t, comments) - comment = comments[len(comments)-1] - assert.Equal(t, user.ID, comment.PosterID) - assert.Equal(t, content.Content, comment.Content) - assert.NoError(t, comment.LoadAttachments(t.Context())) - assert.Len(t, comment.Attachments, 1) - attachment := comment.Attachments[0] - assert.Equal(t, content.Attachments[0].Name, attachment.Name) - assert.EqualValues(t, 4, attachment.Size) + assertReplyComment(t, issue.ID, issues_model.CommentTypeCode, content) }) }) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 0117a032b54..6cb28110969 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -60,67 +60,311 @@ func assertMatch(t testing.TB, issue *issues_model.Issue, keyword string) { assert.True(t, matches) } -func TestNoLoginViewIssues(t *testing.T) { +func TestIssueViews(t *testing.T) { defer tests.PrepareTestEnv(t)() - req := NewRequest(t, "GET", "/user2/repo1/issues") - resp := MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - issue := htmlDoc.doc.Find("#issue-list .list-item-large-title[href='/user2/repo1/issues/1']").Closest(".item") - assert.Equal(t, "label1", strings.TrimSpace(issue.Find(".labels-list").Text())) - assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - assert.Equal(t, assignee.HomeLink(), issue.Find(".assignee").AttrOr("href", "")) - assert.Equal(t, assignee.GetDisplayName(), issue.Find(".assignee").AttrOr("data-tooltip-content", "")) -} - -func TestViewIssueListContexts(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - t.Run("dashboard", func(t *testing.T) { - req := NewRequest(t, "GET", "/issues") - resp := session.MakeRequest(t, req, http.StatusOK) + t.Run("NoLoginViewIssues", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/issues") + resp := MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.Equal(t, "user2/repo1#1", strings.TrimSpace(htmlDoc.doc.Find("#issue-list .index[href='/user2/repo1/issues/1']").Text())) + issue := htmlDoc.doc.Find("#issue-list .list-item-large-title[href='/user2/repo1/issues/1']").Closest(".item") + assert.Equal(t, "label1", strings.TrimSpace(issue.Find(".labels-list").Text())) + assignee := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.Equal(t, assignee.HomeLink(), issue.Find(".assignee").AttrOr("href", "")) + assert.Equal(t, assignee.GetDisplayName(), issue.Find(".assignee").AttrOr("data-tooltip-content", "")) }) - t.Run("milestone", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/milestone/1") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - assert.Equal(t, 1, getIssuesSelection(t, htmlDoc).Length()) - assert.Empty(t, htmlDoc.doc.Find("#issue-list .milestone").Nodes) - labels := htmlDoc.doc.Find("#issue-list .labels-list a") - assert.Equal(t, 2, labels.Length()) - labels.Each(func(_ int, label *goquery.Selection) { - link, err := url.Parse(label.AttrOr("href", "")) - assert.NoError(t, err) - assert.NotContains(t, link.Query(), "milestone") + + t.Run("ViewIssueListContexts", func(t *testing.T) { + session := loginUser(t, "user2") + t.Run("dashboard", func(t *testing.T) { + req := NewRequest(t, "GET", "/issues") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, "user2/repo1#1", strings.TrimSpace(htmlDoc.doc.Find("#issue-list .index[href='/user2/repo1/issues/1']").Text())) + }) + t.Run("milestone", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/milestone/1") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Equal(t, 1, getIssuesSelection(t, htmlDoc).Length()) + assert.Empty(t, htmlDoc.doc.Find("#issue-list .milestone").Nodes) + labels := htmlDoc.doc.Find("#issue-list .labels-list a") + assert.Equal(t, 2, labels.Length()) + labels.Each(func(_ int, label *goquery.Selection) { + link, err := url.Parse(label.AttrOr("href", "")) + assert.NoError(t, err) + assert.NotContains(t, link.Query(), "milestone") + }) }) }) -} -func TestViewIssuesSortByType(t *testing.T) { - defer tests.PrepareTestEnv(t)() + t.Run("ViewIssuesSortByType", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + session := loginUser(t, user.Name) + req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by") + resp := session.MakeRequest(t, req, http.StatusOK) - session := loginUser(t, user.Name) - req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by") - resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + issuesSelection := getIssuesSelection(t, htmlDoc) + expectedNumIssues := min(unittest.GetCount(t, + &issues_model.Issue{RepoID: repo.ID, PosterID: user.ID}, + unittest.Cond("is_closed=?", false), + unittest.Cond("is_pull=?", false), + ), setting.UI.IssuePagingNum) + assert.Equal(t, expectedNumIssues, issuesSelection.Length()) - htmlDoc := NewHTMLParser(t, resp.Body) - issuesSelection := getIssuesSelection(t, htmlDoc) - expectedNumIssues := min(unittest.GetCount(t, - &issues_model.Issue{RepoID: repo.ID, PosterID: user.ID}, - unittest.Cond("is_closed=?", false), - unittest.Cond("is_pull=?", false), - ), setting.UI.IssuePagingNum) - assert.Equal(t, expectedNumIssues, issuesSelection.Length()) + issuesSelection.Each(func(_ int, selection *goquery.Selection) { + issue := getIssue(t, repo.ID, selection) + assert.Equal(t, user.ID, issue.PosterID) + }) + }) - issuesSelection.Each(func(_ int, selection *goquery.Selection) { - issue := getIssue(t, repo.ID, selection) - assert.Equal(t, user.ID, issue.PosterID) + t.Run("SearchIssues", func(t *testing.T) { + session := loginUser(t, "user2") + + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures + + link, _ := url.Parse("/issues/search") + checkIssues := func(expectedCount int, expectedTotal string) { + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + apiIssues := DecodeJSON(t, resp, []*api.Issue{}) + if expectedTotal != "" { + assert.Equal(t, expectedTotal, resp.Header().Get("X-Total-Count")) + } + assert.Len(t, apiIssues, expectedCount) + } + checkIssues(expectedIssueCount, "") + + since := "2000-01-01T00:50:01+00:00" // 946687801 + before := time.Unix(999307200, 0).Format(time.RFC3339) + query := url.Values{} + query.Add("since", since) + query.Add("before", before) + link.RawQuery = query.Encode() + checkIssues(11, "") + query.Del("since") + query.Del("before") + + query.Add("state", "closed") + link.RawQuery = query.Encode() + checkIssues(2, "") + + query.Set("state", "all") + link.RawQuery = query.Encode() + checkIssues(20, "22") + + query.Add("limit", "5") + link.RawQuery = query.Encode() + checkIssues(5, "22") + + query = url.Values{"assigned": {"true"}, "state": {"all"}} + link.RawQuery = query.Encode() + checkIssues(2, "") + + query = url.Values{"milestones": {"milestone1"}, "state": {"all"}} + link.RawQuery = query.Encode() + checkIssues(1, "") + + query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}} + link.RawQuery = query.Encode() + checkIssues(2, "") + + query = url.Values{"owner": {"user2"}} // user + link.RawQuery = query.Encode() + checkIssues(8, "") + + query = url.Values{"owner": {"org3"}} // organization + link.RawQuery = query.Encode() + checkIssues(5, "") + + query = url.Values{"owner": {"org3"}, "team": {"team1"}} // organization + team + link.RawQuery = query.Encode() + checkIssues(2, "") + }) + + t.Run("SearchIssuesWithLabels", func(t *testing.T) { + expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures + + session := loginUser(t, "user1") + link, _ := url.Parse("/issues/search") + query := url.Values{} + checkIssues := func(expectedCount int) { + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + apiIssues := DecodeJSON(t, resp, []*api.Issue{}) + assert.Len(t, apiIssues, expectedCount) + } + + link.RawQuery = query.Encode() + checkIssues(expectedIssueCount) + + query.Add("labels", "label1") + link.RawQuery = query.Encode() + checkIssues(2) + + // multiple labels + query.Set("labels", "label1,label2") + link.RawQuery = query.Encode() + checkIssues(2) + + // an org label + query.Set("labels", "orglabel4") + link.RawQuery = query.Encode() + checkIssues(1) + + // org and repo label + query.Set("labels", "label2,orglabel4") + query.Add("state", "all") + link.RawQuery = query.Encode() + checkIssues(2) + + // org and repo label which share the same issue + query.Set("labels", "label1,orglabel4") + link.RawQuery = query.Encode() + checkIssues(2) + }) + + t.Run("GetIssueInfo", func(t *testing.T) { + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + assert.NoError(t, issue.LoadAttributes(t.Context())) + assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issue.State()) + + session := loginUser(t, owner.Name) + + urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + respStruct := DecodeJSON(t, resp, &struct { + ConvertedIssue api.Issue + RenderedLabels template.HTML + }{}) + + assert.Equal(t, issue.ID, respStruct.ConvertedIssue.ID) + assert.Contains(t, string(respStruct.RenderedLabels), `"labels-list"`) + }) + + t.Run("IssueRefSelectorEnabledForNewIssue", func(t *testing.T) { + // user4 (non-collaborator on user2/repo1) must see an enabled ref selector + // when creating a new issue. + session := loginUser(t, "user4") + req := NewRequest(t, "GET", "/user2/repo1/issues/new") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + sel := htmlDoc.Find(".branch-selector-dropdown") + assert.Equal(t, 1, sel.Length()) + assert.False(t, sel.HasClass("disabled"), "branch selector should be enabled on the new issue form") + }) + + t.Run("ViewIssuesKeyword", func(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ + RepoID: repo.ID, + Index: 1, + }) + issues.UpdateIssueIndexer(t.Context(), issue.ID) + const keyword = "first" + require.Eventually(t, func() bool { + issueIDs, _, err := issues.SearchIssues(t.Context(), &issues.SearchOptions{Keyword: keyword, RepoIDs: []int64{repo.ID}}) + return err == nil && slices.Contains(issueIDs, issue.ID) + }, 10*time.Second, 10*time.Millisecond) + req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword) + resp := MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + issuesSelection := getIssuesSelection(t, htmlDoc) + assert.Equal(t, 1, issuesSelection.Length()) + issuesSelection.Each(func(_ int, selection *goquery.Selection) { + issue := getIssue(t, repo.ID, selection) + assert.False(t, issue.IsClosed) + assert.False(t, issue.IsPull) + assertMatch(t, issue, keyword) + }) + }) + + t.Run("NoLoginViewIssue", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/issues/1") + MakeRequest(t, req, http.StatusOK) + }) + + t.Run("IssueReferenceURL", func(t *testing.T) { + session := loginUser(t, "user2") + + issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) + + req := NewRequest(t, "GET", fmt.Sprintf("%s/issues/%d", repo.Link(), issue.Index)) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + ref, _ := htmlDoc.Find(`.timeline-item.comment.issue-content-comment .reference-issue`).Attr("data-reference") + assert.Equal(t, setting.AppURL+"user2/repo1/issues/1#issue-1", ref) + + ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.issue-content-comment) .reference-issue`).Attr("data-reference") + assert.Equal(t, setting.AppURL+"user2/repo1/issues/1#issuecomment-2", ref) + }) + + t.Run("UpdateIssueDeadline", func(t *testing.T) { + issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) + assert.NoError(t, issueBefore.LoadAttributes(t.Context())) + assert.Equal(t, "2002-04-20", issueBefore.DeadlineUnix.FormatDate()) + assert.Equal(t, api.StateOpen, issueBefore.State()) + + session := loginUser(t, owner.Name) + urlStr := fmt.Sprintf("/%s/%s/issues/%d/deadline", owner.Name, repoBefore.Name, issueBefore.Index) + + req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"}) + session.MakeRequest(t, req, http.StatusOK) + issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + assert.Equal(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate()) + + req = NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": ""}) + session.MakeRequest(t, req, http.StatusOK) + issueAfter = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) + assert.True(t, issueAfter.DeadlineUnix.IsZero()) + }) + + t.Run("IssueRedirect", func(t *testing.T) { + session := loginUser(t, "user2") + assertRedirect := func(requestURL, expectedURL string) { + req := NewRequest(t, "GET", requestURL) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, expectedURL, test.RedirectURL(resp)) + } + + // Test external tracker where style not set (shall default numeric) + assertRedirect("/org26/repo_external_tracker/issues/1", "https://tracker.com/org26/repo_external_tracker/issues/1") + + // Test external tracker with numeric style + assertRedirect("/org26/repo_external_tracker_numeric/issues/1", "https://tracker.com/org26/repo_external_tracker_numeric/issues/1") + + // Test external tracker with alphanumeric style (for a pull request) + assertRedirect("/org26/repo_external_tracker_alpha/issues/1", "/org26/repo_external_tracker_alpha/pulls/1") + + // test to check that the PR redirection works if the issue unit is disabled + // repo1 is a normal repository with issue unit enabled, visit issue 2(which is a pull request) + // will redirect to pulls + assertRedirect("/user2/repo1/issues/2", "/user2/repo1/pulls/2") + + // by the way, test PR redirection + assertRedirect("/user2/repo1/pulls/1", "/user2/repo1/issues/1") + + // disable issue unit + repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeIssues}) + _, err := db.DeleteByID[repo_model.RepoUnit](t.Context(), repoUnit.ID) + assert.NoError(t, err) + + // even if the issue unit is disabled, visiting an issue which is a pull request + // will still redirect to pull request + assertRedirect("/user2/repo1/issues/2", "/user2/repo1/pulls/2") }) } @@ -145,41 +389,6 @@ func TestIssueIDsSortByLabelScope(t *testing.T) { } } -func TestViewIssuesKeyword(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ - RepoID: repo.ID, - Index: 1, - }) - issues.UpdateIssueIndexer(t.Context(), issue.ID) - const keyword = "first" - require.Eventually(t, func() bool { - issueIDs, _, err := issues.SearchIssues(t.Context(), &issues.SearchOptions{Keyword: keyword, RepoIDs: []int64{repo.ID}}) - return err == nil && slices.Contains(issueIDs, issue.ID) - }, 10*time.Second, 10*time.Millisecond) - req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword) - resp := MakeRequest(t, req, http.StatusOK) - - htmlDoc := NewHTMLParser(t, resp.Body) - issuesSelection := getIssuesSelection(t, htmlDoc) - assert.Equal(t, 1, issuesSelection.Length()) - issuesSelection.Each(func(_ int, selection *goquery.Selection) { - issue := getIssue(t, repo.ID, selection) - assert.False(t, issue.IsClosed) - assert.False(t, issue.IsPull) - assertMatch(t, issue, keyword) - }) -} - -func TestNoLoginViewIssue(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - req := NewRequest(t, "GET", "/user2/repo1/issues/1") - MakeRequest(t, req, http.StatusOK) -} - func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content string) string { req := NewRequest(t, "GET", "/"+path.Join(user, repo, "issues", "new")) resp := session.MakeRequest(t, req, http.StatusOK) @@ -418,74 +627,41 @@ func TestIssueCrossReference(t *testing.T) { // Issue that will be referenced _, issueBase := testIssueWithBean(t, "user2", 1, "Title", "Description") + assertReference := func(repoID, issueID, commentID int64, action references.XRefAction) { + unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ + IssueID: issueBase.ID, + RefRepoID: repoID, + RefIssueID: issueID, + RefCommentID: commentID, + RefIsPull: false, + RefAction: action, + }) + } // Ref from issue title issueRefURL, issueRef := testIssueWithBean(t, "user2", 1, fmt.Sprintf("Title ref #%d", issueBase.Index), "Description") - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, - RefCommentID: 0, - RefIsPull: false, - RefAction: references.XRefActionNone, - }) + assertReference(1, issueRef.ID, 0, references.XRefActionNone) // Edit title, neuter ref testIssueChangeInfo(t, "user2", issueRefURL, "title", "Title no ref") - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, - RefCommentID: 0, - RefIsPull: false, - RefAction: references.XRefActionNeutered, - }) + assertReference(1, issueRef.ID, 0, references.XRefActionNeutered) // Ref from issue content issueRefURL, issueRef = testIssueWithBean(t, "user2", 1, "TitleXRef", fmt.Sprintf("Description ref #%d", issueBase.Index)) - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, - RefCommentID: 0, - RefIsPull: false, - RefAction: references.XRefActionNone, - }) + assertReference(1, issueRef.ID, 0, references.XRefActionNone) // Edit content, neuter ref testIssueChangeInfo(t, "user2", issueRefURL, "content", "Description no ref") - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, - RefCommentID: 0, - RefIsPull: false, - RefAction: references.XRefActionNeutered, - }) + assertReference(1, issueRef.ID, 0, references.XRefActionNeutered) // Ref from a comment session := loginUser(t, "user2") commentID := testIssueAddComment(t, session, issueRefURL, fmt.Sprintf("Adding ref from comment #%d", issueBase.Index), "") - comment := &issues_model.Comment{ - IssueID: issueBase.ID, - RefRepoID: 1, - RefIssueID: issueRef.ID, - RefCommentID: commentID, - RefIsPull: false, - RefAction: references.XRefActionNone, - } - unittest.AssertExistsAndLoadBean(t, comment) + assertReference(1, issueRef.ID, commentID, references.XRefActionNone) // Ref from a different repository _, issueRef = testIssueWithBean(t, "user12", 10, "TitleXRef", fmt.Sprintf("Description ref user2/repo1#%d", issueBase.Index)) - unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ - IssueID: issueBase.ID, - RefRepoID: 10, - RefIssueID: issueRef.ID, - RefCommentID: 0, - RefIsPull: false, - RefAction: references.XRefActionNone, - }) + assertReference(10, issueRef.ID, 0, references.XRefActionNone) } func testIssueWithBean(t *testing.T, user string, repoID int64, title, content string) (string, *issues_model.Issue) { @@ -507,246 +683,6 @@ func testIssueChangeInfo(t *testing.T, user, issueURL, info, value string) { _ = session.MakeRequest(t, req, http.StatusOK) } -func TestIssueRedirect(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - - // Test external tracker where style not set (shall default numeric) - req := NewRequest(t, "GET", "/org26/repo_external_tracker/issues/1") - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "https://tracker.com/org26/repo_external_tracker/issues/1", test.RedirectURL(resp)) - - // Test external tracker with numeric style - req = NewRequest(t, "GET", "/org26/repo_external_tracker_numeric/issues/1") - resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "https://tracker.com/org26/repo_external_tracker_numeric/issues/1", test.RedirectURL(resp)) - - // Test external tracker with alphanumeric style (for a pull request) - req = NewRequest(t, "GET", "/org26/repo_external_tracker_alpha/issues/1") - resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/org26/repo_external_tracker_alpha/pulls/1", test.RedirectURL(resp)) - - // test to check that the PR redirection works if the issue unit is disabled - // repo1 is a normal repository with issue unit enabled, visit issue 2(which is a pull request) - // will redirect to pulls - req = NewRequest(t, "GET", "/user2/repo1/issues/2") - resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user2/repo1/pulls/2", test.RedirectURL(resp)) - - // by the way, test PR redirection - req = NewRequest(t, "GET", "/user2/repo1/pulls/1") - resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user2/repo1/issues/1", test.RedirectURL(resp)) - - // disable issue unit - repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeIssues}) - _, err := db.DeleteByID[repo_model.RepoUnit](t.Context(), repoUnit.ID) - assert.NoError(t, err) - - // even if the issue unit is disabled, visiting an issue which is a pull request - // will still redirect to pull request - req = NewRequest(t, "GET", "/user2/repo1/issues/2") - resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user2/repo1/pulls/2", test.RedirectURL(resp)) -} - -func TestSearchIssues(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - - expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures - - link, _ := url.Parse("/issues/search") - req := NewRequest(t, "GET", link.String()) - resp := session.MakeRequest(t, req, http.StatusOK) - apiIssues := DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, expectedIssueCount) - - since := "2000-01-01T00:50:01+00:00" // 946687801 - before := time.Unix(999307200, 0).Format(time.RFC3339) - query := url.Values{} - query.Add("since", since) - query.Add("before", before) - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 11) - query.Del("since") - query.Del("before") - - query.Add("state", "closed") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) - - query.Set("state", "all") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) - assert.Len(t, apiIssues, 20) - - query.Add("limit", "5") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) - assert.Len(t, apiIssues, 5) - - query = url.Values{"assigned": {"true"}, "state": {"all"}} - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) - - query = url.Values{"milestones": {"milestone1"}, "state": {"all"}} - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 1) - - query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}} - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) - - query = url.Values{"owner": {"user2"}} // user - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 8) - - query = url.Values{"owner": {"org3"}} // organization - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 5) - - query = url.Values{"owner": {"org3"}, "team": {"team1"}} // organization + team - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) -} - -func TestSearchIssuesWithLabels(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - expectedIssueCount := min(20, setting.UI.IssuePagingNum) // 20 is from the fixtures - - session := loginUser(t, "user1") - link, _ := url.Parse("/issues/search") - query := url.Values{} - var apiIssues []*api.Issue - - link.RawQuery = query.Encode() - req := NewRequest(t, "GET", link.String()) - resp := session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, expectedIssueCount) - - query.Add("labels", "label1") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) - - // multiple labels - query.Set("labels", "label1,label2") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) - - // an org label - query.Set("labels", "orglabel4") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 1) - - // org and repo label - query.Set("labels", "label2,orglabel4") - query.Add("state", "all") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) - - // org and repo label which share the same issue - query.Set("labels", "label1,orglabel4") - link.RawQuery = query.Encode() - req = NewRequest(t, "GET", link.String()) - resp = session.MakeRequest(t, req, http.StatusOK) - apiIssues = DecodeJSON(t, resp, []*api.Issue{}) - assert.Len(t, apiIssues, 2) -} - -func TestGetIssueInfo(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - assert.NoError(t, issue.LoadAttributes(t.Context())) - assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix)) - assert.Equal(t, api.StateOpen, issue.State()) - - session := loginUser(t, owner.Name) - - urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index) - req := NewRequest(t, "GET", urlStr) - resp := session.MakeRequest(t, req, http.StatusOK) - respStruct := DecodeJSON(t, resp, &struct { - ConvertedIssue api.Issue - RenderedLabels template.HTML - }{}) - - assert.Equal(t, issue.ID, respStruct.ConvertedIssue.ID) - assert.Contains(t, string(respStruct.RenderedLabels), `"labels-list"`) -} - -func TestUpdateIssueDeadline(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - issueBefore := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) - repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) - owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) - assert.NoError(t, issueBefore.LoadAttributes(t.Context())) - assert.Equal(t, "2002-04-20", issueBefore.DeadlineUnix.FormatDate()) - assert.Equal(t, api.StateOpen, issueBefore.State()) - - session := loginUser(t, owner.Name) - urlStr := fmt.Sprintf("/%s/%s/issues/%d/deadline", owner.Name, repoBefore.Name, issueBefore.Index) - - req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"}) - session.MakeRequest(t, req, http.StatusOK) - issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) - assert.Equal(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate()) - - req = NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": ""}) - session.MakeRequest(t, req, http.StatusOK) - issueAfter = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) - assert.True(t, issueAfter.DeadlineUnix.IsZero()) -} - func TestUpdateIssueRefByPoster(t *testing.T) { defer tests.PrepareTestEnv(t)() @@ -784,36 +720,3 @@ func TestIssueRefSelectorEnabledForPoster(t *testing.T) { _, hasURL := sel.Attr("data-url-update-issueref") assert.True(t, hasURL, "data-url-update-issueref must be set for the issue poster") } - -func TestIssueRefSelectorEnabledForNewIssue(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // user4 (non-collaborator on user2/repo1) must see an enabled ref selector - // when creating a new issue. - session := loginUser(t, "user4") - req := NewRequest(t, "GET", "/user2/repo1/issues/new") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - - sel := htmlDoc.Find(".branch-selector-dropdown") - assert.Equal(t, 1, sel.Length()) - assert.False(t, sel.HasClass("disabled"), "branch selector should be enabled on the new issue form") -} - -func TestIssueReferenceURL(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - - issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) - - req := NewRequest(t, "GET", fmt.Sprintf("%s/issues/%d", repo.Link(), issue.Index)) - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - - ref, _ := htmlDoc.Find(`.timeline-item.comment.issue-content-comment .reference-issue`).Attr("data-reference") - assert.Equal(t, setting.AppURL+"user2/repo1/issues/1#issue-1", ref) - - ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.issue-content-comment) .reference-issue`).Attr("data-reference") - assert.Equal(t, setting.AppURL+"user2/repo1/issues/1#issuecomment-2", ref) -} diff --git a/tests/integration/lfs_local_endpoint_test.go b/tests/integration/lfs_local_endpoint_test.go index 3418963741b..3095083080c 100644 --- a/tests/integration/lfs_local_endpoint_test.go +++ b/tests/integration/lfs_local_endpoint_test.go @@ -10,7 +10,6 @@ import ( "testing" "gitea.dev/modules/lfs" - "gitea.dev/tests" "github.com/stretchr/testify/assert" ) @@ -21,8 +20,6 @@ func str2url(raw string) *url.URL { } func TestDetermineLocalEndpoint(t *testing.T) { - defer tests.PrepareTestEnv(t)() - root := t.TempDir() rootdotgit := t.TempDir() diff --git a/tests/integration/linguist_test.go b/tests/integration/linguist_test.go index 45e8d13292b..a69de33d534 100644 --- a/tests/integration/linguist_test.go +++ b/tests/integration/linguist_test.go @@ -31,6 +31,12 @@ func TestLinguist(t *testing.T) { phpContent := "" lockContent := "# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand." mdContent := "markdown" + newFile := func(treePath, content string) *files_service.ChangeRepoFile { + return &files_service.ChangeRepoFile{ + TreePath: treePath, + ContentReader: strings.NewReader(content), + } + } cases := []struct { GitAttributesContent string @@ -44,36 +50,18 @@ func TestLinguist(t *testing.T) { // case 1 { FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, - { - TreePath: "python.py", - ContentReader: strings.NewReader(pyContent), - }, - { - TreePath: "php.php", - ContentReader: strings.NewReader(phpContent), - }, + newFile("cplusplus.cpp", cppContent), + newFile("python.py", pyContent), + newFile("php.php", phpContent), }, ExpectedLanguageOrder: []string{"C++", "PHP", "Python"}, }, // case 2 { FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: ".cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, - { - TreePath: "python.py", - ContentReader: strings.NewReader(pyContent), - }, - { - TreePath: "vendor/php.php", - ContentReader: strings.NewReader(phpContent), - }, + newFile(".cplusplus.cpp", cppContent), + newFile("python.py", pyContent), + newFile("vendor/php.php", phpContent), }, ExpectedLanguageOrder: []string{"Python"}, }, @@ -81,10 +69,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.cpp linguist-language=Go", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, + newFile("cplusplus.cpp", cppContent), }, ExpectedLanguageOrder: []string{"Go"}, }, @@ -92,10 +77,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.cpp gitlab-language=Go?parent=json", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, + newFile("cplusplus.cpp", cppContent), }, ExpectedLanguageOrder: []string{"Go"}, }, @@ -103,10 +85,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.cpp linguist-language=HTML gitlab-language=Go?parent=json", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, + newFile("cplusplus.cpp", cppContent), }, ExpectedLanguageOrder: []string{"HTML"}, }, @@ -114,10 +93,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "vendor/** linguist-vendored=false", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "vendor/php.php", - ContentReader: strings.NewReader(phpContent), - }, + newFile("vendor/php.php", phpContent), }, ExpectedLanguageOrder: []string{"PHP"}, }, @@ -125,18 +101,9 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.cpp linguist-vendored=true\n*.py linguist-vendored\nvendor/** -linguist-vendored", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, - { - TreePath: "python.py", - ContentReader: strings.NewReader(pyContent), - }, - { - TreePath: "vendor/php.php", - ContentReader: strings.NewReader(phpContent), - }, + newFile("cplusplus.cpp", cppContent), + newFile("python.py", pyContent), + newFile("vendor/php.php", phpContent), }, ExpectedLanguageOrder: []string{"PHP"}, }, @@ -144,10 +111,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "poetry.lock linguist-language=Go", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "poetry.lock", - ContentReader: strings.NewReader(lockContent), - }, + newFile("poetry.lock", lockContent), }, ExpectedLanguageOrder: []string{"Go"}, }, @@ -155,10 +119,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "poetry.lock linguist-generated=false", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "poetry.lock", - ContentReader: strings.NewReader(lockContent), - }, + newFile("poetry.lock", lockContent), }, ExpectedLanguageOrder: []string{"TOML"}, }, @@ -166,10 +127,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.cpp -linguist-detectable", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, + newFile("cplusplus.cpp", cppContent), }, ExpectedLanguageOrder: []string{}, }, @@ -177,10 +135,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.md linguist-detectable", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "test.md", - ContentReader: strings.NewReader(mdContent), - }, + newFile("test.md", mdContent), }, ExpectedLanguageOrder: []string{"Markdown"}, }, @@ -188,18 +143,9 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "test2.md linguist-detectable", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, - { - TreePath: "test.md", - ContentReader: strings.NewReader(mdContent), - }, - { - TreePath: "test2.md", - ContentReader: strings.NewReader(mdContent), - }, + newFile("cplusplus.cpp", cppContent), + newFile("test.md", mdContent), + newFile("test2.md", mdContent), }, ExpectedLanguageOrder: []string{"C++", "Markdown"}, }, @@ -207,10 +153,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "README.md linguist-documentation=false", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "README.md", - ContentReader: strings.NewReader(mdContent), - }, + newFile("README.md", mdContent), }, ExpectedLanguageOrder: []string{"Markdown"}, }, @@ -218,10 +161,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.yaml linguist-detectable", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "config.yaml", - ContentReader: strings.NewReader("name: test\ndescription: A test yaml file\n"), - }, + newFile("config.yaml", "name: test\ndescription: A test yaml file\n"), }, ExpectedLanguageOrder: []string{"YAML"}, }, @@ -229,10 +169,7 @@ func TestLinguist(t *testing.T) { { GitAttributesContent: "*.cpp linguist-language=golang", FilesToAdd: []*files_service.ChangeRepoFile{ - { - TreePath: "cplusplus.cpp", - ContentReader: strings.NewReader(cppContent), - }, + newFile("cplusplus.cpp", cppContent), }, ExpectedLanguageOrder: []string{"Go"}, }, @@ -247,10 +184,7 @@ func TestLinguist(t *testing.T) { assert.NoError(t, err) files := []*files_service.ChangeRepoFile{ - { - TreePath: ".gitattributes", - ContentReader: strings.NewReader(c.GitAttributesContent), - }, + newFile(".gitattributes", c.GitAttributesContent), } files = append(files, c.FilesToAdd...) for _, f := range files { diff --git a/tests/integration/markup_external_test.go b/tests/integration/markup_external_test.go index d0b1957138b..993e16af8a8 100644 --- a/tests/integration/markup_external_test.go +++ b/tests/integration/markup_external_test.go @@ -18,14 +18,12 @@ import ( "gitea.dev/modules/public" "gitea.dev/modules/setting" "gitea.dev/modules/test" - "gitea.dev/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestExternalMarkupRenderer(t *testing.T) { - defer tests.PrepareTestEnv(t)() if !setting.Database.Type.IsSQLite3() { t.Skip("only SQLite3 test config supports external markup renderer") return diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go index 8191a26169b..97545675a7e 100644 --- a/tests/integration/migrate_test.go +++ b/tests/integration/migrate_test.go @@ -37,25 +37,23 @@ import ( func TestMigrateLocalPath(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - defer test.MockVariableValue(&setting.ImportLocalPaths, true)() + t.Run("LocalPath", func(t *testing.T) { + defer test.MockVariableValue(&setting.ImportLocalPaths, true)() - adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}) + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}) - basePath := t.TempDir() + basePath := t.TempDir() - lowercasePath := filepath.Join(basePath, "lowercase") - err := os.Mkdir(lowercasePath, 0o700) - assert.NoError(t, err) - - err = migrations.IsMigrateURLAllowed(lowercasePath, adminUser) - assert.NoError(t, err, "case lowercase path") - - mixedcasePath := filepath.Join(basePath, "mIxeDCaSe") - err = os.Mkdir(mixedcasePath, 0o700) - assert.NoError(t, err) - - err = migrations.IsMigrateURLAllowed(mixedcasePath, adminUser) - assert.NoError(t, err, "case mixedcase path") + for _, name := range []string{"lowercase", "mIxeDCaSe"} { + localPath := filepath.Join(basePath, name) + assert.NoError(t, os.Mkdir(localPath, 0o700)) + assert.NoError(t, migrations.IsMigrateURLAllowed(localPath, adminUser), "case "+strings.ToLower(name)+" path") + } + }) + t.Run("UpdateCommentsMigrationsByType", func(t *testing.T) { + err := issues_model.UpdateCommentsMigrationsByType(t.Context(), structs.GithubService, "1", 1) + assert.NoError(t, err) + }) } func TestMigrateGiteaForm(t *testing.T) { @@ -103,13 +101,6 @@ func TestMigrateGiteaForm(t *testing.T) { }) } -func Test_UpdateCommentsMigrationsByType(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - err := issues_model.UpdateCommentsMigrationsByType(t.Context(), structs.GithubService, "1", 1) - assert.NoError(t, err) -} - // setupGiteaMockServer creates a mock HTTP server that replays API responses from fixture files. // If a GITEA_TOKEN environment variable is set, the mock server proxies requests to the live // gitea.com instance and saves the responses as fixture files for future test runs. diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 9f80a3b1e49..e396610e437 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -130,28 +130,26 @@ func TestMirrorPull(t *testing.T) { mirror = unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: mirrorRepo.ID}) assert.Equal(t, lastMirrorSync, mirror.LastSyncUnix) -} - -// TestMirrorPullSSRFRevalidation ensures a pull mirror re-validates its remote URL against -// the migration allow/block list on every sync, so a mirror whose (network) remote now -// points at a disallowed internal host is never fetched. -func TestMirrorPullSSRFRevalidation(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - ctx := t.Context() - var requests atomic.Int32 - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requests.Add(1) - http.Redirect(w, r, "http://"+strings.Replace(r.Host, "127.0.0.1", "0.0.0.0", 1)+r.URL.RequestURI(), http.StatusFound) - })) - defer server.Close() - - mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: 5}) - require.NoError(t, mirror_service.UpdateAddress(ctx, mirror, server.URL+"/repo.git")) - assert.False(t, mirror_service.SyncPullMirror(ctx, mirror.RepoID)) - assert.EqualValues(t, 1, requests.Load(), "the git proxy must deny the redirect to a reserved address") - - defer test.MockVariableValue(&setting.Migrations.AllowedHostList, "")() - assert.False(t, mirror_service.SyncPullMirror(ctx, mirror.RepoID)) - assert.EqualValues(t, 1, requests.Load(), "the disallowed internal remote must not be reached") + + // TestMirrorPullSSRFRevalidation ensures a pull mirror re-validates its remote URL against + // the migration allow/block list on every sync, so a mirror whose (network) remote now + // points at a disallowed internal host is never fetched. + t.Run("SSRFRevalidation", func(t *testing.T) { + ctx := t.Context() + var requests atomic.Int32 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests.Add(1) + http.Redirect(w, r, "http://"+strings.Replace(r.Host, "127.0.0.1", "0.0.0.0", 1)+r.URL.RequestURI(), http.StatusFound) + })) + defer server.Close() + + mirror := unittest.AssertExistsAndLoadBean(t, &repo_model.Mirror{RepoID: 5}) + require.NoError(t, mirror_service.UpdateAddress(ctx, mirror, server.URL+"/repo.git")) + assert.False(t, mirror_service.SyncPullMirror(ctx, mirror.RepoID)) + assert.EqualValues(t, 1, requests.Load(), "the git proxy must deny the redirect to a reserved address") + + defer test.MockVariableValue(&setting.Migrations.AllowedHostList, "")() + assert.False(t, mirror_service.SyncPullMirror(ctx, mirror.RepoID)) + assert.EqualValues(t, 1, requests.Load(), "the disallowed internal remote must not be reached") + }) } diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go index 6372ab856fe..88f1a626bd3 100644 --- a/tests/integration/mirror_push_test.go +++ b/tests/integration/mirror_push_test.go @@ -21,17 +21,48 @@ import ( mirror_service "gitea.dev/services/mirror" repo_service "gitea.dev/services/repository" wiki_service "gitea.dev/services/wiki" - "gitea.dev/tests" "github.com/stretchr/testify/assert" ) func TestMirrorPush(t *testing.T) { - onGiteaRun(t, testMirrorPush) -} + onGiteaRun(t, func(t *testing.T, serverURL *url.URL) { + t.Run("SettingsUpdate", func(t *testing.T) { + session := loginUser(t, "user2") + repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + testCreatePushMirror(t, session, "user2", "repo2", "https://127.0.0.1/user1/repo1.git", "", "", "24h") -func TestMirrorPushWikiDefaultBranchMismatch(t *testing.T) { - onGiteaRun(t, testMirrorPushWikiDefaultBranchMismatch) + pushMirrors, cnt, err := repo_model.GetPushMirrorsByRepoID(t.Context(), repo2.ID, db.ListOptions{}) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + assert.Equal(t, 24*time.Hour, pushMirrors[0].Interval) + repo2PushMirrorID := pushMirrors[0].ID + + // update repo2 push mirror + assert.True(t, doUpdatePushMirror(t, session, "user2", "repo2", repo2PushMirrorID, "10m0s")) + pushMirror := unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) + assert.Equal(t, 10*time.Minute, pushMirror.Interval) + + // avoid updating repo2 push mirror from repo1 + assert.False(t, doUpdatePushMirror(t, session, "user2", "repo1", repo2PushMirrorID, "20m0s")) + pushMirror = unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) + assert.Equal(t, 10*time.Minute, pushMirror.Interval) // not changed + + // avoid deleting repo2 push mirror from repo1 + assert.False(t, doRemovePushMirror(t, session, "user2", "repo1", repo2PushMirrorID)) + unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) + + // delete repo2 push mirror + assert.True(t, doRemovePushMirror(t, session, "user2", "repo2", repo2PushMirrorID)) + unittest.AssertNotExistsBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) + }) + t.Run("Push", func(t *testing.T) { + testMirrorPush(t, serverURL) + }) + t.Run("WikiDefaultBranchMismatch", func(t *testing.T) { + testMirrorPushWikiDefaultBranchMismatch(t, serverURL) + }) + }) } func testMirrorPush(t *testing.T, u *url.URL) { @@ -150,35 +181,3 @@ func doUpdatePushMirror(t *testing.T, session *TestSession, owner, repo string, resp := session.MakeRequest(t, req, NoExpectedStatus) return resp.Code == http.StatusSeeOther } - -func TestRepoSettingPushMirrorUpdate(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - testCreatePushMirror(t, session, "user2", "repo2", "https://127.0.0.1/user1/repo1.git", "", "", "24h") - - pushMirrors, cnt, err := repo_model.GetPushMirrorsByRepoID(t.Context(), repo2.ID, db.ListOptions{}) - assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) - assert.Equal(t, 24*time.Hour, pushMirrors[0].Interval) - repo2PushMirrorID := pushMirrors[0].ID - - // update repo2 push mirror - assert.True(t, doUpdatePushMirror(t, session, "user2", "repo2", repo2PushMirrorID, "10m0s")) - pushMirror := unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - assert.Equal(t, 10*time.Minute, pushMirror.Interval) - - // avoid updating repo2 push mirror from repo1 - assert.False(t, doUpdatePushMirror(t, session, "user2", "repo1", repo2PushMirrorID, "20m0s")) - pushMirror = unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - assert.Equal(t, 10*time.Minute, pushMirror.Interval) // not changed - - // avoid deleting repo2 push mirror from repo1 - assert.False(t, doRemovePushMirror(t, session, "user2", "repo1", repo2PushMirrorID)) - unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - - // delete repo2 push mirror - assert.True(t, doRemovePushMirror(t, session, "user2", "repo2", repo2PushMirrorID)) - unittest.AssertNotExistsBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) -} diff --git a/tests/integration/oauth_avatar_test.go b/tests/integration/oauth_avatar_test.go deleted file mode 100644 index 2cc35980145..00000000000 --- a/tests/integration/oauth_avatar_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - user_model "gitea.dev/models/user" - "gitea.dev/modules/setting" - "gitea.dev/modules/test" - "gitea.dev/modules/web" - "gitea.dev/routers/web/auth" - "gitea.dev/services/auth/source/oauth2" - "gitea.dev/services/context" - "gitea.dev/tests" - - "github.com/markbates/goth" - "github.com/markbates/goth/gothic" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestOAuth2AvatarFromPicture(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.OAuth2Client.UpdateAvatar, true)() - // the SSRF-protected avatar client blocks loopback by default; allow the loopback mock server here - defer test.MockVariableValue(&setting.Security.AllowedHostList, "loopback,external")() - - mockServer := createOAuth2MockProvider() - defer mockServer.Close() - addOAuth2Source(t, "test-oidc-avatar", oauth2.Source{ - Provider: "openidConnect", - ClientID: "test-client-id", - OpenIDConnectAutoDiscoveryURL: mockServer.URL + "/.well-known/openid-configuration", - }) - authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oidc-avatar") - require.NoError(t, err) - oauth2Source, ok := authSource.Cfg.(*oauth2.Source) - require.True(t, ok) - providerName := oauth2Source.Provider - - t.Run("AutoRegister", func(t *testing.T) { - defer test.MockVariableValue(&setting.OAuth2Client.Username, "")() - defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() - defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) { - return goth.User{ - Provider: providerName, - UserID: "oidc-user-ua-pic", - Email: "oidc-user-ua-pic@example.com", - Name: "OIDC UA Pic", - AvatarURL: mockServer.URL + "/avatar.png", - }, nil - })() - - req := NewRequest(t, "GET", "/user/oauth2/test-oidc-avatar/callback?code=XYZ&state=XYZ") - emptyTestSession(t).MakeRequest(t, req, http.StatusSeeOther) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "oidc-user-ua-pic"}) - assert.True(t, user.UseCustomAvatar, "avatar must sync (requires Gitea UA)") - assert.NotEmpty(t, user.Avatar) - }) - - t.Run("LinkAccountRegister", func(t *testing.T) { - const newUserName = "oidc-link-register" - defer web.RouteMockReset() - web.RouteMock(web.MockAfterMiddlewares, func(ctx *context.Context) { - require.NoError(t, auth.Oauth2SetLinkAccountData(ctx, auth.LinkAccountData{ - AuthSourceID: authSource.ID, - GothUser: goth.User{ - Provider: providerName, - UserID: "oidc-link-register-sub", - Email: "oidc-link-register-a@example.com", - Name: "OIDC Link Register", - AvatarURL: mockServer.URL + "/avatar.png", - }, - })) - }) - - req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ - "user_name": newUserName, - "email": "oidc-link-register-b@example.com", - "password": "AVeryStrongPassword!1", - "retype": "AVeryStrongPassword!1", - }) - emptyTestSession(t).MakeRequest(t, req, http.StatusSeeOther) - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: newUserName}) - require.Equal(t, auth_model.OAuth2, user.LoginType) - assert.True(t, user.UseCustomAvatar, "register-link flow must sync avatar from `picture` claim") - assert.NotEmpty(t, user.Avatar) - }) -} diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go index 38b473ad281..cf04fd70fa7 100644 --- a/tests/integration/oauth_test.go +++ b/tests/integration/oauth_test.go @@ -29,7 +29,10 @@ import ( "gitea.dev/modules/test" "gitea.dev/modules/timeutil" "gitea.dev/modules/util" + "gitea.dev/modules/web" + "gitea.dev/routers/web/auth" "gitea.dev/services/auth/source/oauth2" + "gitea.dev/services/context" "gitea.dev/services/oauth2_provider" "gitea.dev/tests" @@ -41,6 +44,77 @@ import ( "github.com/stretchr/testify/require" ) +func TestOAuth2AvatarFromPicture(t *testing.T) { + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.OAuth2Client.UpdateAvatar, true)() + // the SSRF-protected avatar client blocks loopback by default; allow the loopback mock server here + defer test.MockVariableValue(&setting.Security.AllowedHostList, "loopback,external")() + + mockServer := createOAuth2MockProvider() + defer mockServer.Close() + addOAuth2Source(t, "test-oidc-avatar", oauth2.Source{ + Provider: "openidConnect", + ClientID: "test-client-id", + OpenIDConnectAutoDiscoveryURL: mockServer.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "test-oidc-avatar") + require.NoError(t, err) + oauth2Source, ok := authSource.Cfg.(*oauth2.Source) + require.True(t, ok) + providerName := oauth2Source.Provider + + t.Run("AutoRegister", func(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.Username, "")() + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{ + Provider: providerName, + UserID: "oidc-user-ua-pic", + Email: "oidc-user-ua-pic@example.com", + Name: "OIDC UA Pic", + AvatarURL: mockServer.URL + "/avatar.png", + }, nil + })() + + req := NewRequest(t, "GET", "/user/oauth2/test-oidc-avatar/callback?code=XYZ&state=XYZ") + emptyTestSession(t).MakeRequest(t, req, http.StatusSeeOther) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "oidc-user-ua-pic"}) + assert.True(t, user.UseCustomAvatar, "avatar must sync (requires Gitea UA)") + assert.NotEmpty(t, user.Avatar) + }) + + t.Run("LinkAccountRegister", func(t *testing.T) { + const newUserName = "oidc-link-register" + defer web.RouteMockReset() + web.RouteMock(web.MockAfterMiddlewares, func(ctx *context.Context) { + require.NoError(t, auth.Oauth2SetLinkAccountData(ctx, auth.LinkAccountData{ + AuthSourceID: authSource.ID, + GothUser: goth.User{ + Provider: providerName, + UserID: "oidc-link-register-sub", + Email: "oidc-link-register-a@example.com", + Name: "OIDC Link Register", + AvatarURL: mockServer.URL + "/avatar.png", + }, + })) + }) + + req := NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{ + "user_name": newUserName, + "email": "oidc-link-register-b@example.com", + "password": "AVeryStrongPassword!1", + "retype": "AVeryStrongPassword!1", + }) + emptyTestSession(t).MakeRequest(t, req, http.StatusSeeOther) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: newUserName}) + require.Equal(t, auth_model.OAuth2, user.LoginType) + assert.True(t, user.UseCustomAvatar, "register-link flow must sync avatar from `picture` claim") + assert.NotEmpty(t, user.Avatar) + }) +} + func testOAuth2PrepareTestCode(t *testing.T) { require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.OAuth2AuthorizationCode{})) err := db.Insert(t.Context(), &auth_model.OAuth2AuthorizationCode{ @@ -104,6 +178,42 @@ func issueOAuthAuthorizationCode(t *testing.T, user *user_model.User, app *api.O func TestOAuth2(t *testing.T) { defer tests.PrepareTestEnv(t)() + // TestOAuthUserInfoTokenScope verifies the OIDC userinfo endpoint enforces the + // read:user token scope, so a restrictively-scoped token cannot read identity claims. + t.Run("UserInfoTokenScope", func(t *testing.T) { + // a token without the user scope must be rejected + miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) + req := NewRequest(t, "GET", "/login/oauth/userinfo") + req.SetHeader("Authorization", "Bearer "+miscToken) + MakeRequest(t, req, http.StatusForbidden) + + // a token with read:user is allowed and returns the identity claims + userToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser) + req = NewRequest(t, "GET", "/login/oauth/userinfo") + req.SetHeader("Authorization", "Bearer "+userToken) + resp := MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), "user2@example.com") + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + for _, tc := range []struct { + scope, wantScope string + status int + }{ + {"invalid:scope", "no-token-scope", http.StatusForbidden}, + {"openid invalid:scope", "openid no-token-scope", http.StatusForbidden}, + {"no-token-scope", "no-token-scope", http.StatusForbidden}, + {"openid read:user invalid:scope", "openid read:user", http.StatusOK}, + } { + t.Run(tc.scope, func(t *testing.T) { + response := issueOAuthAccessTokenResponseForScope(t, user, tc.scope) + assert.Equal(t, tc.wantScope, response.Scope) + req := NewRequest(t, "GET", "/login/oauth/userinfo") + req.SetHeader("Authorization", "Bearer "+response.AccessToken) + MakeRequest(t, req, tc.status) + }) + } + }) + t.Run("Provider", func(t *testing.T) { t.Run("AuthorizeNoClientID", testAuthorizeNoClientID) t.Run("AuthorizeUnregisteredRedirect", testAuthorizeUnregisteredRedirect) @@ -1421,41 +1531,3 @@ func testOAuthSourceSpecialChars(t *testing.T) { testOAuth2(t, "/user/oauth2/test%2Bplus", http.StatusTemporaryRedirect) testOAuth2(t, "/user/oauth2/test%20plus", http.StatusNotFound) } - -// TestOAuthUserInfoTokenScope verifies the OIDC userinfo endpoint enforces the -// read:user token scope, so a restrictively-scoped token cannot read identity claims. -func TestOAuthUserInfoTokenScope(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // a token without the user scope must be rejected - miscToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadMisc) - req := NewRequest(t, "GET", "/login/oauth/userinfo") - req.SetHeader("Authorization", "Bearer "+miscToken) - MakeRequest(t, req, http.StatusForbidden) - - // a token with read:user is allowed and returns the identity claims - userToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser) - req = NewRequest(t, "GET", "/login/oauth/userinfo") - req.SetHeader("Authorization", "Bearer "+userToken) - resp := MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), "user2@example.com") - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - for _, tc := range []struct { - scope, wantScope string - status int - }{ - {"invalid:scope", "no-token-scope", http.StatusForbidden}, - {"openid invalid:scope", "openid no-token-scope", http.StatusForbidden}, - {"no-token-scope", "no-token-scope", http.StatusForbidden}, - {"openid read:user invalid:scope", "openid read:user", http.StatusOK}, - } { - t.Run(tc.scope, func(t *testing.T) { - response := issueOAuthAccessTokenResponseForScope(t, user, tc.scope) - assert.Equal(t, tc.wantScope, response.Scope) - req := NewRequest(t, "GET", "/login/oauth/userinfo") - req.SetHeader("Authorization", "Bearer "+response.AccessToken) - MakeRequest(t, req, tc.status) - }) - } -} diff --git a/tests/integration/org_team_invite_test.go b/tests/integration/org_team_invite_test.go index 42780a844c9..85c2b0ced96 100644 --- a/tests/integration/org_team_invite_test.go +++ b/tests/integration/org_team_invite_test.go @@ -21,289 +21,229 @@ import ( ) func TestOrgTeamEmailInvite(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - - isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.False(t, isMember) - - session := loginUser(t, "user1") - - teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) - req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ - "uid": "1", - "uname": user.Email, - }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - // get the invite token - invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) - assert.NoError(t, err) - assert.Len(t, invites, 1) - - session = loginUser(t, user.Name) - - // join the team - inviteURL := "/org/invite/" + invites[0].Token - req = NewRequest(t, "POST", inviteURL) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.True(t, isMember) -} - -// Check that users are redirected to accept the invitation correctly after login -func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - - isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.False(t, isMember) - - // create the invite - session := loginUser(t, "user1") - - teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) - req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ - "uid": "1", - "uname": user.Email, - }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - // get the invite token - invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) - assert.NoError(t, err) - assert.Len(t, invites, 1) - - // accept the invite - inviteURL := "/org/invite/" + invites[0].Token - req = NewRequest(t, "GET", "/user/login?redirect_to="+url.QueryEscape(inviteURL)) - resp = MakeRequest(t, req, http.StatusOK) - - req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{ - "user_name": "user5", - "password": "password", - }) - for _, c := range resp.Result().Cookies() { - req.AddCookie(c) + var prepared bool + prepare := func(t *testing.T) func() { + if prepared { + cleanup := tests.PrintCurrentTest(t) + assert.NoError(t, unittest.LoadFixtures()) + return cleanup + } else { + prepared = true + return tests.PrepareTestEnv(t) + } } - resp = MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, inviteURL, test.RedirectURL(resp)) + for _, name := range []string{"Direct", "RedirectsExistingUser", "RedirectsExistingUserWithLogin"} { + t.Run(name, func(t *testing.T) { + defer prepare(t)() - // complete the login process - ch := http.Header{} - ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) - cr := http.Request{Header: ch} + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - session = emptyTestSession(t) - baseURL, err := url.Parse(setting.AppURL) - assert.NoError(t, err) - session.jar.SetCookies(baseURL, cr.Cookies()) + isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) + assert.NoError(t, err) + assert.False(t, isMember) - // make the request - req = NewRequest(t, "POST", test.RedirectURL(resp)) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) + // create the invite + session := loginUser(t, "user1") - isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.True(t, isMember) -} + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ + "uid": "1", + "uname": user.Email, + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", test.RedirectURL(resp)) + session.MakeRequest(t, req, http.StatusOK) -// Check that newly signed up users are redirected to accept the invitation correctly -func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() + // get the invite token + invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) + assert.NoError(t, err) + assert.Len(t, invites, 1) - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) + inviteURL := "/org/invite/" + invites[0].Token + switch name { + case "Direct": + session = loginUser(t, user.Name) + case "RedirectsExistingUser": + // Check that users are redirected to accept the invitation correctly after login + req = NewRequest(t, "GET", "/user/login?redirect_to="+url.QueryEscape(inviteURL)) + resp = MakeRequest(t, req, http.StatusOK) - // create the invite - session := loginUser(t, "user1") + req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{ + "user_name": "user5", + "password": "password", + }) + for _, c := range resp.Result().Cookies() { + req.AddCookie(c) + } - teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) - req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ - "uid": "1", - "uname": "doesnotexist@example.com", - }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) + resp = MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, inviteURL, test.RedirectURL(resp)) - // get the invite token - invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) - assert.NoError(t, err) - assert.Len(t, invites, 1) + // complete the login process + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} - // accept the invite - inviteURL := "/org/invite/" + invites[0].Token - req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) - resp = MakeRequest(t, req, http.StatusOK) + session = emptyTestSession(t) + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + session.jar.SetCookies(baseURL, cr.Cookies()) + inviteURL = test.RedirectURL(resp) + case "RedirectsExistingUserWithLogin": + // Test that a logged-in user who navigates to the sign-up link is then redirected using redirect_to + // For example: an invite may have been created before the user account was created, but they may be + // accepting the invite after having created an account separately + session = loginUser(t, "user5") - req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ - "user_name": "doesnotexist", - "email": "doesnotexist@example.com", - "password": "examplePassword!1", - "retype": "examplePassword!1", - }) - for _, c := range resp.Result().Cookies() { - req.AddCookie(c) + // accept the invite (note: this uses the sign_up url) + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, inviteURL, test.RedirectURL(resp)) + inviteURL = test.RedirectURL(resp) + } + + // join the team + req = NewRequest(t, "POST", inviteURL) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", test.RedirectURL(resp)) + session.MakeRequest(t, req, http.StatusOK) + + isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) + assert.NoError(t, err) + assert.True(t, isMember) + }) } - resp = MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, inviteURL, test.RedirectURL(resp)) + // Check that newly signed up users are redirected to accept the invitation correctly + t.Run("RedirectsNewUser", func(t *testing.T) { + defer prepare(t)() - // complete the signup process - ch := http.Header{} - ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) - cr := http.Request{Header: ch} + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - session = emptyTestSession(t) - baseURL, err := url.Parse(setting.AppURL) - assert.NoError(t, err) - session.jar.SetCookies(baseURL, cr.Cookies()) + // create the invite + session := loginUser(t, "user1") - // make the redirected request - req = NewRequest(t, "POST", test.RedirectURL(resp)) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ + "uid": "1", + "uname": "doesnotexist@example.com", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", test.RedirectURL(resp)) + session.MakeRequest(t, req, http.StatusOK) - // get the new user - newUser, err := user_model.GetUserByName(t.Context(), "doesnotexist") - assert.NoError(t, err) + // get the invite token + invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) + assert.NoError(t, err) + assert.Len(t, invites, 1) - isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, newUser.ID) - assert.NoError(t, err) - assert.True(t, isMember) -} - -// Check that users are redirected correctly after confirming their email -func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) { - // enable email confirmation temporarily - defer test.MockVariableValue(&setting.Service.RegisterEmailConfirm, true)() - defer tests.PrepareTestEnv(t)() - - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - - // user1: create the invite - session := loginUser(t, "user1") - - teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) - req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ - "uid": "1", - "uname": "doesnotexist@example.com", - }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - // get the invite token - invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) - assert.NoError(t, err) - assert.Len(t, invites, 1) - - // new user: accept the invite - session = emptyTestSession(t) - - inviteURL := "/org/invite/" + invites[0].Token - req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) - session.MakeRequest(t, req, http.StatusOK) - req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ - "user_name": "doesnotexist", - "email": "doesnotexist@example.com", - "password": "examplePassword!1", - "retype": "examplePassword!1", - }) - session.MakeRequest(t, req, http.StatusOK) - - user, err := user_model.GetUserByName(t.Context(), "doesnotexist") - assert.NoError(t, err) - - activationCode := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, user) - activateURL := "/user/activate?code=" + activationCode - req = NewRequestWithValues(t, "POST", activateURL, map[string]string{ - "password": "examplePassword!1", - }) - - resp = session.MakeRequest(t, req, http.StatusSeeOther) - // should be redirected to accept the invite - assert.Equal(t, inviteURL, test.RedirectURL(resp)) - - req = NewRequest(t, "POST", test.RedirectURL(resp)) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.True(t, isMember) -} - -// Test that a logged-in user who navigates to the sign-up link is then redirected using redirect_to -// For example: an invite may have been created before the user account was created, but they may be -// accepting the invite after having created an account separately -func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - - isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.False(t, isMember) - - // create the invite - session := loginUser(t, "user1") - - teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) - req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ - "uid": "1", - "uname": user.Email, - }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - // get the invite token - invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) - assert.NoError(t, err) - assert.Len(t, invites, 1) - - // note: the invited user has logged in - session = loginUser(t, "user5") - - // accept the invite (note: this uses the sign_up url) - inviteURL := "/org/invite/" + invites[0].Token - req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, inviteURL, test.RedirectURL(resp)) - - // make the request - req = NewRequest(t, "POST", test.RedirectURL(resp)) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - req = NewRequest(t, "GET", test.RedirectURL(resp)) - session.MakeRequest(t, req, http.StatusOK) - - isMember, err = organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) - assert.NoError(t, err) - assert.True(t, isMember) + // accept the invite + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) + resp = MakeRequest(t, req, http.StatusOK) + + req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ + "user_name": "doesnotexist", + "email": "doesnotexist@example.com", + "password": "examplePassword!1", + "retype": "examplePassword!1", + }) + for _, c := range resp.Result().Cookies() { + req.AddCookie(c) + } + + resp = MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, inviteURL, test.RedirectURL(resp)) + + // complete the signup process + ch := http.Header{} + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) + cr := http.Request{Header: ch} + + session = emptyTestSession(t) + baseURL, err := url.Parse(setting.AppURL) + assert.NoError(t, err) + session.jar.SetCookies(baseURL, cr.Cookies()) + + // make the redirected request + req = NewRequest(t, "POST", test.RedirectURL(resp)) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", test.RedirectURL(resp)) + session.MakeRequest(t, req, http.StatusOK) + + // get the new user + newUser, err := user_model.GetUserByName(t.Context(), "doesnotexist") + assert.NoError(t, err) + + isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, newUser.ID) + assert.NoError(t, err) + assert.True(t, isMember) + }) + + // Check that users are redirected correctly after confirming their email + t.Run("RedirectsNewUserWithActivation", func(t *testing.T) { + // enable email confirmation temporarily + defer test.MockVariableValue(&setting.Service.RegisterEmailConfirm, true)() + defer prepare(t)() + + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) + + // user1: create the invite + session := loginUser(t, "user1") + + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ + "uid": "1", + "uname": "doesnotexist@example.com", + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", test.RedirectURL(resp)) + session.MakeRequest(t, req, http.StatusOK) + + // get the invite token + invites, err := organization.GetInvitesByTeamID(t.Context(), team.ID) + assert.NoError(t, err) + assert.Len(t, invites, 1) + + // new user: accept the invite + session = emptyTestSession(t) + + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) + session.MakeRequest(t, req, http.StatusOK) + req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ + "user_name": "doesnotexist", + "email": "doesnotexist@example.com", + "password": "examplePassword!1", + "retype": "examplePassword!1", + }) + session.MakeRequest(t, req, http.StatusOK) + + user, err := user_model.GetUserByName(t.Context(), "doesnotexist") + assert.NoError(t, err) + + activationCode := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, user) + activateURL := "/user/activate?code=" + activationCode + req = NewRequestWithValues(t, "POST", activateURL, map[string]string{ + "password": "examplePassword!1", + }) + + resp = session.MakeRequest(t, req, http.StatusSeeOther) + // should be redirected to accept the invite + assert.Equal(t, inviteURL, test.RedirectURL(resp)) + + req = NewRequest(t, "POST", test.RedirectURL(resp)) + resp = session.MakeRequest(t, req, http.StatusSeeOther) + req = NewRequest(t, "GET", test.RedirectURL(resp)) + session.MakeRequest(t, req, http.StatusOK) + + isMember, err := organization.IsTeamMember(t.Context(), team.OrgID, team.ID, user.ID) + assert.NoError(t, err) + assert.True(t, isMember) + }) } diff --git a/tests/integration/packages_service_test.go b/tests/integration/packages_service_test.go index 172b5ab5e18..9a4bea45684 100644 --- a/tests/integration/packages_service_test.go +++ b/tests/integration/packages_service_test.go @@ -55,28 +55,21 @@ func TestRemovePackage(t *testing.T) { // Verify properties exist before deletion checkProps := func(p *packages_model.Package, pv *packages_model.PackageVersion, pf *packages_model.PackageFile, shouldExist bool) { - pps, err := packages_model.GetProperties(t.Context(), packages_model.PropertyTypePackage, p.ID) - require.NoError(t, err) - if shouldExist { - assert.NotEmpty(t, pps) - } else { - assert.Empty(t, pps) - } - - pps, err = packages_model.GetProperties(t.Context(), packages_model.PropertyTypeVersion, pv.ID) - require.NoError(t, err) - if shouldExist { - assert.NotEmpty(t, pps) - } else { - assert.Empty(t, pps) - } - - pps, err = packages_model.GetProperties(t.Context(), packages_model.PropertyTypeFile, pf.ID) - require.NoError(t, err) - if shouldExist { - assert.NotEmpty(t, pps) - } else { - assert.Empty(t, pps) + for _, property := range []struct { + refType packages_model.PropertyType + refID int64 + }{ + {packages_model.PropertyTypePackage, p.ID}, + {packages_model.PropertyTypeVersion, pv.ID}, + {packages_model.PropertyTypeFile, pf.ID}, + } { + pps, err := packages_model.GetProperties(t.Context(), property.refType, property.refID) + require.NoError(t, err) + if shouldExist { + assert.NotEmpty(t, pps) + } else { + assert.Empty(t, pps) + } } } diff --git a/tests/integration/privateactivity_test.go b/tests/integration/privateactivity_test.go index b394e54cc72..bd391029b17 100644 --- a/tests/integration/privateactivity_test.go +++ b/tests/integration/privateactivity_test.go @@ -134,282 +134,129 @@ func testPrivateActivityHelperHasHeatmapContentFromSession(t *testing.T, session return len(items) != 0 } -// check activity visibility if the visibility is enabled +func TestPrivateActivityVisibility(t *testing.T) { + for _, test := range []struct { + name string + private bool + user string + visible bool + }{ + // check activity visibility if the visibility is enabled + {"NoVisibleForPublic", false, "", true}, + {"NoVisibleForUserItself", false, privateActivityTestUser, true}, + {"NoVisibleForOtherUser", false, privateActivityTestOtherUser, true}, + {"NoVisibleForAdmin", false, privateActivityTestAdmin, true}, + // check activity visibility if the visibility is disabled + {"YesInvisibleForPublic", true, "", false}, + {"YesVisibleForUserItself", true, privateActivityTestUser, true}, + {"YesInvisibleForOtherUser", true, privateActivityTestOtherUser, false}, + {"YesVisibleForAdmin", true, privateActivityTestAdmin, true}, + } { + t.Run(test.name, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + if test.private { + testPrivateActivityHelperEnablePrivateActivity(t) + } -func TestPrivateActivityNoVisibleForPublic(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - visible := testPrivateActivityHelperHasVisibleActivitiesFromPublic(t) - - assert.True(t, visible, "user should have visible activities") + var visible bool + if test.user == "" { + visible = testPrivateActivityHelperHasVisibleActivitiesFromPublic(t) + } else { + visible = testPrivateActivityHelperHasVisibleActivitiesFromSession(t, loginUser(t, test.user)) + } + if test.visible { + assert.True(t, visible, "user should have visible activities") + } else { + assert.False(t, visible, "user should have no visible activities") + } + }) + } } -func TestPrivateActivityNoVisibleForUserItself(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) +func TestPrivateActivityHeatmapVisibility(t *testing.T) { + for _, test := range []struct { + name string + private bool + user string + dashboard bool + visible bool + }{ + // check heatmap visibility if the visibility is enabled + {"NoHeatmapVisibleForPublic", false, "", false, true}, + {"NoHeatmapVisibleForUserItselfAtProfile", false, privateActivityTestUser, false, true}, + {"NoHeatmapVisibleForUserItselfAtDashboard", false, privateActivityTestUser, true, true}, + {"NoHeatmapVisibleForOtherUser", false, privateActivityTestOtherUser, false, true}, + {"NoHeatmapVisibleForAdmin", false, privateActivityTestAdmin, false, true}, + // check heatmap visibility if the visibility is disabled + {"YesHeatmapInvisibleForPublic", true, "", false, false}, + {"YesHeatmapVisibleForUserItselfAtProfile", true, privateActivityTestUser, false, true}, + {"YesHeatmapVisibleForUserItselfAtDashboard", true, privateActivityTestUser, true, true}, + {"YesHeatmapInvisibleForOtherUser", true, privateActivityTestOtherUser, false, false}, + {"YesHeatmapVisibleForAdmin", true, privateActivityTestAdmin, false, true}, + } { + t.Run(test.name, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + if test.private { + testPrivateActivityHelperEnablePrivateActivity(t) + } - session := loginUser(t, privateActivityTestUser) - visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) - - assert.True(t, visible, "user should have visible activities") + var visible bool + if test.user == "" { + visible = testPrivateActivityHelperHasVisibleHeatmapFromPublic(t) + } else if test.dashboard { + visible = testPrivateActivityHelperHasVisibleDashboardHeatmapFromSession(t, loginUser(t, test.user)) + } else { + visible = testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, loginUser(t, test.user)) + } + if test.visible { + assert.True(t, visible, "user should have visible heatmap") + } else { + assert.False(t, visible, "user should have no visible heatmap") + } + }) + } } -func TestPrivateActivityNoVisibleForOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) +func TestPrivateActivityHeatmapContent(t *testing.T) { + for _, test := range []struct { + name string + private bool + user string + hasContent bool + message string + }{ + // check heatmap api provides content if the visibility is enabled + {"NoHeatmapHasContentForPublic", false, "", true, "user should have heatmap content"}, + {"NoHeatmapHasContentForUserItself", false, privateActivityTestUser, true, "user should have heatmap content"}, + {"NoHeatmapHasContentForOtherUser", false, privateActivityTestOtherUser, true, "user should have heatmap content"}, + {"NoHeatmapHasContentForAdmin", false, privateActivityTestAdmin, true, "user should have heatmap content"}, + // check heatmap api provides no content if the visibility is disabled + // this should be equal to the hidden heatmap at the UI + {"YesHeatmapHasNoContentForPublic", true, "", false, "user should have no heatmap content"}, + {"YesHeatmapHasNoContentForUserItself", true, privateActivityTestUser, true, "user should see their own heatmap content"}, + {"YesHeatmapHasNoContentForOtherUser", true, privateActivityTestOtherUser, false, "other user should not see heatmap content"}, + {"YesHeatmapHasNoContentForAdmin", true, privateActivityTestAdmin, true, "heatmap should show content for admin"}, + } { + t.Run(test.name, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testPrivateActivityDoSomethingForActionEntries(t) + if test.private { + testPrivateActivityHelperEnablePrivateActivity(t) + } - session := loginUser(t, privateActivityTestOtherUser) - visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) - - assert.True(t, visible, "user should have visible activities") -} - -func TestPrivateActivityNoVisibleForAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestAdmin) - visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) - - assert.True(t, visible, "user should have visible activities") -} - -// check activity visibility if the visibility is disabled - -func TestPrivateActivityYesInvisibleForPublic(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - visible := testPrivateActivityHelperHasVisibleActivitiesFromPublic(t) - - assert.False(t, visible, "user should have no visible activities") -} - -func TestPrivateActivityYesVisibleForUserItself(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestUser) - visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) - - assert.True(t, visible, "user should have visible activities") -} - -func TestPrivateActivityYesInvisibleForOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestOtherUser) - visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) - - assert.False(t, visible, "user should have no visible activities") -} - -func TestPrivateActivityYesVisibleForAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestAdmin) - visible := testPrivateActivityHelperHasVisibleActivitiesFromSession(t, session) - - assert.True(t, visible, "user should have visible activities") -} - -// check heatmap visibility if the visibility is enabled - -func TestPrivateActivityNoHeatmapVisibleForPublic(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - visible := testPrivateActivityHelperHasVisibleHeatmapFromPublic(t) - - assert.True(t, visible, "user should have visible heatmap") -} - -func TestPrivateActivityNoHeatmapVisibleForUserItselfAtProfile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestUser) - visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -func TestPrivateActivityNoHeatmapVisibleForUserItselfAtDashboard(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestUser) - visible := testPrivateActivityHelperHasVisibleDashboardHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -func TestPrivateActivityNoHeatmapVisibleForOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestOtherUser) - visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -func TestPrivateActivityNoHeatmapVisibleForAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestAdmin) - visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -// check heatmap visibility if the visibility is disabled - -func TestPrivateActivityYesHeatmapInvisibleForPublic(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - visible := testPrivateActivityHelperHasVisibleHeatmapFromPublic(t) - - assert.False(t, visible, "user should have no visible heatmap") -} - -func TestPrivateActivityYesHeatmapVisibleForUserItselfAtProfile(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestUser) - visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -func TestPrivateActivityYesHeatmapVisibleForUserItselfAtDashboard(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestUser) - visible := testPrivateActivityHelperHasVisibleDashboardHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -func TestPrivateActivityYesHeatmapInvisibleForOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestOtherUser) - visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) - - assert.False(t, visible, "user should have no visible heatmap") -} - -func TestPrivateActivityYesHeatmapVisibleForAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestAdmin) - visible := testPrivateActivityHelperHasVisibleProfileHeatmapFromSession(t, session) - - assert.True(t, visible, "user should have visible heatmap") -} - -// check heatmap api provides content if the visibility is enabled - -func TestPrivateActivityNoHeatmapHasContentForPublic(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - hasContent := testPrivateActivityHelperHasHeatmapContentFromPublic(t) - - assert.True(t, hasContent, "user should have heatmap content") -} - -func TestPrivateActivityNoHeatmapHasContentForUserItself(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestUser) - hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) - - assert.True(t, hasContent, "user should have heatmap content") -} - -func TestPrivateActivityNoHeatmapHasContentForOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestOtherUser) - hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) - - assert.True(t, hasContent, "user should have heatmap content") -} - -func TestPrivateActivityNoHeatmapHasContentForAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - - session := loginUser(t, privateActivityTestAdmin) - hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) - - assert.True(t, hasContent, "user should have heatmap content") -} - -// check heatmap api provides no content if the visibility is disabled -// this should be equal to the hidden heatmap at the UI - -func TestPrivateActivityYesHeatmapHasNoContentForPublic(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - hasContent := testPrivateActivityHelperHasHeatmapContentFromPublic(t) - - assert.False(t, hasContent, "user should have no heatmap content") -} - -func TestPrivateActivityYesHeatmapHasNoContentForUserItself(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestUser) - hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) - - assert.True(t, hasContent, "user should see their own heatmap content") -} - -func TestPrivateActivityYesHeatmapHasNoContentForOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestOtherUser) - hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) - - assert.False(t, hasContent, "other user should not see heatmap content") -} - -func TestPrivateActivityYesHeatmapHasNoContentForAdmin(t *testing.T) { - defer tests.PrepareTestEnv(t)() - testPrivateActivityDoSomethingForActionEntries(t) - testPrivateActivityHelperEnablePrivateActivity(t) - - session := loginUser(t, privateActivityTestAdmin) - hasContent := testPrivateActivityHelperHasHeatmapContentFromSession(t, session) - - assert.True(t, hasContent, "heatmap should show content for admin") + var hasContent bool + if test.user == "" { + hasContent = testPrivateActivityHelperHasHeatmapContentFromPublic(t) + } else { + hasContent = testPrivateActivityHelperHasHeatmapContentFromSession(t, loginUser(t, test.user)) + } + if test.hasContent { + assert.True(t, hasContent, test.message) + } else { + assert.False(t, hasContent, test.message) + } + }) + } } diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 1bc56a15dc3..6123f41ffb8 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -253,54 +253,38 @@ func TestRepoProjectFilterByMilestone(t *testing.T) { sess := loginUser(t, "user2") - t.Run("NoFilter", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/projects/1") - resp := sess.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - issueIDs := getProjectIssueIDs(t, htmlDoc) + for _, test := range []struct { + name string + url string + contains []int64 + notContains []int64 + milestones []string + }{ // All issues should be visible - assert.Contains(t, issueIDs, int64(1)) - assert.Contains(t, issueIDs, int64(2)) - assert.Contains(t, issueIDs, int64(3)) - assert.Contains(t, issueIDs, int64(5)) - assert.Equal(t, []string{"milestone1", "milestone3"}, getProjectMilestoneNames(htmlDoc)) - }) - - t.Run("FilterByMilestone", func(t *testing.T) { + {"NoFilter", "/user2/repo1/projects/1", []int64{1, 2, 3, 5}, nil, []string{"milestone1", "milestone3"}}, // milestone_id=1 is "milestone1" (open), only issue 2 has it - req := NewRequest(t, "GET", "/user2/repo1/projects/1?milestone=1") - resp := sess.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - issueIDs := getProjectIssueIDs(t, htmlDoc) - assert.Contains(t, issueIDs, int64(2)) - assert.NotContains(t, issueIDs, int64(1)) - assert.NotContains(t, issueIDs, int64(3)) - assert.NotContains(t, issueIDs, int64(5)) - }) - - t.Run("FilterByNoMilestone", func(t *testing.T) { + {"FilterByMilestone", "/user2/repo1/projects/1?milestone=1", []int64{2}, []int64{1, 3, 5}, nil}, // milestone=-1 means "no milestone", issues 1 and 5 have no milestone - req := NewRequest(t, "GET", "/user2/repo1/projects/1?milestone=-1") - resp := sess.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - issueIDs := getProjectIssueIDs(t, htmlDoc) - assert.Contains(t, issueIDs, int64(1)) - assert.Contains(t, issueIDs, int64(5)) - assert.NotContains(t, issueIDs, int64(2)) - assert.NotContains(t, issueIDs, int64(3)) - }) - - t.Run("FilterByClosedMilestone", func(t *testing.T) { + {"FilterByNoMilestone", "/user2/repo1/projects/1?milestone=-1", []int64{1, 5}, []int64{2, 3}, nil}, // milestone_id=3 is "milestone3" (closed), only issue 3 has it - req := NewRequest(t, "GET", "/user2/repo1/projects/1?milestone=3") - resp := sess.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - issueIDs := getProjectIssueIDs(t, htmlDoc) - assert.Contains(t, issueIDs, int64(3)) - assert.NotContains(t, issueIDs, int64(1)) - assert.NotContains(t, issueIDs, int64(2)) - assert.NotContains(t, issueIDs, int64(5)) - }) + {"FilterByClosedMilestone", "/user2/repo1/projects/1?milestone=3", []int64{3}, []int64{1, 2, 5}, nil}, + } { + t.Run(test.name, func(t *testing.T) { + req := NewRequest(t, "GET", test.url) + resp := sess.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + issueIDs := getProjectIssueIDs(t, htmlDoc) + for _, issueID := range test.contains { + assert.Contains(t, issueIDs, issueID) + } + for _, issueID := range test.notContains { + assert.NotContains(t, issueIDs, issueID) + } + if test.milestones != nil { + assert.Equal(t, test.milestones, getProjectMilestoneNames(htmlDoc)) + } + }) + } } func TestOrgProjectFilterByMilestone(t *testing.T) { diff --git a/tests/integration/project_workflow_test.go b/tests/integration/project_workflow_test.go index bf013a5c775..57d40278e3a 100644 --- a/tests/integration/project_workflow_test.go +++ b/tests/integration/project_workflow_test.go @@ -57,42 +57,38 @@ func TestProjectWorkflowsWeb(t *testing.T) { session.MakeRequest(t, NewRequest(t, "POST", workflowLink+"/delete"), http.StatusOK) session.MakeRequest(t, NewRequest(t, "GET", workflowLink), http.StatusNotFound) -} + t.Run("Execution", func(t *testing.T) { + link := "/user2/repo1/projects/1" -func TestProjectWorkflowExecution(t *testing.T) { - defer tests.PrepareTestEnv(t)() + for _, opts := range []*api.CreateProjectWorkflowOption{{ + Event: "item_added_to_project", + Actions: api.ProjectWorkflowActions{ColumnID: 2, AddLabelIDs: []int64{1}}, + }, { + Event: "item_column_changed", + Actions: api.ProjectWorkflowActions{IssueState: "close"}, + }} { + session.MakeRequest(t, NewRequestWithJSON(t, "POST", link+"/workflows", opts), http.StatusOK) + } - link := "/user2/repo1/projects/1" - session := loginUser(t, "user2") + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/issues", &api.CreateIssueOption{ + Title: "workflow item", + Projects: []int64{1}, + }).AddTokenAuth(token) + created := DecodeJSON(t, MakeRequest(t, req, http.StatusCreated), &api.Issue{}) - for _, opts := range []*api.CreateProjectWorkflowOption{{ - Event: "item_added_to_project", - Actions: api.ProjectWorkflowActions{ColumnID: 2, AddLabelIDs: []int64{1}}, - }, { - Event: "item_column_changed", - Actions: api.ProjectWorkflowActions{IssueState: "close"}, - }} { - session.MakeRequest(t, NewRequestWithJSON(t, "POST", link+"/workflows", opts), http.StatusOK) - } + assert.EqualValues(t, 2, unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: created.ID, ProjectID: 1}).ProjectColumnID) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: created.ID, LabelID: 1}) + assert.False(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: created.ID}).IsClosed) + resp := session.MakeRequest(t, NewRequestf(t, "GET", "/user2/repo1/issues/%d", created.Index), http.StatusOK) + assert.Contains(t, NewHTMLParser(t, resp.Body).Find(".timeline-item.event .comment-text-line").Text(), "Project workflow Item added to project") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) - req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/issues", &api.CreateIssueOption{ - Title: "workflow item", - Projects: []int64{1}, - }).AddTokenAuth(token) - created := DecodeJSON(t, MakeRequest(t, req, http.StatusCreated), &api.Issue{}) + req = NewRequestWithJSON(t, "POST", link+"/3/move", map[string]any{ + "issues": []map[string]any{{"issueID": created.ID, "sorting": 0}}, + }) + session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, 2, unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: created.ID, ProjectID: 1}).ProjectColumnID) - unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: created.ID, LabelID: 1}) - assert.False(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: created.ID}).IsClosed) - resp := session.MakeRequest(t, NewRequestf(t, "GET", "/user2/repo1/issues/%d", created.Index), http.StatusOK) - assert.Contains(t, NewHTMLParser(t, resp.Body).Find(".timeline-item.event .comment-text-line").Text(), "Project workflow Item added to project") - - req = NewRequestWithJSON(t, "POST", link+"/3/move", map[string]any{ - "issues": []map[string]any{{"issueID": created.ID, "sorting": 0}}, + assert.EqualValues(t, 3, unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: created.ID, ProjectID: 1}).ProjectColumnID) + assert.True(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: created.ID}).IsClosed) }) - session.MakeRequest(t, req, http.StatusOK) - - assert.EqualValues(t, 3, unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: created.ID, ProjectID: 1}).ProjectColumnID) - assert.True(t, unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: created.ID}).IsClosed) } diff --git a/tests/integration/pull_commit_test.go b/tests/integration/pull_commit_test.go deleted file mode 100644 index 938be5f0442..00000000000 --- a/tests/integration/pull_commit_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - pull_service "gitea.dev/services/pull" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestListPullCommits(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user5") - req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list") - resp := session.MakeRequest(t, req, http.StatusOK) - - pullCommitList := DecodeJSON(t, resp, &struct { - Commits []pull_service.CommitInfo `json:"commits"` - LastReviewCommitSha string `json:"last_review_commit_sha"` - }{}) - - require.Len(t, pullCommitList.Commits, 2) - assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", pullCommitList.Commits[0].ID) - assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", pullCommitList.Commits[1].ID) - assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha) - - t.Run("CommitBlobExcerpt", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - req = NewRequest(t, "GET", "/user2/repo1/blob_excerpt/985f0301dba5e7b34be866819cd15ad3d8f508ee?last_left=0&last_right=0&left=2&right=2&left_hunk_size=2&right_hunk_size=2&path=README.md&style=split&direction=up") - resp = session.MakeRequest(t, req, http.StatusOK) - assert.Contains(t, resp.Body.String(), `# repo1`+"\n"+``) - }) -} diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go index 69bb22d2a97..0d12c757da2 100644 --- a/tests/integration/pull_compare_test.go +++ b/tests/integration/pull_compare_test.go @@ -13,12 +13,39 @@ import ( repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" "gitea.dev/modules/test" + pull_service "gitea.dev/services/pull" repo_service "gitea.dev/services/repository" "gitea.dev/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestListPullCommits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user5") + req := NewRequest(t, "GET", "/user2/repo1/pulls/3/commits/list") + resp := session.MakeRequest(t, req, http.StatusOK) + + pullCommitList := DecodeJSON(t, resp, &struct { + Commits []pull_service.CommitInfo `json:"commits"` + LastReviewCommitSha string `json:"last_review_commit_sha"` + }{}) + + require.Len(t, pullCommitList.Commits, 2) + assert.Equal(t, "985f0301dba5e7b34be866819cd15ad3d8f508ee", pullCommitList.Commits[0].ID) + assert.Equal(t, "5c050d3b6d2db231ab1f64e324f1b6b9a0b181c2", pullCommitList.Commits[1].ID) + assert.Equal(t, "4a357436d925b5c974181ff12a994538ddc5a269", pullCommitList.LastReviewCommitSha) + + t.Run("CommitBlobExcerpt", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + req = NewRequest(t, "GET", "/user2/repo1/blob_excerpt/985f0301dba5e7b34be866819cd15ad3d8f508ee?last_left=0&last_right=0&left=2&right=2&left_hunk_size=2&right_hunk_size=2&path=README.md&style=split&direction=up") + resp = session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), `# repo1`+"\n"+``) + }) +} + func TestPullCompare(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index 4d905b3f0c0..8c78eed61ac 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -90,18 +90,20 @@ func TestPullView_CodeOwner(t *testing.T) { }) assert.NoError(t, err) + readmeBranchOptions := func(branch, content string) *files_service.ChangeRepoFilesOptions { + return &files_service.ChangeRepoFilesOptions{ + NewBranch: branch, + Files: []*files_service.ChangeRepoFile{{ + Operation: "update", + TreePath: "README.md", + ContentReader: strings.NewReader(content), + }}, + } + } + t.Run("First Pull Request", func(t *testing.T) { // create a new branch to prepare for pull request - _, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - NewBranch: "codeowner-basebranch", - Files: []*files_service.ChangeRepoFile{ - { - Operation: "update", - TreePath: "README.md", - ContentReader: strings.NewReader("# This is a new project\n"), - }, - }, - }) + _, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, readmeBranchOptions("codeowner-basebranch", "# This is a new project\n")) assert.NoError(t, err) // Create a pull request. @@ -131,17 +133,14 @@ func TestPullView_CodeOwner(t *testing.T) { }, 10*time.Second, 20*time.Millisecond, "code owner user8 was never requested for review") unittest.AssertCount(t, &issues_model.Review{IssueID: pr.IssueID, Type: issues_model.ReviewTypeRequest}, 2) // only user5 and user8 - err = issue_service.ChangeTitle(t.Context(), pr.Issue, user2, "[WIP] Test Pull Request") - assert.NoError(t, err) - prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) - assert.NoError(t, prUpdated1.LoadIssue(t.Context())) - assert.Equal(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title) - - err = issue_service.ChangeTitle(t.Context(), prUpdated1.Issue, user2, "Test Pull Request2") - assert.NoError(t, err) - prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) - assert.NoError(t, prUpdated2.LoadIssue(t.Context())) - assert.Equal(t, "Test Pull Request2", prUpdated2.Issue.Title) + prUpdated := pr + for _, title := range []string{"[WIP] Test Pull Request", "Test Pull Request2"} { + err = issue_service.ChangeTitle(t.Context(), prUpdated.Issue, user2, title) + assert.NoError(t, err) + prUpdated = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) + assert.NoError(t, prUpdated.LoadIssue(t.Context())) + assert.Equal(t, title, prUpdated.Issue.Title) + } // ensure it cannot be merged hasCodeownerReviews := issue_service.HasAllRequiredCodeownerReviews(t.Context(), &protectBranch, pr) @@ -175,16 +174,7 @@ func TestPullView_CodeOwner(t *testing.T) { }) t.Run("Dismissed Code Owner Review", func(t *testing.T) { - _, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - NewBranch: "codeowner-dismiss-branch", - Files: []*files_service.ChangeRepoFile{ - { - Operation: "update", - TreePath: "README.md", - ContentReader: strings.NewReader("# New README content\n"), - }, - }, - }) + _, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, readmeBranchOptions("codeowner-dismiss-branch", "# New README content\n")) assert.NoError(t, err) session := loginUser(t, "user2") @@ -230,16 +220,7 @@ func TestPullView_CodeOwner(t *testing.T) { t.Run("Second Pull Request", func(t *testing.T) { // create a new branch to prepare for pull request - resp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, &files_service.ChangeRepoFilesOptions{ - NewBranch: "codeowner-basebranch2", - Files: []*files_service.ChangeRepoFile{ - { - Operation: "update", - TreePath: "README.md", - ContentReader: strings.NewReader("# This is a new project2\n"), - }, - }, - }) + resp, err := files_service.ChangeRepoFiles(t.Context(), repo, user2, readmeBranchOptions("codeowner-basebranch2", "# This is a new project2\n")) assert.NoError(t, err) // Create a pull request. @@ -277,16 +258,7 @@ func TestPullView_CodeOwner(t *testing.T) { assert.NoError(t, err) // create a new branch to prepare for pull request - resp, err := files_service.ChangeRepoFiles(t.Context(), forkedRepo, user5, &files_service.ChangeRepoFilesOptions{ - NewBranch: "codeowner-basebranch-forked", - Files: []*files_service.ChangeRepoFile{ - { - Operation: "update", - TreePath: "README.md", - ContentReader: strings.NewReader("# This is a new forked project\n"), - }, - }, - }) + resp, err := files_service.ChangeRepoFiles(t.Context(), forkedRepo, user5, readmeBranchOptions("codeowner-basebranch-forked", "# This is a new forked project\n")) assert.NoError(t, err) session := loginUser(t, "user5") diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index a64136a32f8..bba35d334e5 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -80,30 +80,16 @@ func TestViewReleasesNoLogin(t *testing.T) { } func TestCreateRelease(t *testing.T) { - defer tests.PrepareTestEnv(t)() + for _, releaseType := range []string{"stable", "prerelease", "draft"} { + t.Run(releaseType, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) + session := loginUser(t, "user2") + createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", releaseType == "prerelease", releaseType == "draft") - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release.stable"), 4) -} - -func TestCreateReleasePreRelease(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) - - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release.prerelease"), 4) -} - -func TestCreateReleaseDraft(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) - - checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release.draft"), 4) + checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").TrString("repo.release."+releaseType), 4) + }) + } } func TestCreateReleasePaging(t *testing.T) { diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go index a3bfaff0b28..e6bd11d850c 100644 --- a/tests/integration/repo_activity_test.go +++ b/tests/integration/repo_activity_test.go @@ -51,21 +51,18 @@ func TestRepoActivity(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - // Should be 1 published release - list := htmlDoc.doc.Find("#published-releases").Next().Find("p.desc") - assert.Len(t, list.Nodes, 1) - - // Should be 1 merged pull request - list = htmlDoc.doc.Find("#merged-pull-requests").Next().Find("p.desc") - assert.Len(t, list.Nodes, 1) - - // Should be 2 proposed pull requests - list = htmlDoc.doc.Find("#proposed-pull-requests").Next().Find("p.desc") - assert.Len(t, list.Nodes, 2) - - // Should be 3 new issues - list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") - assert.Len(t, list.Nodes, 3) + for _, section := range []struct { + selector string + count int + }{ + {"#published-releases", 1}, // Should be 1 published release + {"#merged-pull-requests", 1}, // Should be 1 merged pull request + {"#proposed-pull-requests", 2}, // Should be 2 proposed pull requests + {"#new-issues", 3}, // Should be 3 new issues + } { + list := htmlDoc.doc.Find(section.selector).Next().Find("p.desc") + assert.Len(t, list.Nodes, section.count) + } // Non-existing default branch repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 31f78598db8..f95fc7978b6 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -165,8 +165,13 @@ func TestRepoCommitsWithStatus(t *testing.T) { assert.True(t, sel.HasClass(class)) } - testRepoCommitsWithStatus := func(t *testing.T, linkList, linkCombined string, state commitstatus.CommitStatusState) { - statuses, status := requestCommitStatuses(t, linkList, linkCombined) + for _, ref := range []string{ + commitID, // By SHA + commitID[:7], // By short SHA + "master", // By Ref + "v1.1", // Tag "v1.1" points to master + } { + statuses, status := requestCommitStatuses(t, "/api/v1/repos/user2/repo1/commits/"+ref+"/statuses", "/api/v1/repos/user2/repo1/commits/"+ref+"/status") require.Len(t, statuses, 1) require.NotNil(t, status) @@ -180,14 +185,6 @@ func TestRepoCommitsWithStatus(t *testing.T) { assert.Equal(t, statuses[0], status.Statuses[0]) assert.Equal(t, commitID, status.SHA) } - // By SHA - testRepoCommitsWithStatus(t, "/api/v1/repos/user2/repo1/commits/"+commitID+"/statuses", "/api/v1/repos/user2/repo1/commits/"+commitID+"/status", state) - // By short SHA - testRepoCommitsWithStatus(t, "/api/v1/repos/user2/repo1/commits/"+commitID[:7]+"/statuses", "/api/v1/repos/user2/repo1/commits/"+commitID[:7]+"/status", state) - // By Ref - testRepoCommitsWithStatus(t, "/api/v1/repos/user2/repo1/commits/master/statuses", "/api/v1/repos/user2/repo1/commits/master/status", state) - // Tag "v1.1" points to master - testRepoCommitsWithStatus(t, "/api/v1/repos/user2/repo1/commits/v1.1/statuses", "/api/v1/repos/user2/repo1/commits/v1.1/status", state) } t.Run("pending", func(t *testing.T) { testRefMaster(t, "pending", "octicon-dot-fill", "tw-text-yellow") }) @@ -209,57 +206,46 @@ func TestRepoCommitsWithStatus(t *testing.T) { }) } -func TestRepoCommitsStatusParallel(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func TestRepoCommitsStatus(t *testing.T) { + for _, name := range []string{"Parallel", "Multiple"} { + t.Run(name, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") - session := loginUser(t, "user2") + // Request repository commits page + req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") + resp := session.MakeRequest(t, req, http.StatusOK) - // Request repository commits page - req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") - resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + // Get first commit URL + commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href") + assert.True(t, exists) + assert.NotEmpty(t, commitURL) - doc := NewHTMLParser(t, resp.Body) - // Get first commit URL - commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href") - assert.True(t, exists) - assert.NotEmpty(t, commitURL) - - var wg sync.WaitGroup - for i := range 10 { - wg.Go(func() { - t.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) { + if name == "Parallel" { + var wg sync.WaitGroup + for i := range 10 { + wg.Go(func() { + t.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) { + ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) + doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusPending, "testci")(t) + }) + }) + } + wg.Wait() + } else { + // Call API to add status for commit ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) - doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusPending, "testci")(t) - }) + t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusSuccess, "testci")) + t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusSuccess, "other_context")) + req = NewRequest(t, "GET", "/user2/repo1/commits/branch/master") + resp = session.MakeRequest(t, req, http.StatusOK) + + doc = NewHTMLParser(t, resp.Body) + // Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present + sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`) + assert.Equal(t, 1, sel.Length()) + } }) } - wg.Wait() -} - -func TestRepoCommitsStatusMultiple(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - - // Request repository commits page - req := NewRequest(t, "GET", "/user2/repo1/commits/branch/master") - resp := session.MakeRequest(t, req, http.StatusOK) - - doc := NewHTMLParser(t, resp.Body) - // Get first commit URL - commitURL, exists := doc.doc.Find("#commits-table .commit-id-short").Attr("href") - assert.True(t, exists) - assert.NotEmpty(t, commitURL) - - // Call API to add status for commit - ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) - t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusSuccess, "testci")) - t.Run("CreateStatus", doAPICreateCommitStatusTest(ctx, path.Base(commitURL), commitstatus.CommitStatusSuccess, "other_context")) - req = NewRequest(t, "GET", "/user2/repo1/commits/branch/master") - resp = session.MakeRequest(t, req, http.StatusOK) - - doc = NewHTMLParser(t, resp.Body) - // Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present - sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`) - assert.Equal(t, 1, sel.Length()) } diff --git a/tests/integration/repo_merge_upstream_test.go b/tests/integration/repo_merge_upstream_test.go index 4b6db52046a..59ffc6ab2cc 100644 --- a/tests/integration/repo_merge_upstream_test.go +++ b/tests/integration/repo_merge_upstream_test.go @@ -62,38 +62,32 @@ func TestRepoMergeUpstream(t *testing.T) { require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "new-file.txt", "master", "test-content-1")) var mergeUpstreamLink string - t.Run("DetectDefaultBranch", func(t *testing.T) { + for _, test := range []struct{ name, branch string }{ // the repo shows a prompt to "sync fork" (defaults to the default branch) - require.Eventually(t, func() bool { - resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) - if mergeUpstreamLink == "" { - return false + {"DetectDefaultBranch", "master"}, + {"DetectSameBranch", "fork-branch"}, + } { + t.Run(test.name, func(t *testing.T) { + if test.branch == "fork-branch" { + // if the fork-branch name also exists in the base repo, then use that branch instead + req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/_new/branch/master", map[string]string{ + "new_branch_name": "fork-branch", + }) + sessionBaseUser.MakeRequest(t, req, http.StatusSeeOther) } - respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() - return strings.Contains(respMsg, `This branch is 1 commit behind user2/repo1:master`) - }, 5*time.Second, 100*time.Millisecond) - }) - t.Run("DetectSameBranch", func(t *testing.T) { - // if the fork-branch name also exists in the base repo, then use that branch instead - req = NewRequestWithValues(t, "POST", "/user2/repo1/branches/_new/branch/master", map[string]string{ - "new_branch_name": "fork-branch", + require.Eventually(t, func() bool { + resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) + if mergeUpstreamLink == "" { + return false + } + respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() + return strings.Contains(respMsg, fmt.Sprintf(`This branch is 1 commit behind user2/repo1:%s`, test.branch, test.branch)) + }, 5*time.Second, 100*time.Millisecond) }) - sessionBaseUser.MakeRequest(t, req, http.StatusSeeOther) - - require.Eventually(t, func() bool { - resp := session.MakeRequest(t, NewRequestf(t, "GET", "/%s/test-repo-fork/src/branch/fork-branch", forkUser.Name), http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - mergeUpstreamLink = queryMergeUpstreamButtonLink(htmlDoc) - if mergeUpstreamLink == "" { - return false - } - respMsg, _ := htmlDoc.Find(".ui.message:not(.positive)").Html() - return strings.Contains(respMsg, `This branch is 1 commit behind user2/repo1:fork-branch`) - }, 5*time.Second, 100*time.Millisecond) - }) + } // click the "sync fork" button req = NewRequest(t, "POST", mergeUpstreamLink) diff --git a/tests/integration/repo_topic_test.go b/tests/integration/repo_topic_test.go index 989416d6879..5a4257ac25c 100644 --- a/tests/integration/repo_topic_test.go +++ b/tests/integration/repo_topic_test.go @@ -31,59 +31,32 @@ func TestTopicSearch(t *testing.T) { defer tests.PrepareTestEnv(t)() searchURL, _ := url.Parse("/explore/topics/search") - // search all topics - res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics := DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 6) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // pagination search topics - topics.TopicNames = nil - query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} - - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 4) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // second page - topics.TopicNames = nil - query = url.Values{"page": []string{"2"}, "limit": []string{"4"}} - - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 2) - assert.Equal(t, "6", res.Header().Get("x-total-count")) - - // add keyword search - topics.TopicNames = nil - query = url.Values{"page": []string{"1"}, "limit": []string{"4"}} - query.Add("q", "topic") - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - assert.Len(t, topics.TopicNames, 2) - - topics.TopicNames = nil - query.Set("q", "database") - searchURL.RawQuery = query.Encode() - res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) - topics = DecodeJSON(t, res, &struct { - TopicNames []*api.TopicResponse `json:"topics"` - }{}) - if assert.Len(t, topics.TopicNames, 1) { - assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.Equal(t, "database", topics.TopicNames[0].Name) - assert.Equal(t, 1, topics.TopicNames[0].RepoCount) + for _, testCase := range []struct { + query url.Values + count int + }{ + // search all topics + {nil, 6}, + // pagination search topics + {url.Values{"page": {"1"}, "limit": {"4"}}, 4}, + // second page + {url.Values{"page": {"2"}, "limit": {"4"}}, 2}, + // add keyword search + {url.Values{"page": {"1"}, "limit": {"4"}, "q": {"topic"}}, 2}, + {url.Values{"page": {"1"}, "limit": {"4"}, "q": {"database"}}, 1}, + } { + searchURL.RawQuery = testCase.query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + topics := DecodeJSON(t, res, &struct { + TopicNames []*api.TopicResponse `json:"topics"` + }{}) + if assert.Len(t, topics.TopicNames, testCase.count) && testCase.query.Get("q") == "database" { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) + } + if testCase.query.Get("q") == "" { + assert.Equal(t, "6", res.Header().Get("x-total-count")) + } } } diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index d9dae4b4fd2..be303019a4e 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -383,112 +383,78 @@ func Test_WebhookPush(t *testing.T) { }) } -func Test_WebhookPushDevBranch(t *testing.T) { - var payloads []api.PushPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.PushPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "push" - }, http.StatusOK) - defer provider.Close() +func Test_WebhookPushBranches(t *testing.T) { + for _, testCase := range []struct { + branch string + baseBranch string + createBranch string + content string + }{ + {"develop", "develop", "", "# a test file for webhook push"}, + {"new_branch", "master", "new_branch", "# a new push from new branch"}, + } { + t.Run(testCase.branch, func(t *testing.T) { + var payloads []api.PushPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PushPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "push" + }, http.StatusOK) + defer provider.Close() - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - // 1. create a new webhook with special webhook for repo1 - session := loginUser(t, "user2") + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") - // only for dev branch - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "develop") + // only for dev branch + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", testCase.branch) - // 2. this should not trigger the webhook - testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") - assert.Empty(t, triggeredEvent) - assert.Empty(t, payloads) + if testCase.createBranch == "" { + // 2. this should not trigger the webhook + testCreateFile(t, session, "user2", "repo1", "master", "", "test_webhook_push.md", "# a test file for webhook push") + assert.Empty(t, triggeredEvent) + assert.Empty(t, payloads) + } - repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) - gitRepo, err := git.OpenRepository(t.Context(), repo1) - assert.NoError(t, err) - defer gitRepo.Close() + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + gitRepo, err := git.OpenRepository(t.Context(), repo1) + assert.NoError(t, err) + defer gitRepo.Close() - beforeCommitID, err := gitRepo.GetBranchCommitID(t.Context(), "develop") - assert.NoError(t, err) + beforeCommitID, err := gitRepo.GetBranchCommitID(t.Context(), testCase.baseBranch) + assert.NoError(t, err) - // 3. trigger the webhook - testCreateFile(t, session, "user2", "repo1", "develop", "", "test_webhook_push.md", "# a test file for webhook push") + // 3. trigger the webhook + testCreateFile(t, session, "user2", "repo1", testCase.baseBranch, testCase.createBranch, "test_webhook_push.md", testCase.content) - afterCommitID, err := gitRepo.GetBranchCommitID(t.Context(), "develop") - assert.NoError(t, err) + afterCommitID, err := gitRepo.GetBranchCommitID(t.Context(), testCase.branch) + assert.NoError(t, err) + expectedBefore := beforeCommitID + if testCase.createBranch != "" { + expectedBefore = git.Sha1ObjectFormat.EmptyObjectID().String() + } - // 4. validate the webhook is triggered - assert.Equal(t, "push", triggeredEvent) - assert.Len(t, payloads, 1) - assert.Equal(t, "refs/heads/develop", payloads[0].Ref) - assert.Equal(t, beforeCommitID, payloads[0].Before) - assert.Equal(t, afterCommitID, payloads[0].After) - assert.Equal(t, "repo1", payloads[0].Repo.Name) - assert.Equal(t, "develop", payloads[0].Branch()) - assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) - assert.Len(t, payloads[0].Commits, 1) - assert.Equal(t, afterCommitID, payloads[0].Commits[0].ID) - assert.Equal(t, setting.AppURL+"user2/repo1/compare/"+beforeCommitID+"..."+afterCommitID, payloads[0].CompareURL) - assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) - assert.Empty(t, payloads[0].Commits[0].Removed) - }) -} - -func Test_WebhookPushToNewBranch(t *testing.T) { - var payloads []api.PushPayload - var triggeredEvent string - provider := newMockWebhookProvider(func(r *http.Request) { - content, _ := io.ReadAll(r.Body) - var payload api.PushPayload - err := json.Unmarshal(content, &payload) - assert.NoError(t, err) - payloads = append(payloads, payload) - triggeredEvent = "push" - }, http.StatusOK) - defer provider.Close() - - onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - // 1. create a new webhook with special webhook for repo1 - session := loginUser(t, "user2") - - // only for dev branch - testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "push", "new_branch") - - repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) - gitRepo, err := git.OpenRepository(t.Context(), repo1) - assert.NoError(t, err) - defer gitRepo.Close() - - beforeCommitID, err := gitRepo.GetBranchCommitID(t.Context(), "master") - assert.NoError(t, err) - - // 2. trigger the webhook - testCreateFile(t, session, "user2", "repo1", "master", "new_branch", "test_webhook_push.md", "# a new push from new branch") - - afterCommitID, err := gitRepo.GetBranchCommitID(t.Context(), "new_branch") - assert.NoError(t, err) - emptyCommitID := git.Sha1ObjectFormat.EmptyObjectID().String() - - // 4. validate the webhook is triggered - assert.Equal(t, "push", triggeredEvent) - assert.Len(t, payloads, 1) - assert.Equal(t, "refs/heads/new_branch", payloads[0].Ref) - assert.Equal(t, emptyCommitID, payloads[0].Before) - assert.Equal(t, afterCommitID, payloads[0].After) - assert.Equal(t, "repo1", payloads[0].Repo.Name) - assert.Equal(t, "new_branch", payloads[0].Branch()) - assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) - assert.Len(t, payloads[0].Commits, 1) - assert.Equal(t, afterCommitID, payloads[0].Commits[0].ID) - assert.Equal(t, setting.AppURL+"user2/repo1/compare/"+beforeCommitID+"..."+afterCommitID, payloads[0].CompareURL) - assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) - assert.Empty(t, payloads[0].Commits[0].Removed) - }) + // 4. validate the webhook is triggered + assert.Equal(t, "push", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, "refs/heads/"+testCase.branch, payloads[0].Ref) + assert.Equal(t, expectedBefore, payloads[0].Before) + assert.Equal(t, afterCommitID, payloads[0].After) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, testCase.branch, payloads[0].Branch()) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Len(t, payloads[0].Commits, 1) + assert.Equal(t, afterCommitID, payloads[0].Commits[0].ID) + assert.Equal(t, setting.AppURL+"user2/repo1/compare/"+beforeCommitID+"..."+afterCommitID, payloads[0].CompareURL) + assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) + assert.Empty(t, payloads[0].Commits[0].Removed) + }) + }) + } } func Test_WebhookIssue(t *testing.T) { @@ -615,56 +581,38 @@ func Test_WebhookIssueMilestone(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issue_milestone") - t.Run("assign a milestone", func(t *testing.T) { - // trigger the webhook - testIssueChangeMilestone(t, session, repo1.Link(), 1, 1) + for _, testCase := range []struct { + name string + milestoneID int + action string + }{ + {"assign a milestone", 1, "milestoned"}, + {"change a milestong", 2, "milestoned"}, + {"remove a milestone", 0, "demilestoned"}, + } { + t.Run(testCase.name, func(t *testing.T) { + if testCase.milestoneID != 1 { + // trigger the webhook again + triggeredEvent = "" + payloads = make([]api.IssuePayload, 0, 1) + } + testIssueChangeMilestone(t, session, repo1.Link(), 1, int64(testCase.milestoneID)) - // validate the webhook is triggered - assert.Equal(t, "issues", triggeredEvent) - assert.Len(t, payloads, 1) - assert.Equal(t, "milestoned", string(payloads[0].Action)) - assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) - assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) - assert.Equal(t, "issue1", payloads[0].Issue.Title) - assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) - assert.EqualValues(t, 1, payloads[0].Issue.Milestone.ID) - }) - - t.Run("change a milestong", func(t *testing.T) { - // trigger the webhook again - triggeredEvent = "" - payloads = make([]api.IssuePayload, 0, 1) - // change milestone to 2 - testIssueChangeMilestone(t, session, repo1.Link(), 1, 2) - - // validate the webhook is triggered - assert.Equal(t, "issues", triggeredEvent) - assert.Len(t, payloads, 1) - assert.Equal(t, "milestoned", string(payloads[0].Action)) - assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) - assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) - assert.Equal(t, "issue1", payloads[0].Issue.Title) - assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) - assert.EqualValues(t, 2, payloads[0].Issue.Milestone.ID) - }) - - t.Run("remove a milestone", func(t *testing.T) { - // trigger the webhook again - triggeredEvent = "" - payloads = make([]api.IssuePayload, 0, 1) - // change milestone to 0 - testIssueChangeMilestone(t, session, repo1.Link(), 1, 0) - - // validate the webhook is triggered - assert.Equal(t, "issues", triggeredEvent) - assert.Len(t, payloads, 1) - assert.Equal(t, "demilestoned", string(payloads[0].Action)) - assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) - assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) - assert.Equal(t, "issue1", payloads[0].Issue.Title) - assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) - assert.Nil(t, payloads[0].Issue.Milestone) - }) + // validate the webhook is triggered + assert.Equal(t, "issues", triggeredEvent) + assert.Len(t, payloads, 1) + assert.Equal(t, testCase.action, string(payloads[0].Action)) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "issue1", payloads[0].Issue.Title) + assert.Equal(t, "content for the first issue", payloads[0].Issue.Body) + if testCase.milestoneID == 0 { + assert.Nil(t, payloads[0].Issue.Milestone) + } else { + assert.EqualValues(t, testCase.milestoneID, payloads[0].Issue.Milestone.ID) + } + }) + } }) } diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index 0def14034f1..f3681e4014a 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -382,87 +382,73 @@ func TestChangeRepoFilesForCreate(t *testing.T) { } func TestChangeRepoFilesForUpdate(t *testing.T) { - // setup - onGiteaRun(t, func(t *testing.T, u *url.URL) { - ctx, _ := contexttest.MockContext(t, "user2/repo1") - ctx.SetPathParam("id", "1") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadGitRepo(t, ctx) - defer ctx.Repo.GitRepo.Close() + for _, name := range []string{"update", "with file move", "without branch names"} { + t.Run(name, func(t *testing.T) { + // setup + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx, _ := contexttest.MockContext(t, "user2/repo1") + ctx.SetPathParam("id", "1") + contexttest.LoadRepo(t, ctx, 1) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() - repo := ctx.Repo.Repository - doer := ctx.Doer - opts := getUpdateRepoFilesOptions(repo) + repo := ctx.Repo.Repository + doer := ctx.Doer + opts := getUpdateRepoFilesOptions(repo) + switch name { + case "with file move": + opts.Files[0].FromTreePath = "README.md" + opts.Files[0].TreePath = "README_new.md" // new file name, README_new.md + case "without branch names": + opts.OldBranch = "" + opts.NewBranch = "" + } - // test - filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) + // test + filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) - // asserts - assert.NoError(t, err) - gitRepo, _ := git.OpenRepository(ctx, repo) - defer gitRepo.Close() + // asserts + assert.NoError(t, err) + gitRepo, _ := git.OpenRepository(ctx, repo) + defer gitRepo.Close() - commit, _ := gitRepo.GetBranchCommit(t.Context(), opts.NewBranch) - lastCommit, _ := commit.GetCommitByPath(t.Context(), gitRepo, opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) - assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) - assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) - }) -} - -func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { - // setup - onGiteaRun(t, func(t *testing.T, u *url.URL) { - ctx, _ := contexttest.MockContext(t, "user2/repo1") - ctx.SetPathParam("id", "1") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadGitRepo(t, ctx) - defer ctx.Repo.GitRepo.Close() - - repo := ctx.Repo.Repository - doer := ctx.Doer - opts := getUpdateRepoFilesOptions(repo) - opts.Files[0].FromTreePath = "README.md" - opts.Files[0].TreePath = "README_new.md" // new file name, README_new.md - - // test - filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) - - // asserts - assert.NoError(t, err) - gitRepo, _ := git.OpenRepository(ctx, repo) - defer gitRepo.Close() - - commit, _ := gitRepo.GetBranchCommit(t.Context(), opts.NewBranch) - lastCommit, _ := commit.GetCommitByPath(t.Context(), gitRepo, opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) - // assert that the old file no longer exists in the last commit of the branch - fromEntry, err := commit.GetTreeEntryByPath(ctx, gitRepo, opts.Files[0].FromTreePath) - switch err.(type) { - case git.ErrNotExist: - // correct, continue - default: - t.Fatalf("expected git.ErrNotExist, got:%v", err) - } - toEntry, err := commit.GetTreeEntryByPath(ctx, gitRepo, opts.Files[0].TreePath) - assert.NoError(t, err) - assert.Nil(t, fromEntry) // Should no longer exist here - assert.NotNil(t, toEntry) // Should exist here - // assert SHA has remained the same but paths use the new file name - assert.Equal(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA) - assert.Equal(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name) - assert.Equal(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path) - assert.Equal(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL) - assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - }) + commit, _ := gitRepo.GetBranchCommit(t.Context(), repo.DefaultBranch) + lastCommit, _ := commit.GetCommitByPath(t.Context(), gitRepo, opts.Files[0].TreePath) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) + if name == "with file move" { + // assert that the old file no longer exists in the last commit of the branch + fromEntry, err := commit.GetTreeEntryByPath(ctx, gitRepo, opts.Files[0].FromTreePath) + switch err.(type) { + case git.ErrNotExist: + // correct, continue + default: + t.Fatalf("expected git.ErrNotExist, got:%v", err) + } + toEntry, err := commit.GetTreeEntryByPath(ctx, gitRepo, opts.Files[0].TreePath) + assert.NoError(t, err) + assert.Nil(t, fromEntry) // Should no longer exist here + assert.NotNil(t, toEntry) // Should exist here + // assert SHA has remained the same but paths use the new file name + assert.Equal(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name) + assert.Equal(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path) + assert.Equal(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL) + } else { + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) + } + if name != "without branch names" { + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + if name == "update" { + assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + } + } + }) + }) + } } func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) { @@ -497,39 +483,6 @@ func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) { }) } -// Test opts with branch names removed, should get same results as above test -func TestChangeRepoFilesWithoutBranchNames(t *testing.T) { - // setup - onGiteaRun(t, func(t *testing.T, u *url.URL) { - ctx, _ := contexttest.MockContext(t, "user2/repo1") - ctx.SetPathParam("id", "1") - contexttest.LoadRepo(t, ctx, 1) - contexttest.LoadRepoCommit(t, ctx) - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadGitRepo(t, ctx) - defer ctx.Repo.GitRepo.Close() - - repo := ctx.Repo.Repository - doer := ctx.Doer - opts := getUpdateRepoFilesOptions(repo) - opts.OldBranch = "" - opts.NewBranch = "" - - // test - filesResponse, err := files_service.ChangeRepoFiles(t.Context(), repo, doer, opts) - - // asserts - assert.NoError(t, err) - gitRepo, _ := git.OpenRepository(ctx, repo) - defer gitRepo.Close() - - commit, _ := gitRepo.GetBranchCommit(t.Context(), repo.DefaultBranch) - lastCommit, _ := commit.GetCommitByPath(t.Context(), gitRepo, opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) - assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) - }) -} - func TestChangeRepoFilesForDelete(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { ctx, _ := contexttest.MockContext(t, "user2/repo1") diff --git a/tests/integration/setting_test.go b/tests/integration/setting_test.go index b6e72488d12..ccce76ca559 100644 --- a/tests/integration/setting_test.go +++ b/tests/integration/setting_test.go @@ -45,44 +45,37 @@ func TestSettingShowUserEmailProfile(t *testing.T) { // user1: keep_email_private = false, user2: keep_email_private = true - setting.UI.ShowUserEmail = true - - // user1 can see own visible email - session := loginUser(t, "user1") - req := NewRequest(t, "GET", "/user1") - resp := session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - assert.Contains(t, htmlDoc.doc.Find(".user.profile").Text(), "user1@example.com") - - // user1 can not see user2's hidden email - req = NewRequest(t, "GET", "/user2") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - // Should only contain if the user visits their own profile page - assert.NotContains(t, htmlDoc.doc.Find(".user.profile").Text(), "user2@example.com") - - // user2 can see user1's visible email - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/user1") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - assert.Contains(t, htmlDoc.doc.Find(".user.profile").Text(), "user1@example.com") - - // user2 can see own hidden email - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/user2") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - assert.Contains(t, htmlDoc.doc.Find(".user.profile").Text(), "user2@example.com") - - setting.UI.ShowUserEmail = false - - // user1 can see own (now hidden) email - session = loginUser(t, "user1") - req = NewRequest(t, "GET", "/user1") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc = NewHTMLParser(t, resp.Body) - assert.Contains(t, htmlDoc.doc.Find(".user.profile").Text(), "user1@example.com") + var session *TestSession + for _, profile := range []struct { + login, name string + showEmail bool + visible bool + }{ + // user1 can see own visible email + {"user1", "user1", true, true}, + // user1 can not see user2's hidden email + // Should only contain if the user visits their own profile page + {"", "user2", true, false}, + // user2 can see user1's visible email + {"user2", "user1", true, true}, + // user2 can see own hidden email + {"user2", "user2", true, true}, + // user1 can see own (now hidden) email + {"user1", "user1", false, true}, + } { + setting.UI.ShowUserEmail = profile.showEmail + if profile.login != "" { + session = loginUser(t, profile.login) + } + req := NewRequest(t, "GET", "/"+profile.name) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + if profile.visible { + assert.Contains(t, htmlDoc.doc.Find(".user.profile").Text(), profile.name+"@example.com") + } else { + assert.NotContains(t, htmlDoc.doc.Find(".user.profile").Text(), profile.name+"@example.com") + } + } setting.UI.ShowUserEmail = showUserEmail } diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index 695fcab6e5b..dd7f3f4c2b9 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -6,6 +6,7 @@ package integration import ( "net/http" "net/url" + "strconv" "strings" "testing" @@ -110,65 +111,43 @@ func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) { _ = auth.Oauth2SetLinkAccountData(ctx, auth.LinkAccountData{AuthSourceID: authSource.ID, GothUser: gothUser}) } - t.Run("EnablePasswordSignInForm=false", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, false)() + for _, enabled := range []bool{false, true} { + t.Run("EnablePasswordSignInForm="+strconv.FormatBool(enabled), func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, enabled)() - req := NewRequest(t, "GET", "/user/login") - resp := MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - AssertHTMLElement(t, doc, "form[action='/user/login']", false) + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, "form[action='/user/login']", enabled) - req = NewRequest(t, "POST", "/user/login") - MakeRequest(t, req, http.StatusForbidden) + postStatus := http.StatusForbidden + if enabled { + postStatus = http.StatusOK + } + req = NewRequest(t, "POST", "/user/login") + MakeRequest(t, req, postStatus) - req = NewRequest(t, "GET", "/user/link_account") - defer web.RouteMockReset() - web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) - resp = MakeRequest(t, req, http.StatusOK) - doc = NewHTMLParser(t, resp.Body) - AssertHTMLElement(t, doc, "form[action='/user/link_account_signin']", false) - }) + req = NewRequest(t, "GET", "/user/link_account") + defer web.RouteMockReset() + web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) + resp = MakeRequest(t, req, http.StatusOK) + doc = NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, "form[action='/user/link_account_signin']", enabled) + }) + } - t.Run("EnablePasswordSignInForm=true", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, true)() + for _, enabled := range []bool{false, true} { + t.Run("EnablePasskeyAuth="+strconv.FormatBool(enabled), func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, enabled)() - req := NewRequest(t, "GET", "/user/login") - resp := MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - AssertHTMLElement(t, doc, "form[action='/user/login']", true) - - req = NewRequest(t, "POST", "/user/login") - MakeRequest(t, req, http.StatusOK) - - req = NewRequest(t, "GET", "/user/link_account") - defer web.RouteMockReset() - web.RouteMock(web.MockAfterMiddlewares, mockLinkAccount) - resp = MakeRequest(t, req, http.StatusOK) - doc = NewHTMLParser(t, resp.Body) - AssertHTMLElement(t, doc, "form[action='/user/link_account_signin']", true) - }) - - t.Run("EnablePasskeyAuth=false", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, false)() - - req := NewRequest(t, "GET", "/user/login") - resp := MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - AssertHTMLElement(t, doc, ".signin-passkey", false) - }) - - t.Run("EnablePasskeyAuth=true", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - defer test.MockVariableValue(&setting.Service.EnablePasskeyAuth, true)() - - req := NewRequest(t, "GET", "/user/login") - resp := MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - AssertHTMLElement(t, doc, ".signin-passkey", true) - }) + req := NewRequest(t, "GET", "/user/login") + resp := MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) + AssertHTMLElement(t, doc, ".signin-passkey", enabled) + }) + } } func TestRequireSignInView(t *testing.T) { @@ -211,3 +190,33 @@ func TestRequireSignInView(t *testing.T) { assert.Equal(t, "/user/login?redirect_to=%2Fuser2%2Frepo1%2Fsrc%2Fbranch%2Fmaster", resp.Header().Get("Location")) }) } + +func TestSignOut(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + t.Run("NormalLogout", func(t *testing.T) { + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/", resp.Header().Get("Location")) + + // logged out, try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("ReverseProxyLogoutRedirect", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.EnableReverseProxyAuth, true)() + defer test.MockVariableValue(&setting.ReverseProxyLogoutRedirect, "/my-sso/logout?return_to=/my-sso/home")() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/logout") + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/my-sso/logout?return_to=/my-sso/home", resp.Header().Get("Location")) + + // logged out, try to view a private repo, should fail + req = NewRequest(t, "GET", "/user2/repo2") + session.MakeRequest(t, req, http.StatusNotFound) + }) +} diff --git a/tests/integration/signout_test.go b/tests/integration/signout_test.go deleted file mode 100644 index fcaf30da075..00000000000 --- a/tests/integration/signout_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - "gitea.dev/modules/setting" - "gitea.dev/modules/test" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestSignOut(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - t.Run("NormalLogout", func(t *testing.T) { - session := loginUser(t, "user2") - - req := NewRequest(t, "GET", "/user/logout") - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/", resp.Header().Get("Location")) - - // logged out, try to view a private repo, should fail - req = NewRequest(t, "GET", "/user2/repo2") - session.MakeRequest(t, req, http.StatusNotFound) - }) - - t.Run("ReverseProxyLogoutRedirect", func(t *testing.T) { - defer test.MockVariableValue(&setting.Service.EnableReverseProxyAuth, true)() - defer test.MockVariableValue(&setting.ReverseProxyLogoutRedirect, "/my-sso/logout?return_to=/my-sso/home")() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/logout") - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/my-sso/logout?return_to=/my-sso/home", resp.Header().Get("Location")) - - // logged out, try to view a private repo, should fail - req = NewRequest(t, "GET", "/user2/repo2") - session.MakeRequest(t, req, http.StatusNotFound) - }) -} diff --git a/tests/integration/user_settings_oauth2_grant_test.go b/tests/integration/user_settings_oauth2_grant_test.go deleted file mode 100644 index c36be23e3b5..00000000000 --- a/tests/integration/user_settings_oauth2_grant_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - auth_model "gitea.dev/models/auth" - "gitea.dev/models/unittest" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestRevokeOAuth2GrantOfOtherUser(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // grant 1 belongs to user1, so user2 must not be able to act on it - session := loginUser(t, "user2") - req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/1/revoke/1", nil) - session.MakeRequest(t, req, http.StatusNotFound) - - grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1}) - assert.EqualValues(t, 1, grant.UserID) -} diff --git a/tests/integration/user_settings_test.go b/tests/integration/user_settings_test.go index e30904cc8a5..03e430e507c 100644 --- a/tests/integration/user_settings_test.go +++ b/tests/integration/user_settings_test.go @@ -56,58 +56,55 @@ func WithDisabledFeatures(t *testing.T, features ...string) { }) } +func TestRevokeOAuth2GrantOfOtherUser(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // grant 1 belongs to user1, so user2 must not be able to act on it + session := loginUser(t, "user2") + req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/1/revoke/1", nil) + session.MakeRequest(t, req, http.StatusNotFound) + + grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1}) + assert.EqualValues(t, 1, grant.UserID) +} + func TestUserSettingsAccount(t *testing.T) { defer tests.PrepareTestEnv(t)() - t.Run("all features enabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, testCase := range []struct { + name string + features []string + credentials bool + deleteSelector string + deletion bool + }{ + {"all features enabled", nil, true, `form[action="/user/settings/account/delete"]`, true}, + {"credentials disabled", []string{setting.UserFeatureManageCredentials}, false, `form[action="/user/settings/account/delete"]`, true}, + {"deletion disabled", []string{setting.UserFeatureDeletion}, true, "#delete-form", false}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + if len(testCase.features) > 0 { + WithDisabledFeatures(t, testCase.features...) + } - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/account") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/settings/account") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) - // account navbar should display - AssertHTMLElement(t, doc, ".menu a[href='/user/settings/account']", true) + if len(testCase.features) == 0 { + // account navbar should display + AssertHTMLElement(t, doc, ".menu a[href='/user/settings/account']", true) + } else { + assertNavbar(t, doc) + } - AssertHTMLElement(t, doc, "#password", true) - AssertHTMLElement(t, doc, "#email", true) - AssertHTMLElement(t, doc, `form[action="/user/settings/account/delete"]`, true) - }) - - t.Run("credentials disabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - WithDisabledFeatures(t, setting.UserFeatureManageCredentials) - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/account") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) - - AssertHTMLElement(t, doc, "#password", false) - AssertHTMLElement(t, doc, "#email", false) - AssertHTMLElement(t, doc, `form[action="/user/settings/account/delete"]`, true) - }) - - t.Run("deletion disabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - WithDisabledFeatures(t, setting.UserFeatureDeletion) - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/account") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) - - AssertHTMLElement(t, doc, "#password", true) - AssertHTMLElement(t, doc, "#email", true) - AssertHTMLElement(t, doc, "#delete-form", false) - }) + AssertHTMLElement(t, doc, "#password", testCase.credentials) + AssertHTMLElement(t, doc, "#email", testCase.credentials) + AssertHTMLElement(t, doc, testCase.deleteSelector, testCase.deletion) + }) + } t.Run("deletion, credentials and email notifications are disabled", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -163,33 +160,27 @@ func TestUserSettingsUpdateEmail(t *testing.T) { session.MakeRequest(t, req, http.StatusNotFound) }) - t.Run("primary email not found", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, testCase := range []struct { + name string + id string + }{ + {"primary email not found", "9999"}, + {"primary email not owned by user", "6"}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - session := loginUser(t, "user2") - req := NewRequestWithValues(t, "POST", "/user/settings/account/email", map[string]string{ - "_method": "PRIMARY", - "id": "9999", + session := loginUser(t, "user2") + req := NewRequestWithValues(t, "POST", "/user/settings/account/email", map[string]string{ + "_method": "PRIMARY", + "id": testCase.id, + }) + resp := session.MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/settings/account", resp.Header().Get("Location")) + flashMsg := session.GetCookieFlashMessage() + assert.Equal(t, "The selected email address could not be found.", flashMsg.ErrorMsg) }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user/settings/account", resp.Header().Get("Location")) - flashMsg := session.GetCookieFlashMessage() - assert.Equal(t, "The selected email address could not be found.", flashMsg.ErrorMsg) - }) - - t.Run("primary email not owned by user", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - session := loginUser(t, "user2") - req := NewRequestWithValues(t, "POST", "/user/settings/account/email", map[string]string{ - "_method": "PRIMARY", - "id": "6", - }) - resp := session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user/settings/account", resp.Header().Get("Location")) - flashMsg := session.GetCookieFlashMessage() - assert.Equal(t, "The selected email address could not be found.", flashMsg.ErrorMsg) - }) + } } func TestUserSettingsDeleteEmail(t *testing.T) { @@ -219,47 +210,46 @@ func TestUserSettingsDelete(t *testing.T) { }) } -func TestUserSettingsAppearance(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func TestUserSettingsPages(t *testing.T) { + for _, page := range []string{"appearance", "packages", "packages/rules/add", "organization", "repos", "blocked_users"} { + t.Run(page, func(t *testing.T) { + defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/appearance") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/settings/"+page) + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) - assertNavbar(t, doc) + assertNavbar(t, doc) + }) + } } func TestUserSettingsSecurity(t *testing.T) { defer tests.PrepareTestEnv(t)() - t.Run("credentials disabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - WithDisabledFeatures(t, setting.UserFeatureManageCredentials) + for _, testCase := range []struct { + name string + feature string + register bool + }{ + {"credentials disabled", setting.UserFeatureManageCredentials, true}, + {"mfa disabled", setting.UserFeatureManageMFA, false}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + WithDisabledFeatures(t, testCase.feature) - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/security") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/settings/security") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) - assertNavbar(t, doc) + assertNavbar(t, doc) - AssertHTMLElement(t, doc, "#register-webauthn", true) - }) - - t.Run("mfa disabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - WithDisabledFeatures(t, setting.UserFeatureManageMFA) - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/security") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) - - AssertHTMLElement(t, doc, "#register-webauthn", false) - }) + AssertHTMLElement(t, doc, "#register-webauthn", testCase.register) + }) + } t.Run("credentials and mfa disabled", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -339,19 +329,14 @@ func TestUserSettingsApplications(t *testing.T) { t.Run("OK", func(t *testing.T) { defer tests.PrintCurrentTest(t)() defer test.MockVariableValue(&setting.OAuth2.CustomSchemes, []string{"my-app"})() - req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{ - "application_name": "Test native app", - "redirect_uris": "http://127.0.0.1", - "confidential_client": "false", - }) - session.MakeRequest(t, req, http.StatusSeeOther) - - req = NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{ - "application_name": "Test native app", - "redirect_uris": "my-app://127.0.0.1", - "confidential_client": "false", - }) - session.MakeRequest(t, req, http.StatusSeeOther) + for _, redirectURI := range []string{"http://127.0.0.1", "my-app://127.0.0.1"} { + req := NewRequestWithValues(t, "POST", "/user/settings/applications/oauth2/2", map[string]string{ + "application_name": "Test native app", + "redirect_uris": redirectURI, + "confidential_client": "false", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + } }) }) }) @@ -360,51 +345,33 @@ func TestUserSettingsApplications(t *testing.T) { func TestUserSettingsKeys(t *testing.T) { defer tests.PrepareTestEnv(t)() - t.Run("all enabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + for _, testCase := range []struct { + name string + feature string + ssh bool + gpg bool + }{ + {"all enabled", "", true, true}, + {"ssh keys disabled", setting.UserFeatureManageSSHKeys, false, true}, + {"gpg keys disabled", setting.UserFeatureManageGPGKeys, true, false}, + } { + t.Run(testCase.name, func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + if testCase.feature != "" { + WithDisabledFeatures(t, testCase.feature) + } - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/keys") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user/settings/keys") + resp := session.MakeRequest(t, req, http.StatusOK) + doc := NewHTMLParser(t, resp.Body) - assertNavbar(t, doc) + assertNavbar(t, doc) - AssertHTMLElement(t, doc, "#add-ssh-button", true) - AssertHTMLElement(t, doc, "#add-gpg-key-panel", true) - }) - - t.Run("ssh keys disabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - WithDisabledFeatures(t, setting.UserFeatureManageSSHKeys) - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/keys") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) - - AssertHTMLElement(t, doc, "#add-ssh-button", false) - AssertHTMLElement(t, doc, "#add-gpg-key-panel", true) - }) - - t.Run("gpg keys disabled", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() - - WithDisabledFeatures(t, setting.UserFeatureManageGPGKeys) - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/keys") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) - - AssertHTMLElement(t, doc, "#add-ssh-button", true) - AssertHTMLElement(t, doc, "#add-gpg-key-panel", false) - }) + AssertHTMLElement(t, doc, "#add-ssh-button", testCase.ssh) + AssertHTMLElement(t, doc, "#add-gpg-key-panel", testCase.gpg) + }) + } t.Run("ssh & gpg keys disabled", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -431,58 +398,3 @@ func TestUserSettingsSecrets(t *testing.T) { session.MakeRequest(t, req, http.StatusNotFound) } } - -func TestUserSettingsPackages(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/packages") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) -} - -func TestUserSettingsPackagesRulesAdd(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/packages/rules/add") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) -} - -func TestUserSettingsOrganization(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/organization") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) -} - -func TestUserSettingsRepos(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/repos") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) -} - -func TestUserSettingsBlockedUsers(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user/settings/blocked_users") - resp := session.MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - - assertNavbar(t, doc) -} diff --git a/tests/integration/version_test.go b/tests/integration/version_test.go deleted file mode 100644 index 631c7c1e25b..00000000000 --- a/tests/integration/version_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "net/http" - "testing" - - "gitea.dev/modules/setting" - "gitea.dev/modules/structs" - "gitea.dev/modules/test" - "gitea.dev/tests" - - "github.com/stretchr/testify/assert" -) - -func TestVersion(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.AppVer, "test-version-1")() - req := NewRequest(t, "GET", "/api/v1/version") - resp := MakeRequest(t, req, http.StatusOK) - - version := DecodeJSON(t, resp, &structs.ServerVersion{}) - assert.Equal(t, setting.AppVer, version.Version) -} diff --git a/tests/integration/view_test.go b/tests/integration/view_test.go index 30f5400c266..f2424a8049b 100644 --- a/tests/integration/view_test.go +++ b/tests/integration/view_test.go @@ -10,6 +10,7 @@ import ( "testing" "gitea.dev/modules/setting" + "gitea.dev/modules/structs" "gitea.dev/modules/test" "gitea.dev/modules/web" "gitea.dev/services/context" @@ -18,6 +19,16 @@ import ( "github.com/stretchr/testify/assert" ) +func TestVersion(t *testing.T) { + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.AppVer, "test-version-1")() + req := NewRequest(t, "GET", "/api/v1/version") + resp := MakeRequest(t, req, http.StatusOK) + + version := DecodeJSON(t, resp, &structs.ServerVersion{}) + assert.Equal(t, setting.AppVer, version.Version) +} + func TestView(t *testing.T) { defer tests.PrepareTestEnv(t)() t.Run("RenderFileSVGIsInImgTag", testRenderFileSVGIsInImgTag)