• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gameap / gameap / 29667068588

19 Jul 2026 12:30AM UTC coverage: 82.155% (-0.005%) from 82.16%
29667068588

push

github

web-flow
Plugin fixes (#28)

* plugin fixes

* review, SERVER_DELETED before SERVER_POST_DELETE fix

* review, prev status fix

* review, limit async dispatches

* review, fix manager and plugin id

* review, busy plugin

* ci logging fixes

* plugin load fix

* fix status transitions

349 of 436 new or added lines in 24 files covered. (80.05%)

3 existing lines in 2 files now uncovered.

47365 of 57653 relevant lines covered (82.16%)

34725.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.61
/internal/api/plugins/upload/install/handler.go
1
package install
2

3
import (
4
        "context"
5
        "log/slog"
6
        "net/http"
7
        "path"
8
        "strconv"
9
        "time"
10

11
        "github.com/gameap/gameap/internal/api/base"
12
        "github.com/gameap/gameap/internal/audit"
13
        "github.com/gameap/gameap/internal/files"
14
        "github.com/gameap/gameap/internal/plugin"
15
        "github.com/gameap/gameap/internal/repositories"
16
        "github.com/gameap/gameap/internal/services/plugininstall"
17
        "github.com/gameap/gameap/pkg/api"
18
        pkgplugin "github.com/gameap/gameap/pkg/plugin"
19
        "github.com/pkg/errors"
20
)
21

22
const extendedWriteDeadline = 5 * time.Minute
23

24
type LoaderManager interface {
25
        LoadTransient(
26
                ctx context.Context, wasmBytes []byte, config map[string]string, pluginID uint64,
27
        ) (*pkgplugin.LoadedPlugin, error)
28
}
29

30
type Handler struct {
31
        manager       LoaderManager
32
        pluginRepo    repositories.PluginRepository
33
        fileManager   files.FileManager
34
        loader        *plugin.Loader
35
        subscriptions plugininstall.SubscriptionRefresher
36
        pluginsDir    string
37
        responder     base.Responder
38
        audit         audit.Logger
39
}
40

41
func NewHandler(
42
        manager LoaderManager,
43
        pluginRepo repositories.PluginRepository,
44
        fileManager files.FileManager,
45
        loader *plugin.Loader,
46
        subscriptions plugininstall.SubscriptionRefresher,
47
        pluginsDir string,
48
        responder base.Responder,
49
        auditLogger audit.Logger,
50
) *Handler {
8✔
51
        if auditLogger == nil {
14✔
52
                auditLogger = audit.NopLogger{}
6✔
53
        }
6✔
54

55
        return &Handler{
8✔
56
                manager:       manager,
8✔
57
                pluginRepo:    pluginRepo,
8✔
58
                fileManager:   fileManager,
8✔
59
                loader:        loader,
8✔
60
                subscriptions: subscriptions,
8✔
61
                pluginsDir:    pluginsDir,
8✔
62
                responder:     responder,
8✔
63
                audit:         auditLogger,
8✔
64
        }
8✔
65
}
66

67
func (h *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
8✔
68
        ctx := r.Context()
8✔
69

8✔
70
        rc := http.NewResponseController(rw)
8✔
71
        if err := rc.SetWriteDeadline(time.Now().Add(extendedWriteDeadline)); err != nil {
16✔
72
                slog.WarnContext(ctx, "failed to extend write deadline", slog.String("error", err.Error()))
8✔
73
        }
8✔
74

75
        wasmBytes, err := plugininstall.ReadWASMFromMultipart(rw, r)
8✔
76
        if err != nil {
9✔
77
                h.responder.WriteError(ctx, rw, err)
1✔
78

1✔
79
                return
1✔
80
        }
1✔
81

82
        if err := plugininstall.ValidateWASM(wasmBytes); err != nil {
8✔
83
                h.responder.WriteError(ctx, rw, err)
1✔
84

1✔
85
                return
1✔
86
        }
1✔
87

88
        loaded, err := h.manager.LoadTransient(ctx, wasmBytes, nil, 0)
6✔
89
        if err != nil {
7✔
90
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to load plugin for validation"))
1✔
91

1✔
92
                return
1✔
93
        }
1✔
94
        defer func() {
10✔
95
                if err := loaded.Close(ctx); err != nil {
5✔
NEW
96
                        slog.WarnContext(ctx, "failed to close transient plugin",
×
NEW
97
                                slog.String("plugin_id", loaded.Info.Id),
×
NEW
98
                                slog.String("error", err.Error()))
×
NEW
99
                }
×
100
        }()
101

102
        dbID := pkgplugin.ParsePluginID(loaded.Info.Id)
5✔
103

5✔
104
        if err := plugininstall.CheckNotInstalled(ctx, h.pluginRepo, dbID); err != nil {
7✔
105
                h.responder.WriteError(ctx, rw, err)
2✔
106

2✔
107
                return
2✔
108
        }
2✔
109

110
        filename := strconv.FormatUint(uint64(dbID), 10) + ".wasm"
3✔
111
        pluginPath := path.Join(h.pluginsDir, filename)
3✔
112

3✔
113
        if err := h.fileManager.Write(ctx, pluginPath, wasmBytes); err != nil {
3✔
114
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to save plugin file"))
×
115

×
116
                return
×
117
        }
×
118

119
        pluginRecord := plugininstall.BuildPluginRecord(dbID, loaded, filename, "file://"+filename)
3✔
120

3✔
121
        if err := h.pluginRepo.Save(ctx, pluginRecord); err != nil {
3✔
122
                _ = h.fileManager.Delete(ctx, pluginPath)
×
123
                h.responder.WriteError(ctx, rw, errors.WithMessage(err, "failed to save plugin record"))
×
124

×
125
                return
×
126
        }
×
127

128
        audit.SensitiveOp(ctx, h.audit, audit.EventPluginInstall, audit.CategoryPluginOp,
3✔
129
                "plugin", strconv.FormatUint(uint64(dbID), 10), "install",
3✔
130
                slog.String("plugin", loaded.Info.Id))
3✔
131

3✔
132
        if err := plugininstall.TryLoadPlugin(ctx, h.loader, h.pluginRepo, pluginRecord, filename); err != nil {
3✔
133
                h.responder.WriteError(ctx, rw, api.WrapHTTPError(
×
134
                        errors.WithMessage(err, "plugin installed but failed to load"),
×
135
                        http.StatusUnprocessableEntity,
×
136
                ))
×
137

×
138
                return
×
139
        }
×
140

141
        plugininstall.RefreshSubscriptions(ctx, h.subscriptions)
3✔
142

3✔
143
        h.responder.Write(ctx, rw, newInstallResponse(pluginRecord))
3✔
144
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc